using System.Collections.ObjectModel;
using NexusInstaller.Models;

namespace NexusInstaller.Services;

public sealed class InstallProgressCoordinator
{
    public const int MaxRecentEvents = 50;
    public static readonly TimeSpan StepTimeout = TimeSpan.FromMinutes(10);
    public static readonly TimeSpan HungCheckInterval = TimeSpan.FromSeconds(2);

    private readonly InstallerLogService _log;
    private readonly object _lock = new();
    private DateTimeOffset? _runningSince;
    private int? _runningIndex;
    private CancellationTokenSource? _hungTimerCts;

    public InstallProgressCoordinator()
    {
        _log = InstallerLogService.Instance;
        RecentEvents = new ObservableCollection<InstallProgressEvent>();
    }

    internal InstallProgressCoordinator(InstallerLogService log)
    {
        _log = log;
        RecentEvents = new ObservableCollection<InstallProgressEvent>();
    }

    public event Action? ProgressChanged;

    public ObservableCollection<InstallProgressEvent> RecentEvents { get; }

    public bool IsHung { get; private set; }

    public void Reset()
    {
        lock (_lock)
        {
            IsHung = false;
            _runningSince = null;
            _runningIndex = null;
            StopHungWatch();
            RecentEvents.Clear();
        }

        ProgressChanged?.Invoke();
    }

    public void ReportStepStarted(InstallPhaseItem step)
    {
        step.Status = InstallStepStatus.Running;
        step.StartedAt = DateTimeOffset.Now;
        step.Detail = null;

        lock (_lock)
        {
            IsHung = false;
            _runningSince = step.StartedAt;
            _runningIndex = step.Index;
            StartHungWatch(step);
        }

        AddEvent(InstallProgressEventKind.StepStarted, $"Starter: {step.Label}", step.Index);
        _log.Info($"Install step {step.Index + 1}: {step.Label} — kører");
        ProgressChanged?.Invoke();
    }

    public void ReportStepRetrying(InstallPhaseItem step, string reason)
    {
        step.Status = InstallStepStatus.Retrying;
        step.Detail = reason;
        step.StartedAt = DateTimeOffset.Now;

        lock (_lock)
        {
            IsHung = false;
            _runningSince = step.StartedAt;
            _runningIndex = step.Index;
            StartHungWatch(step);
        }

        AddEvent(InstallProgressEventKind.StepRetrying, $"Genprøver {step.Label}: {reason}", step.Index);
        _log.Warn($"Install step {step.Index + 1}: genprøver — {reason}");
        ProgressChanged?.Invoke();
    }

    public void ReportStepCompleted(InstallPhaseItem step, string? detail = null)
    {
        step.Status = InstallStepStatus.Completed;
        step.Detail = detail;
        step.StartedAt = null;

        lock (_lock)
        {
            if (_runningIndex == step.Index)
            {
                _runningSince = null;
                _runningIndex = null;
                StopHungWatch();
            }
        }

        AddEvent(InstallProgressEventKind.StepCompleted, $"Færdig: {step.Label}", step.Index);
        _log.Info($"Install step {step.Index + 1}: {step.Label} — færdig");
        ProgressChanged?.Invoke();
    }

    public void ReportStepSkipped(InstallPhaseItem step, string? reason = null)
    {
        step.Status = InstallStepStatus.Skipped;
        step.Detail = reason;
        step.StartedAt = null;

        AddEvent(InstallProgressEventKind.StepSkipped, $"Sprunget over: {step.Label}", step.Index);
        _log.Info($"Install step {step.Index + 1}: {step.Label} — sprunget over");
        ProgressChanged?.Invoke();
    }

    public void ReportStepFailed(InstallPhaseItem step, string error)
    {
        step.Status = InstallStepStatus.Failed;
        step.Detail = error;
        step.StartedAt = null;

        lock (_lock)
        {
            if (_runningIndex == step.Index)
            {
                _runningSince = null;
                _runningIndex = null;
                StopHungWatch();
            }
        }

        AddEvent(InstallProgressEventKind.StepFailed, $"{step.Label}: {error}", step.Index);
        _log.Error($"Install step {step.Index + 1}: {step.Label} — {error}");
        ProgressChanged?.Invoke();
    }

    public void ReportStepBlocked(InstallPhaseItem step, string reason)
    {
        step.Status = InstallStepStatus.Blocked;
        step.Detail = reason;
        step.StartedAt = null;

        AddEvent(InstallProgressEventKind.Warning, $"Blokeret: {step.Label} — {reason}", step.Index);
        _log.Warn($"Install step {step.Index + 1}: blokeret — {reason}");
        ProgressChanged?.Invoke();
    }

    public void ReportInfo(string message) => AddEvent(InstallProgressEventKind.Info, message, null);

    public void ReportFileLock(string message)
    {
        AddEvent(InstallProgressEventKind.FileLock, message, _runningIndex);
        _log.Warn(message);
        ProgressChanged?.Invoke();
    }

    public void ReportTimeout(InstallPhaseItem step)
    {
        lock (_lock)
        {
            IsHung = true;
        }

        step.Detail = $"Timeout efter {(int)StepTimeout.TotalMinutes} min";
        AddEvent(InstallProgressEventKind.Timeout, $"Timeout: {step.Label}", step.Index);
        _log.Error($"Install step {step.Index + 1}: timeout — {step.Label}");
        ProgressChanged?.Invoke();
    }

    private void StartHungWatch(InstallPhaseItem step)
    {
        StopHungWatch();
        _hungTimerCts = new CancellationTokenSource();
        var token = _hungTimerCts.Token;
        _ = Task.Run(async () =>
        {
            try
            {
                await Task.Delay(StepTimeout, token);
                if (!token.IsCancellationRequested && step.Status is InstallStepStatus.Running or InstallStepStatus.Retrying)
                {
                    ReportTimeout(step);
                }
            }
            catch (OperationCanceledException)
            {
                // expected when step completes
            }
        }, token);
    }

    private void StopHungWatch()
    {
        _hungTimerCts?.Cancel();
        _hungTimerCts?.Dispose();
        _hungTimerCts = null;
    }

    private void AddEvent(InstallProgressEventKind kind, string message, int? stepIndex)
    {
        var evt = new InstallProgressEvent
        {
            Timestamp = DateTimeOffset.Now,
            Kind = kind,
            Message = Sanitize(message),
            StepIndex = stepIndex,
        };

        lock (_lock)
        {
            RecentEvents.Insert(0, evt);
            while (RecentEvents.Count > MaxRecentEvents)
            {
                RecentEvents.RemoveAt(RecentEvents.Count - 1);
            }
        }

        ProgressChanged?.Invoke();
    }

    private static string Sanitize(string message)
    {
        if (string.IsNullOrEmpty(message))
        {
            return string.Empty;
        }

        foreach (var marker in new[] { "password", "token", "secret", "apikey", "api_key" })
        {
            var idx = message.IndexOf(marker, StringComparison.OrdinalIgnoreCase);
            if (idx >= 0)
            {
                return message[..idx] + "[redacted]";
            }
        }

        return message;
    }
}
