using System.Diagnostics;
using System.Net.Http.Json;
using System.Net.WebSockets;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace ITNexusAgent.Services;

public class ShellService
{
    private readonly string _serverUrl;
    private readonly string _agentKey;
    private readonly string _hostname;

    public ShellService(string serverUrl, string agentKey, string hostname)
    {
        _serverUrl = serverUrl;
        _agentKey = agentKey;
        _hostname = hostname;
    }

    public async Task RunAsync(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            try { await ConnectAsync(ct); }
            catch (Exception ex) { AgentWorker.Log($"SHELL: {ex.Message}"); }

            if (!ct.IsCancellationRequested)
                await Task.Delay(TimeSpan.FromSeconds(15), ct).ContinueWith(_ => { });
        }
    }

    private async Task ConnectAsync(CancellationToken ct)
    {
        var wsUrl = _serverUrl
            .Replace("https://", "wss://")
            .Replace("http://", "ws://")
            + $"/ws?type=agent&key={Uri.EscapeDataString(_agentKey)}&hostname={Uri.EscapeDataString(_hostname)}";

        using var ws = new ClientWebSocket();
        ws.Options.SetRequestHeader("User-Agent", "IT-Nexus-Agent/2.3.0");

        await ws.ConnectAsync(new Uri(wsUrl), ct);
        AgentWorker.Log("SHELL: WebSocket verbunden");

        Process? shell = null;
        CancellationTokenSource? shellCts = null;

        async Task SendText(string text)
        {
            if (ws.State != WebSocketState.Open) return;
            var bytes = Encoding.UTF8.GetBytes(text);
            await ws.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);
        }

        void StartShell()
        {
            try { shellCts?.Cancel(); shell?.Kill(); } catch { }

            shellCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
            var token = shellCts.Token;

            var psi = new ProcessStartInfo("powershell.exe", "-NoExit -NoLogo -NoProfile")
            {
                CreateNoWindow = true,
                RedirectStandardInput = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                StandardOutputEncoding = Encoding.UTF8,
                StandardErrorEncoding = Encoding.UTF8,
            };

            shell = Process.Start(psi)!;
            AgentWorker.Log("SHELL: PowerShell gestartet");

            // stdout streamen
            _ = Task.Run(async () =>
            {
                var buf = new char[1024];
                while (!token.IsCancellationRequested)
                {
                    try
                    {
                        int n = await shell.StandardOutput.ReadAsync(buf, 0, buf.Length);
                        if (n == 0) break;
                        await SendText(new string(buf, 0, n));
                    }
                    catch { break; }
                }
            }, token);

            // stderr streamen (rot markiert)
            _ = Task.Run(async () =>
            {
                var buf = new char[1024];
                while (!token.IsCancellationRequested)
                {
                    try
                    {
                        int n = await shell.StandardError.ReadAsync(buf, 0, buf.Length);
                        if (n == 0) break;
                        await SendText("\x1b[31m" + new string(buf, 0, n) + "\x1b[0m");
                    }
                    catch { break; }
                }
            }, token);
        }

        void StopShell()
        {
            shellCts?.Cancel();
            try { shell?.Kill(); } catch { }
            shell = null;
            AgentWorker.Log("SHELL: PowerShell gestoppt");
        }

        try
        {
            var buffer = new byte[8192];
            while (ws.State == WebSocketState.Open && !ct.IsCancellationRequested)
            {
                WebSocketReceiveResult result;
                try
                {
                    result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), ct);
                }
                catch { break; }

                if (result.MessageType == WebSocketMessageType.Close) break;

                var msg = Encoding.UTF8.GetString(buffer, 0, result.Count);
                JObject? obj;
                try { obj = JObject.Parse(msg); }
                catch { continue; }

                switch (obj["type"]?.ToString())
                {
                    case "start_shell":
                        StartShell();
                        break;

                    case "input":
                        if (shell != null && !shell.HasExited)
                        {
                            var data = obj["data"]?.ToString() ?? "";
                            await shell.StandardInput.WriteAsync(data);
                            await shell.StandardInput.FlushAsync();
                        }
                        break;

                    case "stop_shell":
                        StopShell();
                        break;

                    case "announcements":
                        // Sofort-Push von Ankündigungen (ohne Warten auf Check-in)
                        var anns = obj["announcements"]?.ToObject<List<ITNexusAgent.Models.Announcement>>();
                        if (anns != null && anns.Count > 0)
                        {
                            var notifier = new NotificationService(
                                System.Diagnostics.Process.GetCurrentProcess().MainModule!.FileName,
                                @"C:\ProgramData\IT Nexus Agent");
                            notifier.ShowAnnouncements(anns);
                            AgentWorker.Log($"SHELL: {anns.Count} Ankündigung(en) via WebSocket empfangen");
                            // ACK senden + shown_announcements.json bereinigen damit gelöschte+neu erstellte
                            // Ankündigungen mit gleicher ID nicht geblockt werden
                            foreach (var ann in anns)
                            {
                                try
                                {
                                    await AckAnnouncementAsync(ann.Id);
                                    notifier.RemoveShownId(ann.Id);
                                }
                                catch (Exception ex) { AgentWorker.Log($"SHELL: ACK Fehler für ID {ann.Id}: {ex.Message}"); }
                            }
                        }
                        break;
                }
            }
        }
        finally
        {
            StopShell();
        }

        AgentWorker.Log("SHELL: WebSocket getrennt");
    }

    private async Task AckAnnouncementAsync(int announcementId)
    {
        using var http = new HttpClient();
        http.DefaultRequestHeaders.Add("x-agent-key", _agentKey);
        var body = JsonContent.Create(new { hostname = _hostname });
        await http.PostAsync($"{_serverUrl}/api/announcements/{announcementId}/ack-agent", body);
    }
}
