using System.Collections.Generic;
using System.IO;
using System.Xml.Schema;
using System.Xml;
using System;
using Microsoft.Extensions.Configuration;
using Npgsql;

namespace LAPS_XMLQC_Service.Services
{
    public class XmlValidatorService
    {
        private readonly string _connectionString;

        public XmlValidatorService(IConfiguration configuration)
        {
            _connectionString = configuration.GetConnectionString("DbConnection");
        }

        public string GetDocumentPath(string documentType, int projectDefinitionId)
        {
            string filePath = string.Empty;
            using (var connection = new NpgsqlConnection(_connectionString))
            {
                connection.Open();
                using (var command = new NpgsqlCommand("SELECT documentpath FROM tblprojectdocuments WHERE documenttype = @documentType AND projectdefinitionid = @projectDefinitionId", connection))
                {                   
                    command.Parameters.AddWithValue("@documentType", documentType);
                    command.Parameters.AddWithValue("@projectDefinitionId", projectDefinitionId);

                    filePath = Convert.ToString(command.ExecuteScalar());
                }              
            }  
            return filePath; 
        }
        /// <summary>
        /// Validates XML against a DTD file provided via file path.
        /// </summary>
        /// <param name="xmlContent">The XML content as a string.</param>
        /// <param name="dtdFilePath">The file path to the DTD.</param>
        /// <returns>A list of validation errors (empty if valid).</returns>

        /// <summary>
        /// Validates XML content against a specified DTD file.
        /// </summary>
        /// <param name="xmlContent">The XML content as a string.</param>
        /// <param name="dtdFilePath">The path to the DTD file.</param>
        /// <returns>A list of validation errors or a success message if valid.</returns>
        public List<string> ValidateXmlAgainstDtd(string xmlContent, string dtdFilePath)
        {
            var validationErrors = new List<string>();

            try
            {
                // Step 1: Check if the XML is well-formed
                //validationErrors = ValidateXmlAndDetectUnclosedTags(xmlContent);
                //if (validationErrors.Count > 0)
                //{
                //    return validationErrors; // Stop further processing if not well-formed
                //}

                // Step 2: Inject the DTD file path if not already present
                string xmlWithDtd = InjectDtdPath(xmlContent, dtdFilePath);

                // Step 3: Set up DTD validation settings
                var settings = new XmlReaderSettings
                {
                    DtdProcessing = DtdProcessing.Parse,
                    ValidationType = ValidationType.DTD,
                    XmlResolver = new XmlUrlResolver()
                };

                // Attach a validation event handler
                settings.ValidationEventHandler += (sender, e) =>
                {
                    validationErrors.Add($"DTD validation error: {e.Message}");
                };

                // Step 4: Validate the XML against the DTD
                using (var stringReader = new StringReader(xmlWithDtd))
                using (var xmlReader = XmlReader.Create(stringReader, settings))
                {
                    while (xmlReader.Read()) { } // Triggers DTD validation
                }

                // If no errors, indicate successful validation
                if (validationErrors.Count == 0)
                {
                    validationErrors.Add("XML is valid against the provided DTD.");
                }
            }
            catch (XmlException ex)
            {
                validationErrors.Add($"XML parsing error: {ex.Message}");
            }
            catch (Exception ex)
            {
                validationErrors.Add($"Unexpected error: {ex.Message}");
            }

            return validationErrors;

        }

        //public List<string> FindUnclosedTags(string xmlString)
        //{
        //    var unclosedTags = new List<string>();
        //    var stack = new Stack<string>();

        //    // Load the XML document
        //    XmlDocument xmlDoc = new XmlDocument();
        //    xmlDoc.LoadXml(xmlString);

        //    // Traverse all elements in the XML document
        //    XmlNodeList elements = xmlDoc.GetElementsByTagName("*");

        //    foreach (XmlNode element in elements)
        //    {
        //        // Check for opening tags (elements with children)
        //        if (element.NodeType == XmlNodeType.Element)
        //        {
        //            // If the element is not self-closing, push it onto the stack
        //            if (element.HasChildNodes || !IsSelfClosingTag(element))
        //            {
        //                stack.Push(element.Name);
        //            }
        //            else
        //            {
        //                // If it's a self-closing tag, it doesn't need to be tracked
        //                // But you could add logic to handle these if needed
        //            }
        //        }
        //        else if (element.NodeType == XmlNodeType.EndElement)
        //        {
        //            // If it's a closing tag, check if it matches the last element in the stack
        //            if (stack.Count > 0 && stack.Peek() == element.Name)
        //            {
        //                stack.Pop(); // Remove matched opening tag
        //            }
        //            else
        //            {
        //                // If there's no match, add the unclosed tag to the list
        //                unclosedTags.Add(element.Name);
        //            }
        //        }
        //    }

        //    // If any opening tags are left in the stack, they are unclosed
        //    while (stack.Count > 0)
        //    {
        //        unclosedTags.Add(stack.Pop());
        //    }

        //    return unclosedTags;
        //}

        //// Helper function to determine if an element is self-closing
        //private bool IsSelfClosingTag(XmlNode element)
        //{
        //    // Example of simple self-closing tag detection, you may need to refine based on your XML format
        //    return element.OuterXml.EndsWith("/>");
        //}

        //public static List<string> ParseXmlWithUnclosedTagDetection(string xmlContent)
        //{
        //    var validationErrors = new List<string>();
        //    var openElements = new Stack<string>(); // Stack to track open elements
        //    var lineNumber = 0;
        //    var linePosition = 0;

        //    try
        //    {
        //        using (var reader = new XmlTextReader(xmlContent))
        //        {
        //            while (reader.Read())
        //            {
        //                lineNumber = reader.LineNumber;
        //                linePosition = reader.LinePosition;

        //                // Handle element start
        //                if (reader.NodeType == XmlNodeType.Element)
        //                {
        //                    openElements.Push(reader.Name);
        //                }
        //                // Handle element end
        //                else if (reader.NodeType == XmlNodeType.EndElement)
        //                {
        //                    // Check if the closing tag matches the most recent opening tag
        //                    if (openElements.Count == 0 || openElements.Peek() != reader.Name)
        //                    {
        //                        validationErrors.Add($"Unmatched closing tag </{reader.Name}> at line {lineNumber}, position {linePosition}.");
        //                    }
        //                    else
        //                    {
        //                        openElements.Pop(); // Matched closing tag, pop from stack
        //                    }
        //                }
        //            }
        //        }

        //        // After reading, check for any unclosed tags
        //        while (openElements.Count > 0)
        //        {
        //            var unclosedElement = openElements.Pop();
        //            validationErrors.Add($"Unclosed tag <{unclosedElement}> at line {lineNumber}, position {linePosition}.");
        //        }

        //        if (validationErrors.Count == 0)
        //        {
        //            validationErrors.Add("XML is well-formed.");
        //        }
        //    }
        //    catch (XmlException ex)
        //    {
        //        validationErrors.Add($"XML parsing error: {ex.Message} at line {lineNumber}, position {linePosition}.");
        //    }
        //    catch (Exception ex)
        //    {
        //        validationErrors.Add($"Unexpected error: {ex.Message}");
        //    }

        //    return validationErrors;
        //}

        //public static List<string> ValidateXmlAndDetectUnclosedTags(string xmlContent)
        //{
        //    var validationErrors = new List<string>();
        //    var openElements = new Stack<(string ElementName, int LineNumber, int LinePosition)>(); // Stack to track open elements with line info

        //    try
        //    {
        //        var settings = new XmlReaderSettings
        //        {
        //            DtdProcessing = DtdProcessing.Ignore, // Disable DTD processing
        //            IgnoreComments = true, // Ignore comments
        //            IgnoreWhitespace = true // Ignore insignificant whitespace
        //        };

        //        using (var stringReader = new StringReader(xmlContent))
        //        using (var xmlReader = XmlReader.Create(stringReader, settings))
        //        {
        //            IXmlLineInfo lineInfo = xmlReader as IXmlLineInfo;

        //            while (xmlReader.Read())
        //            {
        //                // Track opening tags and check for closing tags
        //                if (xmlReader.NodeType == XmlNodeType.Element)
        //                {
        //                    // Push start element onto the stack
        //                    openElements.Push((xmlReader.Name, lineInfo?.LineNumber ?? -1, lineInfo?.LinePosition ?? -1));
        //                }
        //                else if (xmlReader.NodeType == XmlNodeType.EndElement)
        //                {
        //                    if (openElements.Count == 0 || openElements.Peek().ElementName != xmlReader.Name)
        //                    {
        //                        // Report unmatched closing tag
        //                        validationErrors.Add($"Unmatched closing tag </{xmlReader.Name}> at line {lineInfo?.LineNumber}, position {lineInfo?.LinePosition}.");
        //                    }
        //                    else
        //                    {
        //                        // Matched closing tag, pop the stack
        //                        openElements.Pop();
        //                    }
        //                }
        //            }
        //        }

        //        // Report unclosed tags remaining in the stack
        //        while (openElements.Count > 0)
        //        {
        //            var (ElementName, LineNumber, LinePosition) = openElements.Pop();
        //            validationErrors.Add($"Unclosed tag <{ElementName}> at line {LineNumber}, position {LinePosition}.");
        //        }
        //    }
        //    catch (XmlException ex)
        //    {
        //        validationErrors.Add($"XML parsing error: {ex.Message} at line {ex.LineNumber}, position {ex.LinePosition}.");
        //    }
        //    catch (Exception ex)
        //    {
        //        validationErrors.Add($"Unexpected error: {ex.Message}");
        //    }

        //    if (validationErrors.Count == 0)
        //    {
        //        validationErrors.Add("XML is well-formed.");
        //    }

        //    return validationErrors;
        //}


        //public List<string> ValidateXmlAgainstDtd(string xmlContent, string dtdFilePath)
        //{
        //    var validationErrors = new List<string>();

        //    try
        //    {
        //        // Inject the DTD file path into the XML content
        //        string xmlWithDtd = string.Empty;

        //        if (!xmlContent.Contains("<!DOCTYPE"))
        //        {
        //            xmlWithDtd = InjectDtdPath(xmlContent, dtdFilePath);
        //        }
        //        else
        //        {
        //            xmlWithDtd = xmlContent;
        //        }               

        //        var settings = new XmlReaderSettings
        //        {
        //            DtdProcessing = DtdProcessing.Parse, // Enable DTD parsing
        //            ValidationType = ValidationType.DTD, // Set validation type to DTD
        //            XmlResolver = new XmlUrlResolver() // Enable resolution of external DTD files
        //        };

        //        // Attach a validation event handler to capture errors
        //        settings.ValidationEventHandler += (sender, e) =>
        //        {
        //            validationErrors.Add($"DTD validation error: {e.Message}");
        //        };

        //        // Create an XmlReader for the XML content with the DTD validation settings
        //        using (var stringReader = new StringReader(xmlWithDtd))
        //        using (var xmlReader = XmlReader.Create(stringReader, settings))
        //        {
        //            // Read the XML content (this triggers DTD validation)
        //            while (xmlReader.Read()) { }
        //        }

        //        // If there are no errors, validation passed
        //        if (validationErrors.Count == 0)
        //        {
        //            validationErrors.Add("XML is valid against the provided DTD.");
        //        }
        //    }
        //    catch (XmlException ex)
        //    {
        //        validationErrors.Add($"XML is not well-formed: {ex.Message}");
        //    }
        //    catch (XmlSchemaValidationException ex)
        //    {
        //        validationErrors.Add($"DTD validation error: {ex.Message}");
        //    }

        //    return validationErrors;
        //}



        /// <summary>
        /// Validates XML against an XSD file provided via file path.
        /// </summary>
        /// <param name="xmlContent">The XML content as a string.</param>
        /// <param name="xsdFilePath">The file path to the XSD.</param>
        /// <returns>A list of validation errors (empty if valid).</returns>
        public List<string> ValidateXmlAgainstXsd(string xmlContent, string xsdFilePath)
        {
            var validationErrors = new List<string>();

            try
            {
                // Load the XSD into an XmlSchemaSet from the file path
                var schemas = new XmlSchemaSet();
                schemas.Add(null, xsdFilePath);

                // Create XmlReaderSettings and attach the validation event handler
                var settings = new XmlReaderSettings
                {
                    ValidationType = ValidationType.Schema,
                    Schemas = schemas
                };

                // ValidationEventHandler to capture validation errors
                settings.ValidationEventHandler += (sender, e) =>
                {
                    validationErrors.Add($"XSD validation error: {e.Message}");
                };

                // Create XmlReader for XML validation
                using (var stringReader = new StringReader(xmlContent))
                using (var xmlReader = XmlReader.Create(stringReader, settings))
                {
                    // Read the XML (this will trigger validation)
                    while (xmlReader.Read()) { }
                }

                // If there are no errors, validation passed
                if (validationErrors.Count == 0)
                {                
                    validationErrors.Add("XML is valid against the provided XSD.");
                }
            }
            catch (XmlException ex)
            {
                validationErrors.Add($"XML is not well-formed: {ex.Message}");
            }

            return validationErrors;
        }

        //public List<string> ValidateXmlStructure(string xmlContent)
        //{
        //    var errors = new List<string>();

        //    try
        //    {
        //        var settings = new XmlReaderSettings
        //        {
        //            ConformanceLevel = ConformanceLevel.Document, // Ensures XML is well-formed
        //            DtdProcessing = DtdProcessing.Prohibit // Disables DTD processing for security
        //        };

        //        // Adding a handler for validation events
        //        settings.ValidationEventHandler += (sender, e) =>
        //        {
        //            errors.Add($"Validation Error: {e.Message} at Line {e.Exception.LineNumber}, Position {e.Exception.LinePosition}");
        //        };

        //        // Using XmlReader to read the XML content
        //        using var reader = XmlReader.Create(new StringReader(xmlContent), settings);
        //        while (reader.Read()) { } // Reads through the XML document
        //    }
        //    catch (XmlException ex)
        //    {
        //        // Captures XML parsing errors like unclosed tags
        //        errors.Add($"XML Parsing Error: {ex.Message} at Line {ex.LineNumber}, Position {ex.LinePosition}");
        //    }
        //    catch (Exception ex)
        //    {
        //        // Captures other errors
        //        errors.Add($"Unexpected Error: {ex.Message}");
        //    }

        //    return errors; // Return all collected validation errors
        //}


        /// <summary>
        /// Injects a DOCTYPE declaration with a SYSTEM DTD reference into the XML content.
        /// </summary>
        /// <param name="xmlContent">The XML content as a string.</param>
        /// <param name="dtdFilePath">The path to the DTD file.</param>
        /// <returns>The modified XML content with the DOCTYPE declaration.</returns>
        private string InjectDtdPath(string xmlContent, string dtdFilePath, string publicId = null)
        {
            // Check if the DOCTYPE declaration is already present
            if (!xmlContent.Contains("<!DOCTYPE"))
            {
                // Skip over the XML declaration if it exists
                int xmlDeclarationEnd = xmlContent.IndexOf("?>");
                int searchStartIndex = (xmlDeclarationEnd > -1) ? xmlDeclarationEnd + 2 : 0;

                // Locate the root element
                int rootStartIndex = xmlContent.IndexOf('<', searchStartIndex);
                if (rootStartIndex == -1)
                {
                    throw new InvalidOperationException("Invalid XML content: No opening tag found.");
                }

                int rootEndIndex = xmlContent.IndexOf('>', rootStartIndex);
                if (rootEndIndex == -1)
                {
                    throw new InvalidOperationException("Invalid XML content: No closing tag for the root element.");
                }

                // Extract the root element name (e.g., "catalog")
                string rootElement = xmlContent.Substring(rootStartIndex + 1, rootEndIndex - rootStartIndex - 1).Split(' ')[0];

                // Construct the DOCTYPE declaration
                string doctypeDeclaration = string.IsNullOrEmpty(publicId)
                    ? $"<!DOCTYPE {rootElement} SYSTEM \"{dtdFilePath}\">"
                    : $"<!DOCTYPE {rootElement} PUBLIC \"{publicId}\" \"{dtdFilePath}\">";

                // Insert the DOCTYPE declaration after the XML declaration (if present)
                if (xmlDeclarationEnd > -1) // XML declaration exists
                {
                    return xmlContent.Insert(xmlDeclarationEnd + 2, "\n" + doctypeDeclaration);
                }

                // Otherwise, prepend the DOCTYPE declaration to the XML content
                return doctypeDeclaration + "\n" + xmlContent;
            }

            // If DOCTYPE is already present, return the content unchanged
            return xmlContent;
        }




        /// <summary>
        /// Helper method to inject the DTD file path into the XML's DOCTYPE declaration.
        /// </summary>
        /// <param name="xmlContent">The XML content as a string.</param>
        /// <param name="dtdFilePath">The file path to the DTD.</param>
        /// <returns>The XML content with the DOCTYPE declaration added.</returns>
        //private string InjectDtdPath1(string xmlContent, string dtdFilePath)
        //{
        //    const string doctypeDeclaration = "<!DOCTYPE catalog SYSTEM \"{0}\">";

        //    if (xmlContent.Contains("<?xml"))
        //    {
        //        string formattedDoctype = string.Format(doctypeDeclaration, dtdFilePath);

        //        // Find the position of the first newline character after the XML declaration
        //        int firstNewlineIndex = xmlContent.IndexOf('\n');

        //        if (firstNewlineIndex != -1)
        //        {
        //            // Insert the DOCTYPE declaration after the XML declaration
        //            return xmlContent.Insert(firstNewlineIndex + 1, formattedDoctype + "\n");
        //        }
        //        else
        //        {
        //            // If no newline is found, assume it's a single line XML and append after the XML declaration
        //            return xmlContent + "\n" + formattedDoctype;
        //        }
        //    }
        //    else
        //    {
                
        //        return $"{string.Format(doctypeDeclaration, dtdFilePath)}\n{xmlContent}";
        //    }              
        //}

        //public List<string> ValidateXml(string xmlContent, string schemaContent, string schemaType)
        //{
        //    var errors = new List<string>();

        //    try
        //    {
                

        //        string xmlWithDtd = $"<!DOCTYPE bookstore SYSTEM \"bookstore.dtd\">\n{xmlContent}";
        //        XmlParserContext context = new XmlParserContext(null, null, null, XmlSpace.None);
        //        XmlReaderSettings settings = new XmlReaderSettings
        //        {
        //           // ProhibitDtd = false,
        //            DtdProcessing = DtdProcessing.Prohibit,
        //            ValidationType = ValidationType.DTD
        //        };
        //        settings.ValidationEventHandler += (sender, args) =>
        //        {
        //            errors.Add(args.Message);
        //        };

        //        using (var reader = XmlReader.Create(new StringReader(xmlWithDtd), settings, context))
        //        {
        //            while (reader.Read()) { }
        //        }


        //    }
        //    catch (XmlException ex)
        //    {
        //        errors.Add($"XML Exception: {ex.Message}");
        //    }
        //    catch (Exception ex)
        //    {
        //        errors.Add($"General Error: {ex.Message}");
        //    }

        //    return errors;
        //}
    }
}
