using System.Reflection;
using System.Text;
using ApplicationLayer.Services.VisaApplications.NeededServices;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
namespace Infrastructure.EntityToExcelTemplateWriter
{
    /// 
    /// Writes object to excel using template.xlsx file and reflections
    /// 
    public class ExcelWriter : IEntityWriter
    {
        private const char InsertionSymbol = '$';
        private readonly char[] endChars = [',', ';'];
        /// 
        /// Write object to stream in Excel table format
        /// 
        ///  object to write 
        ///  cancellation token 
        ///  Stream with template.xlsx file with replaced entries like '$EntityPropName.AnotherProp' 
        ///  thrown when template file is incorrect 
        ///  thrown if any property path in template is incorrect
        public async Task WriteEntityToStream(object entity, CancellationToken cancellationToken)
        {
            var outStream = new MemoryStream();
            await using (var stream = File.Open("template.xlsx", FileMode.Open, FileAccess.Read))
            {
                await stream.CopyToAsync(outStream, cancellationToken);
            }
            using var spreadsheetDocument = SpreadsheetDocument.Open(outStream, true);
            var workbookPart = spreadsheetDocument.WorkbookPart
                               ?? throw new NullReferenceException("There is no workbook part in document");
            var shareStringTable = workbookPart.SharedStringTablePart?.SharedStringTable ??
                                   throw new NullReferenceException("There is no data in document");
            var shareStringTableItems = shareStringTable.Elements().ToArray();
            foreach (var item in shareStringTableItems)
            {
                if (string.IsNullOrEmpty(item.InnerText))
                {
                    continue;
                }
                var entries = item.InnerText.Split();
                for (var i = 0; i < entries.Length; i++)
                {
                    var entry = entries[i];
                    if (entry.FirstOrDefault() is not InsertionSymbol || entry.Length <= 1)
                    {
                        continue;
                    }
                    entry = entry[1..];
                    var trimmedCount = entry.Length - entry.TrimEnd(endChars).Length;
                    var trimmed = entry[^trimmedCount..];
                    entry = entry.TrimEnd(endChars);
                    var memberPath = entry.Split('.');
                    var value = GetValueFor(entity, memberPath.First());
                    var stringToInsert = "None";
                    foreach (var memberName in memberPath.Skip(1))
                    {
                        if (value is null)
                        {
                            break;
                        }
                        value = GetValueFor(value, memberName);
                    }
                    if (value is not null)
                    {
                        switch (value)
                        {
                            case DateTime date:
                                stringToInsert = date.ToShortDateString();
                                break;
                            case Enum val:
                                var enumString = val.ToString();
                                var stringBuilder = new StringBuilder();
                                for (var charIndex = 0; charIndex < enumString.Length - 1; charIndex++)
                                {
                                    stringBuilder.Append(enumString[charIndex]);
                                    if (char.IsUpper(enumString[charIndex + 1]))
                                    {
                                        stringBuilder.Append(' ');
                                    }
                                }
                                stringBuilder.Append(enumString.Last());
                                stringToInsert = stringBuilder.ToString();
                                break;
                            default:
                                stringToInsert = value.ToString();
                                break;
                        }
                    }
                    entries[i] = stringToInsert! + trimmed;
                }
                item.Text!.Text = string.Join(' ', entries);
            }
            spreadsheetDocument.Save();
            return outStream;
        }
        private static object? GetValueFor(object entity, string member)
        {
            var memberInfo = entity.GetType()
                                 .GetMembers()
                                 .FirstOrDefault(p => p.Name == member)
                             ?? throw new InvalidOperationException(
                                 $"Invalid member path in document. Not found: {member}");
            return memberInfo switch
            {
                PropertyInfo propertyInfo => propertyInfo.GetValue(entity),
                MethodInfo methodInfo => methodInfo.Invoke(entity, []),
                _ => throw new InvalidOperationException("Only properties and methods allowed.")
            };
        }
    }
}