using System.ComponentModel;
using System.Reflection;
using IWS5.Server.Enums;
using IWS5.Server.Models;
using IWS5.Server.Models.DocumentConversion;
using Syncfusion.DocIO;
using Syncfusion.DocIO.DLS;

namespace IWS5.Server.Utils;

internal static class CoverSheetProcessor
{
    /// <summary>
    /// Processes a Word document template from file path and returns byte array
    /// Template file remains unchanged
    /// </summary>
    /// <param name="templateFilePath">Path to the template file</param>
    /// <param name="templateData">Data to populate the template</param>
    /// <returns>Processed document as byte array</returns>
    internal static async Task<byte[]> ProcessTemplateAsync(string templateFilePath, CoverSheetTemplateData templateData)
    {
        if (string.IsNullOrWhiteSpace(templateFilePath))
            throw new ArgumentException("Template file path cannot be null or empty.", nameof(templateFilePath));
        
        if (!File.Exists(templateFilePath))
            throw new FileNotFoundException($"Template file not found: {templateFilePath}");

        var templateBytes = File.ReadAllBytes(templateFilePath);
        return await ProcessTemplateAsync(templateBytes, templateData);
    }

    /// <summary>
    /// Processes a Word document template from byte array
    /// </summary>
    /// <param name="templateBytes">The template document as byte array</param>
    /// <param name="templateData">Data to populate the template</param>
    /// <returns>Processed document as byte array</returns>
    private static async Task<byte[]> ProcessTemplateAsync(byte[] templateBytes, CoverSheetTemplateData templateData)
    {
        if (templateBytes == null || templateBytes.Length == 0)
            throw new ArgumentException("Template bytes cannot be null or empty.", nameof(templateBytes));
        
        if (templateData == null)
            throw new ArgumentNullException(nameof(templateData));

        try
        {
            using var templateStream = new MemoryStream(templateBytes);
            using var document = new WordDocument(templateStream, FormatType.Automatic);
            
            ReplaceTextPlaceholders(document, templateData);
            
            if (templateData.FolderFields?.Count > 0)
            {
                InsertFolderFieldRows(document, templateData.FolderFields, templateData.BarcodeFont);
            }
            
            // Save processed document to memory
            using var outputStream = new MemoryStream();
            document.Save(outputStream, FormatType.Docx);
            
            return await Task.FromResult(outputStream.ToArray());
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException($"Error processing document template: {ex.Message}", ex);
        }
    }

    private static void ReplaceTextPlaceholders(WordDocument document, CoverSheetTemplateData templateData)
    {
        ReplaceTextWithOptionalBarcode(document, "{WorkItemId}", $"{templateData.WorkItemId}", templateData.BarcodeFont);
        
        ReplaceTextWithOptionalBarcode(document, "{WorkItemName}", templateData.WorkItemName ?? string.Empty, templateData.BarcodeFont);
    }

    private static void ReplaceTextWithOptionalBarcode(WordDocument document, string placeholder, string replacementText, ATBarcodeFont? barcodeFont)
    {
        TextSelection textSelection = document.Find(placeholder, true, true);
    
        while (textSelection != null)
        {
            WParagraph paragraph = textSelection.GetAsOneRange().OwnerParagraph;
            WSection section = paragraph.OwnerTextBody.Owner as WSection;
            int insertIndex = section.Paragraphs.IndexOf(paragraph);
        
            // Replace the original placeholder
            textSelection.GetAsOneRange().Text = replacementText;
        
            // Add barcode duplicate if barcode font is specified
            if (barcodeFont.HasValue)
            {
                var barcodeParagraph = CreateBarcodeParagraph(document, paragraph, replacementText, barcodeFont.Value);
                section.Paragraphs.Insert(insertIndex + 1, barcodeParagraph);
            }
        
            // Find next occurrence starting from the current paragraph
            textSelection = document.FindNext(paragraph, placeholder, true, true);
        }
    }

    private static void InsertFolderFieldRows(WordDocument document, List<FolderField> folderFields, ATBarcodeFont? barcodeFont)
    {
        var placeholderText = "{FolderFieldName}: {FolderFieldValue}";
        TextSelection textSelection = document.Find(placeholderText, true, true);
        
        if (textSelection != null)
        {
            WParagraph paragraph = textSelection.GetAsOneRange().OwnerParagraph;
            WSection section = paragraph.OwnerTextBody.Owner as WSection;
            int insertIndex = section.Paragraphs.IndexOf(paragraph);
            
            string originalStyleName = paragraph.StyleName;
            WCharacterFormat originalCharFormat = null;
            if (paragraph.ChildEntities.Count > 0 && paragraph.ChildEntities[0] is WTextRange originalRange)
            {
                originalCharFormat = originalRange.CharacterFormat;
            }
            
            int currentInsertIndex = insertIndex;
            
            for (int i = 0; i < folderFields.Count; i++)
            {
                var folderField = folderFields[i];
                var fieldText = $"{folderField.Name}: {folderField.Value}";
                
                // Create regular paragraph
                var newParagraph = CreateParagraph(document, fieldText, originalStyleName, paragraph.ParagraphFormat, originalCharFormat);
                section.Paragraphs.Insert(currentInsertIndex, newParagraph);
                currentInsertIndex++;
                
                // Create barcode paragraph if barcode font is specified
                if (barcodeFont.HasValue)
                {
                    var barcodeParagraph = CreateBarcodeParagraph(document, paragraph, fieldText, barcodeFont.Value);
                    section.Paragraphs.Insert(currentInsertIndex, barcodeParagraph);
                    currentInsertIndex++;
                }
            }
            
            // Remove the original placeholder paragraph
            int paragraphIndex = section.Paragraphs.IndexOf(paragraph);
            if (paragraphIndex >= 0)
            {
                section.Paragraphs.RemoveAt(paragraphIndex);
            }
        }
        else
        {
            AppendFolderFieldsToDocument(document, folderFields, barcodeFont);
        }
    }

    private static WParagraph CreateParagraph(WordDocument document, string text, string styleName, WParagraphFormat paragraphFormat, WCharacterFormat characterFormat)
    {
        var newParagraph = new WParagraph(document);
        
        if (!string.IsNullOrEmpty(styleName))
        {
            newParagraph.ApplyStyle(styleName);
        }
        
        CopyParagraphFormat(paragraphFormat, newParagraph.ParagraphFormat);
        
        var textRange = new WTextRange(document);
        textRange.Text = text;
        
        if (characterFormat != null)
        {
            CopyCharacterFormat(characterFormat, textRange.CharacterFormat);
        }
        
        newParagraph.ChildEntities.Add(textRange);
        return newParagraph;
    }

    private static WParagraph CreateBarcodeParagraph(WordDocument document, WParagraph originalParagraph, string text, ATBarcodeFont barcodeFont)
    {
        var barcodeParagraph = new WParagraph(document);
        
        // Copy original paragraph formatting
        if (!string.IsNullOrEmpty(originalParagraph.StyleName))
        {
            barcodeParagraph.ApplyStyle(originalParagraph.StyleName);
        }
        
        CopyParagraphFormat(originalParagraph.ParagraphFormat, barcodeParagraph.ParagraphFormat);
        
        // Create text range with barcode font
        var textRange = new WTextRange(document);
        textRange.Text = text;
        
        // Copy original character formatting
        if (originalParagraph.ChildEntities.Count > 0 && originalParagraph.ChildEntities[0] is WTextRange originalRange)
        {
            CopyCharacterFormat(originalRange.CharacterFormat, textRange.CharacterFormat);
        }
        
        // Set barcode font
        textRange.CharacterFormat.FontName = GetBarcodeDescription(barcodeFont);
        
        barcodeParagraph.ChildEntities.Add(textRange);
        return barcodeParagraph;
    }

    private static string GetBarcodeDescription(ATBarcodeFont barcodeFont)
    {
        var field = barcodeFont.GetType().GetField(barcodeFont.ToString());
        var attribute = field?.GetCustomAttribute<DescriptionAttribute>();
        return attribute?.Description ?? barcodeFont.ToString();
    }

    private static void CopyParagraphFormat(WParagraphFormat source, WParagraphFormat target)
    {
        target.AfterSpacing = source.AfterSpacing;
        target.BeforeSpacing = source.BeforeSpacing;
        target.FirstLineIndent = source.FirstLineIndent;
        target.LeftIndent = source.LeftIndent;
        target.LineSpacing = source.LineSpacing;
        target.LineSpacingRule = source.LineSpacingRule;
        target.RightIndent = source.RightIndent;
        target.HorizontalAlignment = source.HorizontalAlignment;
    }

    private static void CopyCharacterFormat(WCharacterFormat source, WCharacterFormat target)
    {
        target.Bold = source.Bold;
        target.Italic = source.Italic;
        target.FontName = source.FontName;
        target.FontSize = source.FontSize;
        target.TextColor = source.TextColor;
    }

    private static void AppendFolderFieldsToDocument(WordDocument document, List<FolderField> folderFields, ATBarcodeFont? barcodeFont)
    {
        var section = document.Sections[0];
        
        foreach (var folderField in folderFields)
        {
            var fieldText = $"{folderField.Name}: {folderField.Value}";
            
            // Add regular paragraph
            var paragraph = new WParagraph(document);
            var textRange = new WTextRange(document)
            {
                Text = fieldText
            };
            
            paragraph.ChildEntities.Add(textRange);
            section.Paragraphs.Add(paragraph);
            
            // Add barcode paragraph if barcode font is specified
            if (barcodeFont.HasValue)
            {
                var barcodeParagraph = new WParagraph(document);
                var barcodeTextRange = new WTextRange(document)
                {
                    Text = fieldText
                };
                barcodeTextRange.CharacterFormat.FontName = GetBarcodeDescription(barcodeFont.Value);
                
                barcodeParagraph.ChildEntities.Add(barcodeTextRange);
                section.Paragraphs.Add(barcodeParagraph);
            }
        }
    }
}