using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace TrajectoryPlanningVisualization;

internal sealed class LoopbackHttpRequest
{
    public LoopbackHttpRequest(string method, string path, string token)
    {
        Method = method;
        Path = path;
        Token = token;
    }

    public string Method { get; }
    public string Path { get; }
    public string Token { get; }
}

internal static class LoopbackHttpRequestReader
{
    internal const int MaximumHeaderBytes = 16 * 1024;
    private const int RequestReadTimeoutMilliseconds = 2000;

    public static bool TryRead(Stream stream, out LoopbackHttpRequest request)
    {
        request = null;
        var bytes = new List<byte>();
        bool tooLarge = false;
        int terminatorBytes = 0;
        var stopwatch = Stopwatch.StartNew();
        while (stopwatch.ElapsedMilliseconds < RequestReadTimeoutMilliseconds)
        {
            int value;
            try
            {
                int remainingMilliseconds = Math.Max(1, RequestReadTimeoutMilliseconds - (int)stopwatch.ElapsedMilliseconds);
                stream.ReadTimeout = remainingMilliseconds;
                value = stream.ReadByte();
            }
            catch (IOException)
            {
                return false;
            }

            if (value < 0)
            {
                return false;
            }

            byte next = (byte)value;
            if (!tooLarge)
            {
                bytes.Add(next);
                if (bytes.Count >= MaximumHeaderBytes)
                {
                    tooLarge = true;
                }
            }

            terminatorBytes = next == (terminatorBytes == 0 || terminatorBytes == 2 ? (byte)'\r' : (byte)'\n')
                ? terminatorBytes + 1
                : next == '\r' ? 1 : 0;
            if (terminatorBytes == 4)
            {
                return !tooLarge && TryParse(Encoding.ASCII.GetString(bytes.ToArray()), out request);
            }
        }

        return false;
    }

    private static bool TryParse(string headerText, out LoopbackHttpRequest request)
    {
        request = null;
        string[] lines = headerText.Split(new[] { "\r\n" }, StringSplitOptions.None);
        if (lines.Length < 2 || string.IsNullOrEmpty(lines[0]))
        {
            return false;
        }

        string[] parts = lines[0].Split(' ');
        if (parts.Length != 3 || parts[0].Length == 0 || parts[1].Length == 0 || parts[2] != "HTTP/1.1")
        {
            return false;
        }

        string target = parts[1];
        if (!target.StartsWith("/", StringComparison.Ordinal) || target.IndexOf('#') >= 0)
        {
            return false;
        }

        int queryStart = target.IndexOf('?');
        string path = queryStart < 0 ? target : target.Substring(0, queryStart);
        string token = null;
        if (queryStart >= 0)
        {
            string query = target.Substring(queryStart + 1);
            string[] pairs = query.Split('&');
            foreach (string pair in pairs)
            {
                int equals = pair.IndexOf('=');
                string name = equals < 0 ? pair : pair.Substring(0, equals);
                if (name == "token")
                {
                    try
                    {
                        token = Uri.UnescapeDataString(equals < 0 ? string.Empty : pair.Substring(equals + 1));
                    }
                    catch (UriFormatException)
                    {
                        return false;
                    }
                }
            }
        }

        request = new LoopbackHttpRequest(parts[0], path, token);
        return true;
    }
}
