using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace NcProgramManager.Tests.Integration.Stubs
{
    /// <summary>
    /// Minimal in-process LSV2 server.
    /// Frame format: [0x02][SIZE_HI][SIZE_LO][CMD_HI][CMD_LO][data...][CRC_HI][CRC_LO]
    /// SIZE = len(CMD) + len(data) = 2 + dataLen
    /// </summary>
    public sealed class Lsv2ServerStub : IDisposable
    {
        private const ushort T_OK         = 0x0000;
        private const ushort T_SE         = 0x0001;
        private const ushort T_LS_SELECT  = 0x00A0;
        private const ushort T_LS_LOG_IN  = 0x00A1;
        private const ushort T_LS_LOG_OUT = 0x00A2;
        private const ushort T_R_FL       = 0x0023;
        private const ushort T_S_FL       = 0x0024;
        private const ushort T_FD         = 0x0025;

        private readonly TcpListener _listener;
        private readonly Thread _acceptThread;
        private volatile bool _running;

        public int Port { get; }

        /// <summary>In-memory file store: full path → content.</summary>
        public Dictionary<string, string> Files { get; } = new Dictionary<string, string>(StringComparer.Ordinal);

        public Lsv2ServerStub()
        {
            _listener = new TcpListener(IPAddress.Loopback, 0);
            _listener.Start();
            Port = ((IPEndPoint)_listener.LocalEndpoint).Port;
            _running = true;
            _acceptThread = new Thread(AcceptLoop) { IsBackground = true, Name = "Lsv2Stub-Accept" };
            _acceptThread.Start();
        }

        public void Dispose()
        {
            _running = false;
            try { _listener.Stop(); } catch { }
        }

        private void AcceptLoop()
        {
            while (_running)
            {
                TcpClient client;
                try { client = _listener.AcceptTcpClient(); }
                catch { break; }
                var t = new Thread(() => HandleClient(client)) { IsBackground = true, Name = "Lsv2Stub-Client" };
                t.Start();
            }
        }

        private void HandleClient(TcpClient client)
        {
            using (client)
            using (var stream = client.GetStream())
            {
                while (true)
                {
                    ushort cmd;
                    byte[] data = ReadFrame(stream, out cmd);
                    if (data == null) break;

                    switch (cmd)
                    {
                        case T_LS_SELECT:
                        case T_LS_LOG_IN:
                            SendFrame(stream, T_OK, new byte[0]);
                            break;

                        case T_LS_LOG_OUT:
                            SendFrame(stream, T_OK, new byte[0]);
                            return;

                        case T_R_FL:
                        {
                            // data = path\0
                            string path = ExtractString(data, 0);
                            string content;
                            if (!Files.TryGetValue(path, out content))
                            {
                                SendFrame(stream, T_SE, new byte[0]);
                                break;
                            }
                            byte[] contentBytes = Encoding.ASCII.GetBytes(content);
                            SendFrame(stream, T_FD, contentBytes);
                            SendFrame(stream, T_OK, new byte[0]);
                            break;
                        }

                        case T_S_FL:
                        {
                            // data = path\0 + content
                            string path = ExtractString(data, 0);
                            int pathLen = Encoding.ASCII.GetByteCount(path) + 1; // +1 for \0
                            int contentLen = data.Length - pathLen;
                            string content = contentLen > 0
                                ? Encoding.ASCII.GetString(data, pathLen, contentLen)
                                : "";
                            Files[path] = content;
                            SendFrame(stream, T_OK, new byte[0]);
                            break;
                        }

                        default:
                            SendFrame(stream, T_SE, new byte[0]);
                            break;
                    }
                }
            }
        }

        private static byte[] ReadFrame(Stream stream, out ushort cmd)
        {
            cmd = 0xFFFF;
            int stx = stream.ReadByte();
            if (stx != 0x02) return null;

            int sizeHi = stream.ReadByte();
            int sizeLo = stream.ReadByte();
            if (sizeHi < 0 || sizeLo < 0) return null;
            int size = (sizeHi << 8) | sizeLo;
            if (size < 2) return null;

            int cmdHi = stream.ReadByte();
            int cmdLo = stream.ReadByte();
            if (cmdHi < 0 || cmdLo < 0) return null;
            cmd = (ushort)((cmdHi << 8) | cmdLo);

            int dataLen = size - 2;
            byte[] data = new byte[dataLen];
            int read = 0;
            while (read < dataLen)
            {
                int n = stream.Read(data, read, dataLen - read);
                if (n == 0) return null;
                read += n;
            }

            // consume CRC
            stream.ReadByte();
            stream.ReadByte();

            return data;
        }

        private static void SendFrame(Stream stream, ushort cmdCode, byte[] data)
        {
            int dataLen = data.Length;
            ushort size = (ushort)(2 + dataLen);

            byte sizeHi = (byte)(size    >> 8);
            byte sizeLo = (byte)(size    & 0xFF);
            byte cmdHi  = (byte)(cmdCode >> 8);
            byte cmdLo  = (byte)(cmdCode & 0xFF);

            byte[] crcInput = new byte[4 + dataLen];
            crcInput[0] = sizeHi; crcInput[1] = sizeLo;
            crcInput[2] = cmdHi;  crcInput[3] = cmdLo;
            Buffer.BlockCopy(data, 0, crcInput, 4, dataLen);
            ushort crc = Crc16(crcInput);

            byte[] frame = new byte[1 + 4 + dataLen + 2];
            int i = 0;
            frame[i++] = 0x02;
            frame[i++] = sizeHi;
            frame[i++] = sizeLo;
            frame[i++] = cmdHi;
            frame[i++] = cmdLo;
            Buffer.BlockCopy(data, 0, frame, i, dataLen);
            i += dataLen;
            frame[i++] = (byte)(crc >> 8);
            frame[i]   = (byte)(crc & 0xFF);

            stream.Write(frame, 0, frame.Length);
            stream.Flush();
        }

        private static string ExtractString(byte[] data, int offset)
        {
            int end = offset;
            while (end < data.Length && data[end] != 0) end++;
            return Encoding.ASCII.GetString(data, offset, end - offset);
        }

        // CRC16 CCITT — same poly/init as Lsv2Client
        private static ushort Crc16(byte[] data)
        {
            ushort crc = 0xFFFF;
            foreach (byte b in data)
            {
                crc ^= (ushort)(b << 8);
                for (int i = 0; i < 8; i++)
                    crc = (crc & 0x8000) != 0
                        ? (ushort)((crc << 1) ^ 0x1021)
                        : (ushort)(crc << 1);
            }
            return crc;
        }
    }
}
