using System.Text;
using System.Xml;
using Books.Api.Saft.Models;

namespace Books.Api.Saft.Services;

/// <summary>
/// Builds SAF-T DK compliant XML documents.
/// Based on OECD SAF-T 2.0 with Danish customizations.
/// </summary>
public class SaftXmlBuilder
{
    private const string SaftNamespace = "urn:StandardAuditFile-Taxation-Financial:DK";

    /// <summary>
    /// Builds a SAF-T XML document from the provided data.
    /// </summary>
    public string Build(SaftDocument document)
    {
        var settings = new XmlWriterSettings
        {
            Indent = true,
            IndentChars = "  ",
            Encoding = new UTF8Encoding(false), // UTF-8 without BOM
            OmitXmlDeclaration = false
        };

        using var stream = new MemoryStream();
        using (var writer = XmlWriter.Create(stream, settings))
        {
            writer.WriteStartDocument();
            writer.WriteStartElement("AuditFile", SaftNamespace);

            WriteHeader(writer, document.Header);
            WriteMasterFiles(writer, document.MasterFiles);
            WriteGeneralLedgerEntries(writer, document.Entries);

            writer.WriteEndElement(); // AuditFile
            writer.WriteEndDocument();
        }

        return Encoding.UTF8.GetString(stream.ToArray());
    }

    private static void WriteHeader(XmlWriter writer, SaftHeader header)
    {
        writer.WriteStartElement("Header");

        writer.WriteElementString("AuditFileVersion", header.AuditFileVersion);
        writer.WriteElementString("AuditFileDateCreated", header.AuditFileDateCreated);
        writer.WriteElementString("SoftwareCompanyName", header.SoftwareCompanyName);
        writer.WriteElementString("SoftwareID", header.SoftwareID);
        writer.WriteElementString("SoftwareVersion", header.SoftwareVersion);

        WriteCompany(writer, header.Company);
        WriteSelectionCriteria(writer, header.SelectionCriteria);

        writer.WriteEndElement(); // Header
    }

    private static void WriteCompany(XmlWriter writer, SaftCompany company)
    {
        writer.WriteStartElement("Company");

        writer.WriteElementString("RegistrationNumber", company.RegistrationNumber);
        writer.WriteElementString("Name", company.Name);

        WriteAddress(writer, company.Address);

        if (company.Contact != null)
        {
            WriteContact(writer, company.Contact);
        }

        writer.WriteEndElement(); // Company
    }

    private static void WriteAddress(XmlWriter writer, SaftAddress address)
    {
        writer.WriteStartElement("Address");

        if (!string.IsNullOrEmpty(address.StreetName))
            writer.WriteElementString("StreetName", address.StreetName);

        if (!string.IsNullOrEmpty(address.City))
            writer.WriteElementString("City", address.City);

        if (!string.IsNullOrEmpty(address.PostalCode))
            writer.WriteElementString("PostalCode", address.PostalCode);

        writer.WriteElementString("Country", address.Country);

        writer.WriteEndElement(); // Address
    }

    private static void WriteContact(XmlWriter writer, SaftContact contact)
    {
        writer.WriteStartElement("Contact");

        if (!string.IsNullOrEmpty(contact.Telephone))
            writer.WriteElementString("Telephone", contact.Telephone);

        if (!string.IsNullOrEmpty(contact.Email))
            writer.WriteElementString("Email", contact.Email);

        if (!string.IsNullOrEmpty(contact.Website))
            writer.WriteElementString("Website", contact.Website);

        writer.WriteEndElement(); // Contact
    }

    private static void WriteSelectionCriteria(XmlWriter writer, SaftSelectionCriteria criteria)
    {
        writer.WriteStartElement("SelectionCriteria");

        writer.WriteElementString("PeriodStart", criteria.PeriodStart);
        writer.WriteElementString("PeriodEnd", criteria.PeriodEnd);

        writer.WriteEndElement(); // SelectionCriteria
    }

    private static void WriteMasterFiles(XmlWriter writer, SaftMasterFiles masterFiles)
    {
        writer.WriteStartElement("MasterFiles");

        WriteGeneralLedgerAccounts(writer, masterFiles.GeneralLedgerAccounts);
        WriteCustomers(writer, masterFiles.Customers);
        WriteSuppliers(writer, masterFiles.Suppliers);

        writer.WriteEndElement(); // MasterFiles
    }

    private static void WriteGeneralLedgerAccounts(XmlWriter writer, List<SaftAccount> accounts)
    {
        if (accounts.Count == 0) return;

        writer.WriteStartElement("GeneralLedgerAccounts");

        // SAF-T DK 2027 requirement: StandardAccount metadata
        // Ref: BEK nr. 97 af 26/01/2023, Erhvervsstyrelsen
        writer.WriteElementString("NameOfStandardAccount", "Standardkontoplan");
        writer.WriteElementString("VersionOfStandardAccount", "20230131");

        foreach (var account in accounts)
        {
            writer.WriteStartElement("Account");

            writer.WriteElementString("AccountID", account.AccountID);
            writer.WriteElementString("AccountDescription", account.AccountDescription);

            if (!string.IsNullOrEmpty(account.StandardAccountID))
                writer.WriteElementString("StandardAccountID", account.StandardAccountID);

            writer.WriteElementString("AccountType", account.AccountType);

            // Opening balances
            if (account.OpeningDebitBalance != 0)
                writer.WriteElementString("OpeningDebitBalance", FormatDecimal(account.OpeningDebitBalance));
            if (account.OpeningCreditBalance != 0)
                writer.WriteElementString("OpeningCreditBalance", FormatDecimal(account.OpeningCreditBalance));

            // Closing balances
            if (account.ClosingDebitBalance != 0)
                writer.WriteElementString("ClosingDebitBalance", FormatDecimal(account.ClosingDebitBalance));
            if (account.ClosingCreditBalance != 0)
                writer.WriteElementString("ClosingCreditBalance", FormatDecimal(account.ClosingCreditBalance));

            writer.WriteEndElement(); // Account
        }

        writer.WriteEndElement(); // GeneralLedgerAccounts
    }

    private static void WriteCustomers(XmlWriter writer, List<SaftCustomer> customers)
    {
        if (customers.Count == 0) return;

        writer.WriteStartElement("Customers");

        foreach (var customer in customers)
        {
            writer.WriteStartElement("Customer");

            writer.WriteElementString("CustomerID", customer.CustomerID);

            if (!string.IsNullOrEmpty(customer.AccountID))
                writer.WriteElementString("AccountID", customer.AccountID);

            writer.WriteElementString("Name", customer.Name);

            if (!string.IsNullOrEmpty(customer.RegistrationNumber))
                writer.WriteElementString("RegistrationNumber", customer.RegistrationNumber);

            if (customer.Address != null)
                WriteAddress(writer, customer.Address);

            if (customer.Contact != null)
                WriteContact(writer, customer.Contact);

            writer.WriteEndElement(); // Customer
        }

        writer.WriteEndElement(); // Customers
    }

    private static void WriteSuppliers(XmlWriter writer, List<SaftSupplier> suppliers)
    {
        if (suppliers.Count == 0) return;

        writer.WriteStartElement("Suppliers");

        foreach (var supplier in suppliers)
        {
            writer.WriteStartElement("Supplier");

            writer.WriteElementString("SupplierID", supplier.SupplierID);

            if (!string.IsNullOrEmpty(supplier.AccountID))
                writer.WriteElementString("AccountID", supplier.AccountID);

            writer.WriteElementString("Name", supplier.Name);

            if (!string.IsNullOrEmpty(supplier.RegistrationNumber))
                writer.WriteElementString("RegistrationNumber", supplier.RegistrationNumber);

            if (supplier.Address != null)
                WriteAddress(writer, supplier.Address);

            if (supplier.Contact != null)
                WriteContact(writer, supplier.Contact);

            writer.WriteEndElement(); // Supplier
        }

        writer.WriteEndElement(); // Suppliers
    }

    private static void WriteGeneralLedgerEntries(XmlWriter writer, SaftGeneralLedgerEntries entries)
    {
        writer.WriteStartElement("GeneralLedgerEntries");

        writer.WriteElementString("NumberOfEntries", entries.NumberOfEntries.ToString());
        writer.WriteElementString("TotalDebit", FormatDecimal(entries.TotalDebit));
        writer.WriteElementString("TotalCredit", FormatDecimal(entries.TotalCredit));

        foreach (var journal in entries.Journals)
        {
            WriteJournal(writer, journal);
        }

        writer.WriteEndElement(); // GeneralLedgerEntries
    }

    private static void WriteJournal(XmlWriter writer, SaftJournal journal)
    {
        writer.WriteStartElement("Journal");

        writer.WriteElementString("JournalID", journal.JournalID);
        writer.WriteElementString("Description", journal.Description);
        writer.WriteElementString("Type", journal.Type);

        foreach (var transaction in journal.Transactions)
        {
            WriteTransaction(writer, transaction);
        }

        writer.WriteEndElement(); // Journal
    }

    private static void WriteTransaction(XmlWriter writer, SaftTransaction transaction)
    {
        writer.WriteStartElement("Transaction");

        writer.WriteElementString("TransactionID", transaction.TransactionID);
        writer.WriteElementString("Period", transaction.Period);
        writer.WriteElementString("TransactionDate", transaction.TransactionDate);
        writer.WriteElementString("Description", transaction.Description);
        writer.WriteElementString("SystemEntryDate", transaction.SystemEntryDate);
        writer.WriteElementString("GLPostingDate", transaction.GLPostingDate);

        foreach (var line in transaction.Lines)
        {
            WriteTransactionLine(writer, line);
        }

        writer.WriteEndElement(); // Transaction
    }

    private static void WriteTransactionLine(XmlWriter writer, SaftTransactionLine line)
    {
        writer.WriteStartElement("Line");

        writer.WriteElementString("RecordID", line.RecordID);
        writer.WriteElementString("AccountID", line.AccountID);

        if (!string.IsNullOrEmpty(line.Description))
            writer.WriteElementString("Description", line.Description);

        if (line.DebitAmount.HasValue && line.DebitAmount.Value != 0)
            writer.WriteElementString("DebitAmount", FormatDecimal(line.DebitAmount.Value));

        if (line.CreditAmount.HasValue && line.CreditAmount.Value != 0)
            writer.WriteElementString("CreditAmount", FormatDecimal(line.CreditAmount.Value));

        if (!string.IsNullOrEmpty(line.CustomerID))
            writer.WriteElementString("CustomerID", line.CustomerID);

        if (!string.IsNullOrEmpty(line.SupplierID))
            writer.WriteElementString("SupplierID", line.SupplierID);

        if (line.TaxInfo != null)
        {
            WriteTaxInformation(writer, line.TaxInfo);
        }

        writer.WriteEndElement(); // Line
    }

    private static void WriteTaxInformation(XmlWriter writer, SaftTaxInformation taxInfo)
    {
        writer.WriteStartElement("TaxInformation");

        writer.WriteElementString("TaxCode", taxInfo.TaxCode);

        if (taxInfo.TaxPercentage.HasValue)
            writer.WriteElementString("TaxPercentage", FormatDecimal(taxInfo.TaxPercentage.Value));

        if (taxInfo.TaxBase.HasValue)
            writer.WriteElementString("TaxBase", FormatDecimal(taxInfo.TaxBase.Value));

        writer.WriteElementString("TaxAmount", FormatDecimal(taxInfo.TaxAmount));

        writer.WriteEndElement(); // TaxInformation
    }

    private static string FormatDecimal(decimal value)
    {
        // SAF-T requires decimal format with dot as decimal separator
        return value.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);
    }
}
