using System.IO;
using System.Diagnostics;
using System.Text.Json;

namespace NexusShell.Services.Diagnostics;

public sealed class DiagnosticsService
{
    private readonly WorkspaceEventLog _eventLog;
    private readonly DiagnosticSessionManager _sessionManager;
    private readonly DateTimeOffset _startedAtUtc = DateTimeOffset.UtcNow;

    public DiagnosticsService(WorkspaceEventLog eventLog, DiagnosticSessionManager sessionManager)
    {
        _eventLog = eventLog;
        _sessionManager = sessionManager;
    }

    public DateTimeOffset StartedAtUtc => _startedAtUtc;
    public TimeSpan Uptime => DateTimeOffset.UtcNow - _startedAtUtc;
    public DiagnosticSessionInfo? ActiveSession => _sessionManager.ActiveSession;
    public int PendingLogEntries => _eventLog.PendingCount;
    public int DroppedLogEntries => _eventLog.DroppedCount;

    public void EnsureSessionStarted()
    {
        if (_sessionManager.ActiveSession is not { IsActive: true })
        {
            _sessionManager.StartSession();
            LogLifecycle("diagnostics_session_started", "Diagnostiksession startet");
        }
    }

    public void StartSession()
    {
        _sessionManager.StartSession();
        LogLifecycle("diagnostics_session_started", "Diagnostiksession startet manuelt");
    }

    public void StopSession()
    {
        LogLifecycle("diagnostics_session_stopped", "Diagnostiksession stoppet");
        _sessionManager.EndSession();
    }

    public async Task<string> ExportSessionZipAsync(CancellationToken cancellationToken = default)
    {
        EnsureSessionStarted();
        WriteSupportFiles();
        return await _sessionManager.ExportSessionZipAsync(cancellationToken);
    }

    public async Task<DiagnosticUploadResult> SendDiagnosticsAsync(
        Uri? endpoint = null,
        CancellationToken cancellationToken = default)
    {
        EnsureSessionStarted();
        var zipPath = await _sessionManager.ExportSessionZipAsync(cancellationToken);
        var supportId = $"NX-{ActiveSession?.SessionId ?? DateTimeOffset.UtcNow.ToString("yyyyMMdd-HHmmss")}";

        if (endpoint is null)
        {
            var receiptPath = Path.Combine(
                Path.GetDirectoryName(zipPath)!,
                $"diagnostic-receipt-{supportId}.json");
            File.WriteAllText(receiptPath, JsonSerializer.Serialize(new
            {
                supportId,
                zipPath,
                uploaded = false,
                reason = "Upload endpoint mangler i POC",
                redaction = "Passwords, tokens, bearer headers, secrets and credentialed connection strings are redacted before export.",
                createdAtUtc = DateTimeOffset.UtcNow,
            }, new JsonSerializerOptions { WriteIndented = true }));

            LogLifecycle("diagnostics_upload_pending", "Diagnostik exporteret lokalt; upload endpoint mangler",
                new Dictionary<string, string>
                {
                    ["supportId"] = supportId,
                    ["zipPath"] = zipPath,
                });

            return new DiagnosticUploadResult(
                supportId,
                zipPath,
                receiptPath,
                Uploaded: false,
                "Upload endpoint mangler. Diagnostikpakke er gemt lokalt.");
        }

        LogLifecycle("diagnostics_upload_ready", "Diagnostikpakke klar til upload",
            new Dictionary<string, string>
            {
                ["supportId"] = supportId,
                ["endpoint"] = endpoint.Host,
            });

        return new DiagnosticUploadResult(
            supportId,
            zipPath,
            ReceiptPath: null,
            Uploaded: false,
            "Upload endpoint er konfigureret, men HTTP upload er ikke aktiveret i denne POC.");
    }

    public void OpenLogsFolder() => _sessionManager.OpenLogsFolder();

    public IReadOnlyList<DiagnosticLogEntry> GetRecentEvents(int maxCount = 50) =>
        _eventLog.ReadRecentEntries(maxCount);

    public void LogLifecycle(string eventName, string message, IReadOnlyDictionary<string, string>? props = null) =>
        Write(DiagnosticEventCategory.Lifecycle, eventName, message, props);

    public void LogWorkspace(string eventName, string message, IReadOnlyDictionary<string, string>? props = null) =>
        Write(DiagnosticEventCategory.Workspace, eventName, message, props);

    public void LogWindow(string eventName, string message, IReadOnlyDictionary<string, string>? props = null) =>
        Write(DiagnosticEventCategory.Window, eventName, message, props);

    public void LogLaunch(string eventName, string message, IReadOnlyDictionary<string, string>? props = null) =>
        Write(DiagnosticEventCategory.Launch, eventName, message, props);

    public void LogPerformance(string eventName, string message, IReadOnlyDictionary<string, string>? props = null) =>
        Write(DiagnosticEventCategory.Performance, eventName, message, props);

    public IReadOnlyList<NexusTimerSnapshot> ActiveTimers => NexusTimerRegistry.Snapshot();

    public void LogTimerSnapshot()
    {
        var timers = ActiveTimers;
        Write(DiagnosticEventCategory.Performance, "timer_snapshot",
            $"{timers.Count} aktive timer(e)",
            timers.Count == 0
                ? null
                : new Dictionary<string, string>
                {
                    ["activeTimers"] = string.Join(", ", timers.Select(t => $"{t.Owner}:{t.Name}@{t.Interval.TotalSeconds:0.#}s")),
                });
    }

    public T TrackOperation<T>(string operation, Func<T> action, long warnAfterMs = 200)
    {
        var sw = Stopwatch.StartNew();
        try
        {
            return action();
        }
        finally
        {
            sw.Stop();
            LogOperationTiming(operation, sw.ElapsedMilliseconds, warnAfterMs);
        }
    }

    public void TrackOperation(string operation, Action action, long warnAfterMs = 200)
    {
        var sw = Stopwatch.StartNew();
        try
        {
            action();
        }
        finally
        {
            sw.Stop();
            LogOperationTiming(operation, sw.ElapsedMilliseconds, warnAfterMs);
        }
    }

    public async Task<T> TrackOperationAsync<T>(
        string operation,
        Func<CancellationToken, Task<T>> action,
        long warnAfterMs = 200,
        CancellationToken cancellationToken = default)
    {
        var sw = Stopwatch.StartNew();
        try
        {
            return await action(cancellationToken);
        }
        finally
        {
            sw.Stop();
            LogOperationTiming(operation, sw.ElapsedMilliseconds, warnAfterMs);
        }
    }

    public void LogApi(string operation, string message, long durationMs) =>
        Write(DiagnosticEventCategory.Api, "api_call", message,
            new Dictionary<string, string>
            {
                ["operation"] = operation,
                ["durationMs"] = durationMs.ToString(),
            });

    public void LogError(string eventName, string message, Exception? ex = null)
    {
        var props = ex is null
            ? null
            : new Dictionary<string, string> { ["exceptionType"] = ex.GetType().Name };
        Write(DiagnosticEventCategory.Error, eventName, message, props);
    }

    private void Write(
        DiagnosticEventCategory category,
        string eventName,
        string message,
        IReadOnlyDictionary<string, string>? props)
    {
        EnsureSessionStarted();
        _eventLog.Enqueue(new DiagnosticLogEntry(
            DateTimeOffset.UtcNow,
            category,
            eventName,
            LogRedactor.Redact(message),
            LogRedactor.RedactProperties(props)));
    }

    private void LogOperationTiming(string operation, long durationMs, long warnAfterMs)
    {
        if (durationMs < warnAfterMs)
        {
            return;
        }

        var props = new Dictionary<string, string>
        {
            ["operation"] = operation,
            ["durationMs"] = durationMs.ToString(),
            ["thresholdMs"] = warnAfterMs.ToString(),
        };

        Write(DiagnosticEventCategory.Performance,
            "long_operation",
            $"{operation}: {durationMs} ms",
            props);
    }

    private void WriteSupportFiles()
    {
        var session = ActiveSession;
        if (session is null)
        {
            return;
        }

        Directory.CreateDirectory(session.SessionDirectory);
        var timerSnapshots = ActiveTimers;
        var recent = GetRecentEvents(25);
        var summary =
            $"Ninja Nexus diagnostic support summary{Environment.NewLine}" +
            $"Session: {session.SessionId}{Environment.NewLine}" +
            $"Started UTC: {session.StartedAtUtc:u}{Environment.NewLine}" +
            $"Uptime: {Uptime}{Environment.NewLine}" +
            $"Pending logs: {PendingLogEntries}{Environment.NewLine}" +
            $"Dropped logs: {DroppedLogEntries}{Environment.NewLine}" +
            $"Active timers: {timerSnapshots.Count}{Environment.NewLine}" +
            $"Recent events: {recent.Count}{Environment.NewLine}" +
            $"Export mode: local export only{Environment.NewLine}";

        File.WriteAllText(Path.Combine(session.SessionDirectory, "support-summary.txt"), summary);
        File.WriteAllText(
            Path.Combine(session.SessionDirectory, "timers.json"),
            JsonSerializer.Serialize(timerSnapshots, new JsonSerializerOptions { WriteIndented = true }));
    }
}

public sealed record DiagnosticUploadResult(
    string SupportId,
    string ZipPath,
    string? ReceiptPath,
    bool Uploaded,
    string Message);
