using System.Diagnostics;
using System.IO;
using System.Windows;
using AxCopilot.Models;
using System.Linq;

namespace AxCopilot.Services;

/// <summary>
/// L5-6: 자동화 스케줄 백그라운드 서비스.
/// 30초 간격으로 활성 스케줄을 검사하여 해당 시각에 액션을 실행합니다.
/// </summary>
public sealed class SchedulerService : IDisposable
{
    private readonly SettingsService _settings;
    private Timer?                   _timer;
    private bool                     _disposed;

    public SchedulerService(SettingsService settings)
    {
        _settings = settings;
    }

    // ─── 시작 / 중지 ─────────────────────────────────────────────────────
    public void Start()
    {
        // 30초 간격 체크 (즉시 1회 실행 후)
        _timer = new Timer(OnTick, null, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30));
        LogService.Info("SchedulerService 시작");
    }

    public void Stop()
    {
        _timer?.Change(Timeout.Infinite, Timeout.Infinite);
        LogService.Info("SchedulerService 중지");
    }

    public void Dispose()
    {
        if (_disposed) return;
        _disposed = true;
        _timer?.Dispose();
        _timer = null;
    }

    // ─── 트리거 검사 ─────────────────────────────────────────────────────
    private void OnTick(object? _)
    {
        try
        {
            var now       = DateTime.Now;
            var schedules = _settings.Settings.Schedules;
            bool dirty    = false;

            foreach (var entry in schedules.ToList()) // ToList: 반복 중 수정 방지
            {
                if (!ShouldFire(entry, now)) continue;

                LogService.Info($"스케줄 실행: '{entry.Name}' ({entry.TriggerType} {entry.TriggerTime})");
                ExecuteAction(entry);

                entry.LastRun = now;
                dirty = true;

                // once 트리거는 실행 후 비활성화
                if (entry.TriggerType == "once")
                    entry.Enabled = false;
            }

            if (dirty) _settings.Save();
        }
        catch (Exception ex)
        {
            LogService.Error($"SchedulerService 오류: {ex.Message}");
        }
    }

    // ─── 트리거 조건 검사 ─────────────────────────────────────────────────
    private static bool ShouldFire(ScheduleEntry entry, DateTime now)
    {
        if (!entry.Enabled) return false;
        if (!TimeSpan.TryParse(entry.TriggerTime, out var triggerTime)) return false;

        // 트리거 시각과 ±1분 이내인지 확인
        var targetDt = now.Date + triggerTime;
        if (Math.Abs((now - targetDt).TotalMinutes) > 1.0) return false;

        // 오늘 이미 실행했는지 확인 (once 제외)
        if (entry.TriggerType != "once" &&
            entry.LastRun.HasValue &&
            entry.LastRun.Value.Date == now.Date)
            return false;

        bool typeMatch = entry.TriggerType switch
        {
            "daily"    => true,
            "weekdays" => now.DayOfWeek >= DayOfWeek.Monday &&
                          now.DayOfWeek <= DayOfWeek.Friday,
            "weekly"   => entry.WeekDays.Count > 0 &&
                          entry.WeekDays.Contains((int)now.DayOfWeek),
            "once"     => !entry.LastRun.HasValue &&
                          entry.TriggerDate != null &&
                          DateTime.TryParse(entry.TriggerDate, out var d) &&
                          now.Date == d.Date,
            _          => false
        };

        if (!typeMatch) return false;

        // ─── L6-4: 프로세스 조건 검사 ─────────────────────────────────────
        if (!string.IsNullOrWhiteSpace(entry.ConditionProcess))
        {
            var procName = entry.ConditionProcess.Trim()
                .Replace(".exe", "", StringComparison.OrdinalIgnoreCase);

            bool isRunning = Process.GetProcessesByName(procName).Length > 0;

            if (entry.ConditionProcessMustRun && !isRunning) return false;
            if (!entry.ConditionProcessMustRun && isRunning)  return false;
        }

        return true;
    }

    // ─── 액션 실행 ────────────────────────────────────────────────────────
    private static void ExecuteAction(ScheduleEntry entry)
    {
        try
        {
            switch (entry.ActionType)
            {
                case "app":
                    if (!string.IsNullOrWhiteSpace(entry.ActionTarget))
                        Process.Start(new ProcessStartInfo
                        {
                            FileName        = entry.ActionTarget,
                            Arguments       = entry.ActionArgs ?? "",
                            UseShellExecute = true
                        });
                    break;

                case "notification":
                    var msg = string.IsNullOrWhiteSpace(entry.ActionTarget)
                        ? entry.Name
                        : entry.ActionTarget;
                    Application.Current?.Dispatcher.Invoke(() =>
                        NotificationService.Notify($"[스케줄] {entry.Name}", msg));
                    break;
            }
        }
        catch (Exception ex)
        {
            LogService.Warn($"스케줄 액션 실행 실패 '{entry.Name}': {ex.Message}");
        }
    }

    // ─── 유틸리티 (핸들러·편집기에서 공유) ──────────────────────────────

    /// <summary>지정 스케줄의 다음 실행 예정 시각을 계산합니다.</summary>
    public static DateTime? ComputeNextRun(ScheduleEntry entry)
    {
        if (!entry.Enabled) return null;
        if (!TimeSpan.TryParse(entry.TriggerTime, out var t)) return null;

        var now = DateTime.Now;

        return entry.TriggerType switch
        {
            "once" when
                !entry.LastRun.HasValue &&
                entry.TriggerDate != null &&
                DateTime.TryParse(entry.TriggerDate, out var d) &&
                (now.Date + t) >= now =>
                d.Date + t,

            "daily" => NextOccurrence(now, t, _ => true),

            "weekdays" => NextOccurrence(now, t, dt =>
                dt.DayOfWeek >= DayOfWeek.Monday && dt.DayOfWeek <= DayOfWeek.Friday),

            "weekly" when entry.WeekDays.Count > 0 => NextOccurrence(now, t, dt =>
                entry.WeekDays.Contains((int)dt.DayOfWeek)),

            _ => null
        };
    }

    private static DateTime? NextOccurrence(DateTime now, TimeSpan t, Func<DateTime, bool> dayFilter)
    {
        for (int i = 0; i <= 7; i++)
        {
            var candidate = now.Date.AddDays(i) + t;
            if (candidate > now && dayFilter(candidate))
                return candidate;
        }
        return null;
    }

    /// <summary>트리거 유형 표시 이름.</summary>
    public static string TriggerLabel(ScheduleEntry e) => e.TriggerType switch
    {
        "daily"    => "매일",
        "weekdays" => "주중(월~금)",
        "weekly"   => WeekDayLabel(e.WeekDays),
        "once"     => $"한번({e.TriggerDate})",
        _          => e.TriggerType
    };

    private static readonly string[] DayShort = ["일", "월", "화", "수", "목", "금", "토"];

    private static string WeekDayLabel(List<int> days) =>
        days.Count == 0 ? "매주(요일 미지정)" :
        "매주 " + string.Join("·", days.OrderBy(d => d).Select(d => DayShort[d]));
}
