using System;
using System.Text;

namespace io.github.hatayama.uLoopMCP
{
    /// <summary>
    /// Parses Content-Length framed messages for JSON-RPC 2.0 communication.
    /// Handles HTTP-style headers with format: "Content-Length: <n>\r\n\r\n<json_content>"
    /// </summary>
    public class FrameParser
    {
        private const string CONTENT_LENGTH_HEADER = "Content-Length:";
        private const string HEADER_SEPARATOR = "\r\n\r\n";
        private const string LINE_SEPARATOR = "\r\n";
        
        /// <summary>
        /// Attempts to parse a Content-Length framed message from the buffer.
        /// </summary>
        /// <param name="buffer">The buffer containing the data</param>
        /// <param name="length">The length of valid data in the buffer</param>
        /// <param name="contentLength">The parsed content length, or -1 if parsing failed</param>
        /// <param name="headerLength">The total length of the header including separators, or -1 if parsing failed</param>
        /// <returns>True if the header was successfully parsed, false otherwise</returns>
        public bool TryParseFrame(byte[] buffer, int length, out int contentLength, out int headerLength)
        {
            contentLength = -1;
            headerLength = -1;
            
            if (buffer == null || length <= 0)
            {
                return false;
            }
            
            // Find the header separator without converting entire buffer to string
            byte[] separatorBytes = Encoding.UTF8.GetBytes(HEADER_SEPARATOR);
            int separatorIndex = FindByteSequence(buffer, length, separatorBytes);
            if (separatorIndex == -1)
            {
                // Header not complete yet
                return false;
            }
            
            // Extract header section only - convert only the header part to string
            string headerSection = Encoding.UTF8.GetString(buffer, 0, separatorIndex);
            int tempHeaderLength = separatorIndex + separatorBytes.Length;
            
            // Parse Content-Length from header
            bool parseResult = TryParseContentLength(headerSection, out contentLength);
            
            // Only set headerLength if parsing was successful
            if (parseResult)
            {
                headerLength = tempHeaderLength;
                return true;
            }
            else
            {
                // Reset values on failure
                contentLength = -1;
                headerLength = -1;
                return false;
            }
        }
        
        /// <summary>
        /// Checks if a complete frame is available in the buffer.
        /// </summary>
        /// <param name="buffer">The buffer containing the data</param>
        /// <param name="length">The length of valid data in the buffer</param>
        /// <param name="contentLength">The expected content length</param>
        /// <param name="headerLength">The header length including separators</param>
        /// <returns>True if the complete frame is available, false otherwise</returns>
        public bool IsCompleteFrame(byte[] buffer, int length, int contentLength, int headerLength)
        {
            if (buffer == null || length <= 0 || contentLength < 0 || headerLength < 0)
            {
                return false;
            }
            
            int expectedTotalLength = headerLength + contentLength;
            return length >= expectedTotalLength;
        }
        
        /// <summary>
        /// Extracts the JSON content from a complete frame.
        /// </summary>
        /// <param name="buffer">The buffer containing the complete frame</param>
        /// <param name="contentLength">The content length</param>
        /// <param name="headerLength">The header length including separators</param>
        /// <returns>The JSON content as a string, or null if extraction failed</returns>
        public string ExtractJsonContent(byte[] buffer, int contentLength, int headerLength)
        {
            if (buffer == null || contentLength <= 0 || headerLength < 0)
            {
                return null;
            }
            
            // Extract JSON content after the header - let encoding exceptions bubble up
            return Encoding.UTF8.GetString(buffer, headerLength, contentLength);
        }
        
        /// <summary>
        /// Validates that the Content-Length value is within acceptable limits.
        /// </summary>
        /// <param name="contentLength">The content length to validate</param>
        /// <returns>True if the content length is valid, false otherwise</returns>
        public bool IsValidContentLength(int contentLength)
        {
            return contentLength >= 0 && contentLength <= BufferConfig.MAX_MESSAGE_SIZE;
        }
        
        /// <summary>
        /// Parses the Content-Length value from the header section.
        /// </summary>
        /// <param name="headerSection">The header section string</param>
        /// <param name="contentLength">The parsed content length</param>
        /// <returns>True if parsing was successful, false otherwise</returns>
        private bool TryParseContentLength(string headerSection, out int contentLength)
        {
            contentLength = -1;
            
            if (string.IsNullOrEmpty(headerSection))
            {
                return false;
            }
            
            // Split header into lines
            string[] lines = headerSection.Split(new[] { LINE_SEPARATOR }, StringSplitOptions.RemoveEmptyEntries);
            
            foreach (string line in lines)
            {
                string trimmedLine = line.Trim();
                
                // Check if this line contains Content-Length header
                if (trimmedLine.StartsWith(CONTENT_LENGTH_HEADER, StringComparison.OrdinalIgnoreCase))
                {
                    // Extract the value after the colon
                    int colonIndex = trimmedLine.IndexOf(':');
                    if (colonIndex == -1 || colonIndex >= trimmedLine.Length - 1)
                    {
                        continue;
                    }
                    
                    string valueString = trimmedLine.Substring(colonIndex + 1).Trim();
                    
                    // Try to parse the integer value
                    if (int.TryParse(valueString, out int parsedValue))
                    {
                        if (IsValidContentLength(parsedValue))
                        {
                            contentLength = parsedValue;
                            return true;
                        }
                        else
                        {
                            // Return false but don't set headerLength to -1 in calling method
                            contentLength = -1;
                            return false;
                        }
                    }
                    else
                    {
                        // Return false but don't set headerLength to -1 in calling method
                        contentLength = -1;
                        return false;
                    }
                }
            }
            
            return false;
        }
        
        /// <summary>
        /// Finds the first occurrence of a byte sequence in a buffer.
        /// </summary>
        /// <param name="buffer">The buffer to search in</param>
        /// <param name="bufferLength">The length of valid data in the buffer</param>
        /// <param name="sequence">The byte sequence to find</param>
        /// <returns>The index of the first occurrence, or -1 if not found</returns>
        private static int FindByteSequence(byte[] buffer, int bufferLength, byte[] sequence)
        {
            if (buffer == null || sequence == null || sequence.Length == 0 || bufferLength < sequence.Length)
                return -1;

            for (int i = 0; i <= bufferLength - sequence.Length; i++)
            {
                bool found = true;
                for (int j = 0; j < sequence.Length; j++)
                {
                    if (buffer[i + j] != sequence[j])
                    {
                        found = false;
                        break;
                    }
                }
                if (found)
                    return i;
            }
            return -1;
        }
    }
    
    /// <summary>
    /// Configuration constants for buffer management and frame parsing.
    /// </summary>
    public static class BufferConfig
    {
        /// <summary>
        /// Initial buffer size for new connections.
        /// </summary>
        public const int INITIAL_BUFFER_SIZE = 4096;
        
        /// <summary>
        /// Maximum buffer size to prevent memory exhaustion.
        /// </summary>
        public const int MAX_BUFFER_SIZE = 1024 * 1024; // 1MB
        
        /// <summary>
        /// Maximum allowed message size.
        /// </summary>
        public const int MAX_MESSAGE_SIZE = 1024 * 1024; // 1MB
        
        /// <summary>
        /// Buffer growth factor when resizing is needed.
        /// </summary>
        public const int BUFFER_GROWTH_FACTOR = 2;
        
        /// <summary>
        /// Minimum buffer size to maintain.
        /// </summary>
        public const int MIN_BUFFER_SIZE = 1024;
    }
}