using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;

internal static class OverlayDesktopApp
{
    private const string Secret = "OBS-BASEBALL-OVERLAY-2026-LICENSE-SECRET-v1";
    private static readonly string Root = AppDomain.CurrentDomain.BaseDirectory;
    private static readonly string LicensePath = Path.Combine(Root, "license.json");
    private static string scoreboardState = "null";
    private static readonly Dictionary<string, string> Types = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
    {
        { ".html", "text/html; charset=utf-8" },
        { ".css", "text/css; charset=utf-8" },
        { ".js", "text/javascript; charset=utf-8" },
        { ".json", "application/json; charset=utf-8" },
        { ".png", "image/png" },
        { ".jpg", "image/jpeg" },
        { ".jpeg", "image/jpeg" },
        { ".svg", "image/svg+xml" }
    };

    private static void Main()
    {
        var port = 5177;
        int.TryParse(Environment.GetEnvironmentVariable("PORT"), out port);
        if (port <= 0) port = 5177;
        var listener = new TcpListener(IPAddress.Any, port);
        try
        {
            listener.Start();
        }
        catch (Exception error)
        {
            Console.WriteLine("Impossibile avviare il server sulla porta " + port + ": " + error.Message);
            Console.WriteLine("Chiudi eventuali altre copie dell'app e riavvia.");
            Console.ReadKey();
            return;
        }

        Console.Title = "OBS Baseball Overlay";
        Console.WriteLine("Controller: http://localhost:" + port + "/controller.html");
        Console.WriteLine("Overlay OBS: http://localhost:" + port + "/overlay.html");
        Console.WriteLine("Line score OBS: http://localhost:" + port + "/line-score.html");
        Console.WriteLine("Lineup controller: http://localhost:" + port + "/lineup-controller.html");
        Console.WriteLine("Lineup OBS: http://localhost:" + port + "/lineup-overlay.html");
        Console.WriteLine();
        Console.WriteLine("Tieni aperta questa finestra mentre usi OBS.");
        Console.WriteLine("Premi Ctrl+C per fermare il server.");

        OpenBrowser("http://localhost:" + port + "/controller.html");

        while (true)
        {
            var client = listener.AcceptTcpClient();
            ThreadPool.QueueUserWorkItem(_ => Handle(client));
        }
    }

    private static void Handle(TcpClient client)
    {
        using (client)
        {
            try
            {
                var stream = client.GetStream();
                var request = ReadRequest(stream);
                if (request == null) return;
                var path = request.Path;

                if (path == "/api/state")
                {
                    if (request.Method == "GET")
                    {
                        Write(stream, 200, "OK", scoreboardState, "application/json; charset=utf-8");
                        return;
                    }
                    if (request.Method == "POST")
                    {
                        scoreboardState = request.Body;
                        Write(stream, 204, "No Content", "", "text/plain; charset=utf-8");
                        return;
                    }
                }

                if (path == "/api/license")
                {
                    if (request.Method == "GET")
                    {
                        Write(stream, 200, "OK", LicenseStatusJson(), "application/json; charset=utf-8");
                        return;
                    }
                    if (request.Method == "POST")
                    {
                        File.WriteAllText(LicensePath, request.Body, Encoding.UTF8);
                        Write(stream, 200, "OK", LicenseStatusJson(), "application/json; charset=utf-8");
                        return;
                    }
                }

                var license = GetLicenseStatus();
                if (!license.Valid && IsProtectedPath(path))
                {
                    Redirect(stream, "/license.html");
                    return;
                }

                ServeFile(stream, path == "/" ? "/controller.html" : path);
            }
            catch
            {
                try { Write(client.GetStream(), 500, "Server Error", "Server error", "text/plain; charset=utf-8"); } catch { }
            }
        }
    }

    private static bool IsProtectedPath(string path)
    {
        if (path == "/license.html" || path == "/styles.css" || path == "/scoreboard.js" || path.StartsWith("/assets/")) return false;
        if (path.StartsWith("/api/")) return false;
        return path.EndsWith(".html", StringComparison.OrdinalIgnoreCase) || path == "/";
    }

    private static HttpRequest ReadRequest(NetworkStream stream)
    {
        var buffer = new byte[1024 * 1024];
        var total = 0;
        var headerEnd = -1;
        while (total < buffer.Length)
        {
            var read = stream.Read(buffer, total, buffer.Length - total);
            if (read <= 0) return null;
            total += read;
            var text = Encoding.UTF8.GetString(buffer, 0, total);
            headerEnd = text.IndexOf("\r\n\r\n", StringComparison.Ordinal);
            if (headerEnd >= 0) break;
        }
        if (headerEnd < 0) return null;
        var headerText = Encoding.UTF8.GetString(buffer, 0, headerEnd);
        var lines = headerText.Split(new[] { "\r\n" }, StringSplitOptions.None);
        var first = lines[0].Split(' ');
        if (first.Length < 2) return null;
        var contentLength = 0;
        foreach (var line in lines)
        {
            if (line.StartsWith("Content-Length:", StringComparison.OrdinalIgnoreCase))
            {
                int.TryParse(line.Substring("Content-Length:".Length).Trim(), out contentLength);
            }
        }
        var bodyStart = headerEnd + 4;
        while (total - bodyStart < contentLength && total < buffer.Length)
        {
            var read = stream.Read(buffer, total, buffer.Length - total);
            if (read <= 0) break;
            total += read;
        }
        var body = contentLength > 0 ? Encoding.UTF8.GetString(buffer, bodyStart, Math.Min(contentLength, total - bodyStart)) : "";
        var path = first[1].Split('?')[0];
        return new HttpRequest(first[0], Uri.UnescapeDataString(path), body);
    }

    private static void ServeFile(NetworkStream stream, string relativePath)
    {
        var safe = relativePath.TrimStart('/').Replace('/', Path.DirectorySeparatorChar);
        var fullPath = Path.GetFullPath(Path.Combine(Root, safe));
        if (!fullPath.StartsWith(Root, StringComparison.OrdinalIgnoreCase) || !File.Exists(fullPath))
        {
            Write(stream, 404, "Not Found", "Not found", "text/plain; charset=utf-8");
            return;
        }

        var extension = Path.GetExtension(fullPath);
        var bytes = File.ReadAllBytes(fullPath);
        WriteBytes(stream, 200, "OK", bytes, Types.ContainsKey(extension) ? Types[extension] : "application/octet-stream");
    }

    private static void Redirect(NetworkStream stream, string location)
    {
        var header = "HTTP/1.1 302 Found\r\nLocation: " + location + "\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
        var bytes = Encoding.ASCII.GetBytes(header);
        stream.Write(bytes, 0, bytes.Length);
    }

    private static void Write(NetworkStream stream, int status, string statusText, string text, string contentType)
    {
        WriteBytes(stream, status, statusText, Encoding.UTF8.GetBytes(text), contentType);
    }

    private static void WriteBytes(NetworkStream stream, int status, string statusText, byte[] bytes, string contentType)
    {
        var header = "HTTP/1.1 " + status + " " + statusText + "\r\n" +
            "Content-Type: " + contentType + "\r\n" +
            "Content-Length: " + bytes.Length + "\r\n" +
            "Cache-Control: no-store\r\n" +
            "Connection: close\r\n\r\n";
        var headerBytes = Encoding.ASCII.GetBytes(header);
        stream.Write(headerBytes, 0, headerBytes.Length);
        if (bytes.Length > 0) stream.Write(bytes, 0, bytes.Length);
    }

    private static string LicenseStatusJson()
    {
        var status = GetLicenseStatus();
        return "{\"valid\":" + (status.Valid ? "true" : "false") +
            ",\"licensee\":\"" + Escape(status.Licensee) +
            "\",\"expires\":\"" + Escape(status.Expires) +
            "\",\"message\":\"" + Escape(status.Message) + "\"}";
    }

    private static LicenseStatus GetLicenseStatus()
    {
        if (!File.Exists(LicensePath)) return new LicenseStatus(false, "", "", "Licenza non attivata");
        var json = File.ReadAllText(LicensePath, Encoding.UTF8);
        var licensee = JsonValue(json, "licensee");
        var expires = JsonValue(json, "expires");
        var key = JsonValue(json, "key");
        if (licensee.Length == 0 || expires.Length == 0 || key.Length == 0) return new LicenseStatus(false, licensee, expires, "Licenza incompleta");
        DateTime date;
        if (!DateTime.TryParse(expires, out date)) return new LicenseStatus(false, licensee, expires, "Data licenza non valida");
        if (date.Date < DateTime.Today) return new LicenseStatus(false, licensee, expires, "Licenza scaduta");
        var expected = GenerateKey(licensee, expires);
        if (!String.Equals(expected, key, StringComparison.OrdinalIgnoreCase)) return new LicenseStatus(false, licensee, expires, "Codice licenza non valido");
        return new LicenseStatus(true, licensee, expires, "Licenza valida");
    }

    private static string GenerateKey(string licensee, string expires)
    {
        using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(Secret)))
        {
            var data = Encoding.UTF8.GetBytes(Normalize(licensee) + "|" + expires.Trim());
            var hash = hmac.ComputeHash(data);
            return BitConverter.ToString(hash).Replace("-", "").Substring(0, 24);
        }
    }

    private static string Normalize(string value)
    {
        return (value ?? "").Trim().ToUpperInvariant();
    }

    private static string JsonValue(string json, string key)
    {
        var marker = "\"" + key + "\"";
        var index = json.IndexOf(marker, StringComparison.OrdinalIgnoreCase);
        if (index < 0) return "";
        var colon = json.IndexOf(':', index);
        var firstQuote = json.IndexOf('"', colon + 1);
        var secondQuote = firstQuote < 0 ? -1 : json.IndexOf('"', firstQuote + 1);
        if (firstQuote < 0 || secondQuote < 0) return "";
        return json.Substring(firstQuote + 1, secondQuote - firstQuote - 1).Replace("\\\"", "\"");
    }

    private static string Escape(string value)
    {
        return (value ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"");
    }

    private static void OpenBrowser(string url)
    {
        try { Process.Start(url); } catch { }
    }

    private sealed class LicenseStatus
    {
        public readonly bool Valid;
        public readonly string Licensee;
        public readonly string Expires;
        public readonly string Message;

        public LicenseStatus(bool valid, string licensee, string expires, string message)
        {
            Valid = valid;
            Licensee = licensee;
            Expires = expires;
            Message = message;
        }
    }

    private sealed class HttpRequest
    {
        public readonly string Method;
        public readonly string Path;
        public readonly string Body;

        public HttpRequest(string method, string path, string body)
        {
            Method = method;
            Path = path;
            Body = body;
        }
    }
}
