using AxCopilot.Models;

namespace AxCopilot.Services.Agent;

/// <summary>
/// Phase 17-C: AgentLoopService — 확장 훅 시스템 통합.
/// 에이전트 라이프사이클 이벤트(SessionStart/End, UserPromptSubmit,
/// PreToolUse/PostToolUse, PreCompact/PostCompact 등)에
/// ExtendedHookRunner를 연결합니다.
/// </summary>
public partial class AgentLoopService
{
    /// <summary>
    /// 지정 이벤트의 확장 훅을 실행합니다.
    /// - additionalContext가 있으면 시스템 메시지에 주입
    /// - Block인 경우 true 반환 → 호출자가 작업을 중단해야 함
    /// </summary>
    internal async Task<bool> RunExtendedEventAsync(
        HookEventKind kind,
        List<ChatMessage>? messages,
        CancellationToken ct,
        string? toolName = null,
        string? toolInput = null,
        string? toolOutput = null,
        bool toolSuccess = true,
        string? userMessage = null,
        string? changedFile = null)
    {
        var hooks = GetExtendedHooks(kind);
        if (hooks.Count == 0) return false;

        var ctx = new HookContext
        {
            Event          = kind,
            ToolName       = toolName,
            ToolInput      = toolInput,
            ToolOutput     = toolOutput,
            ToolSuccess    = toolSuccess,
            UserMessage    = userMessage,
            ChangedFilePath = changedFile,
            SessionId      = _sessionId,
            WorkFolder     = _settings.Settings.Llm.WorkFolder ?? "",
        };

        try
        {
            var result = await ExtendedHookRunner.RunEventAsync(hooks, ctx, ct, _llm);
            ApplyExtendedHookResult(result, messages);

            // Phase 17-C4: SessionStart 훅 WatchPaths → FileWatcherService 등록
            if (kind == HookEventKind.SessionStart &&
                result.WatchPaths != null && result.WatchPaths.Count > 0)
            {
                _fileWatcher.ClearAll();  // 이전 세션 감시 경로 초기화
                var workFolder = _settings.Settings.Llm.WorkFolder ?? "";
                foreach (var pathPattern in result.WatchPaths)
                {
                    // 상대 경로는 workFolder 기준으로 해석
                    var absPattern = System.IO.Path.IsPathRooted(pathPattern)
                        ? pathPattern
                        : System.IO.Path.Combine(workFolder, pathPattern);
                    _fileWatcher.Watch(absPattern, changedPath =>
                    {
                        // FileChanged 훅 fire-and-forget
                        _ = RunExtendedEventAsync(
                            HookEventKind.FileChanged, null, CancellationToken.None,
                            changedFile: changedPath);
                        EmitEvent(AgentEventType.Thinking, "file_watcher",
                            $"파일 변경 감지: {System.IO.Path.GetFileName(changedPath)}");
                    });
                }
                EmitEvent(AgentEventType.Thinking, "file_watcher",
                    $"watchPaths 등록 완료 ({result.WatchPaths.Count}개 패턴)");
            }

            // HookFired 이벤트 로그 기록
            if (_eventLog != null && hooks.Count > 0)
                _ = _eventLog.AppendAsync(AgentEventLogType.HookFired,
                    System.Text.Json.JsonSerializer.Serialize(new
                    {
                        eventKind = kind.ToString(),
                        hookCount = hooks.Count,
                        blocked   = result.Block
                    }));

            return result.Block;
        }
        catch (Exception ex)
        {
            LogService.Warn($"[ExtendedHook] {kind} 훅 실행 오류: {ex.Message}");
            return false;
        }
    }

    /// <summary>훅 결과의 additionalContext를 시스템 메시지에 주입합니다.</summary>
    private static void ApplyExtendedHookResult(ExtendedHookResult result, List<ChatMessage>? messages)
    {
        if (string.IsNullOrEmpty(result.AdditionalContext) || messages == null) return;

        const string marker = "[Hook Context]";
        var sysMsg = messages.FirstOrDefault(m => m.Role == "system");
        if (sysMsg != null)
        {
            // 기존 [Hook Context] 섹션 교체 (중복 방지)
            var idx = sysMsg.Content.IndexOf(marker, StringComparison.Ordinal);
            if (idx >= 0)
                sysMsg.Content = sysMsg.Content[..idx] + marker + "\n" + result.AdditionalContext;
            else
                sysMsg.Content += $"\n\n{marker}\n{result.AdditionalContext}";
        }
        else
        {
            messages.Insert(0, new ChatMessage
            {
                Role    = "system",
                Content = $"{marker}\n{result.AdditionalContext}"
            });
        }
    }

    // ─────────────────────────────────────────────────────────────────────
    //  설정 → 런타임 엔트리 변환
    // ─────────────────────────────────────────────────────────────────────

    /// <summary>설정에서 특정 이벤트의 활성 훅 엔트리 목록을 반환합니다.</summary>
    private List<ExtendedHookEntry> GetExtendedHooks(HookEventKind kind)
    {
        var cfg = _settings.Settings.Llm.ExtendedHooks;

        IReadOnlyList<ExtendedHookEntryConfig>? configs = kind switch
        {
            HookEventKind.SessionStart       => cfg.SessionStart,
            HookEventKind.SessionEnd         => cfg.SessionEnd,
            HookEventKind.AgentStop          => cfg.AgentStop,
            HookEventKind.UserPromptSubmit   => cfg.UserPromptSubmit,
            HookEventKind.PreToolUse         => cfg.PreToolUse,
            HookEventKind.PostToolUse        => cfg.PostToolUse,
            HookEventKind.PostToolUseFailure => cfg.PostToolUseFailure,
            HookEventKind.PreCompact         => cfg.PreCompact,
            HookEventKind.PostCompact        => cfg.PostCompact,
            HookEventKind.FileChanged        => cfg.FileChanged,
            HookEventKind.PermissionRequest  => cfg.PermissionRequest,
            _                                => null
        };

        if (configs == null || configs.Count == 0) return new List<ExtendedHookEntry>();
        return ConvertToRuntimeEntries(configs, kind);
    }

    /// <summary>설정 모델(ExtendedHookEntryConfig) → 런타임 모델(ExtendedHookEntry) 변환.</summary>
    private static List<ExtendedHookEntry> ConvertToRuntimeEntries(
        IReadOnlyList<ExtendedHookEntryConfig> configs, HookEventKind kind)
    {
        return configs
            .Where(c => c.Enabled)
            .Select(c => new ExtendedHookEntry
            {
                Name           = c.Name,
                Event          = kind,
                Matcher        = c.Matcher,
                Mode           = ParseMode(c.Mode),
                ScriptPath     = c.ScriptPath,
                Url            = c.Url,
                Prompt         = c.Prompt,
                Model          = c.Model,
                Enabled        = c.Enabled,
                Once           = c.Once,
                IsAsync        = c.IsAsync,
                TimeoutSeconds = c.TimeoutSeconds > 0 ? c.TimeoutSeconds : 30,
                StatusMessage  = c.StatusMessage,
                WatchPaths     = c.WatchPaths ?? new List<string>(),
            })
            .ToList();
    }

    private static HookExecutionMode ParseMode(string? mode) =>
        mode?.ToLowerInvariant() switch
        {
            "http"   => HookExecutionMode.Http,
            "prompt" => HookExecutionMode.Prompt,
            "agent"  => HookExecutionMode.Agent,
            _        => HookExecutionMode.Command
        };
}
