using CashLineERP.Domain.Finance;
using CashLineERP.Domain.Organization;
using CashLineERP.Domain.Parties;
using CashLineERP.SharedKernel.Tax;
using CashLineERP.SharedKernel.Workflow;
using System.Text.Json;

namespace CashLineERP.Application.Tax;

public static class EtaPayloadBuilder
{
    private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };

    public static bool SupportsInvoiceType(string? invoiceType) =>
        TryResolveDocumentProfile(invoiceType) is not null;

    public static EtaPayloadBuildResult Build(AccountsReceivableInvoice invoice, BusinessParty? customer, Branch? branch)
    {
        var profile = TryResolveDocumentProfile(invoice.InvoiceType);
        var validationRules = BuildValidationRules(invoice, customer, branch, profile);
        var validationMessages = validationRules
            .Where(rule => !rule.IsSatisfied)
            .Select(rule => rule.Message)
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToArray();
        var taxBreakdown = invoice.Lines
            .GroupBy(line => StatutoryTaxPolicy.NormalizeTaxCode(line.TaxCode))
            .OrderBy(group => group.Key)
            .Select(group => new
            {
                taxCode = group.Key,
                taxCategory = StatutoryTaxPolicy.ResolveCategory(group.Key),
                taxRate = StatutoryTaxPolicy.ResolveRate(group.Key),
                taxableAmount = group.Sum(line => line.LineNetAmount),
                taxAmount = group.Sum(line => line.LineTaxAmount),
                totalAmount = group.Sum(line => line.LineTotalAmount),
                lineCount = group.Count()
            })
            .ToArray();

        var normalizedDocumentType = profile?.DocumentTypeCode ?? "UNSUPPORTED";
        var payload = new
        {
            documentType = normalizedDocumentType,
            documentNature = profile?.DocumentNature ?? "UNSUPPORTED",
            invoiceNumber = invoice.InvoiceNumber,
            invoiceDate = invoice.InvoiceDate,
            taxDate = invoice.TaxDate,
            dueDate = invoice.DueDate,
            currencyCode = invoice.CurrencyCode,
            seller = new
            {
                companyCode = "CASHLINE_ERP",
                branchCode = branch?.BranchCode,
                taxBranchCode = branch?.TaxBranchCode,
                countryCode = branch?.CountryCode ?? "EG"
            },
            buyer = new
            {
                customerId = invoice.CustomerId,
                customerCode = customer?.PartyCode,
                customerName = customer?.PartyName,
                taxRegistrationNumber = customer?.TaxRegistrationNumber,
                countryCode = customer?.CountryCode ?? "EG",
                addressLine1 = customer?.AddressLine1,
                email = customer?.Email,
                mobileNumber = customer?.MobileNumber
            },
            referenceDocument = profile?.RequiresAdjustmentReference == true
                ? new
                {
                    referenceRequired = true,
                    sourceSalesOrderId = invoice.SalesOrderId,
                    sourceDispatchOrderId = invoice.DispatchOrderId,
                    referenceEvidenceStatus = invoice.SalesOrderId.HasValue || invoice.DispatchOrderId.HasValue
                        ? "DERIVED_FROM_SOURCE_LINKS"
                        : "MISSING_REFERENCE"
                }
                : null,
            totals = new
            {
                subtotalAmount = invoice.SubtotalAmount,
                taxAmount = invoice.TaxAmount,
                totalAmount = invoice.TotalAmount
            },
            taxBreakdown,
            lines = invoice.Lines
                .OrderBy(line => line.LineNumber)
                .Select(line => new
                {
                    lineNumber = line.LineNumber,
                    itemId = line.ItemId,
                    quantity = line.QuantityInvoiced,
                    unitOfMeasureCode = line.UnitOfMeasureCode,
                    unitPrice = line.UnitPrice,
                    taxCode = line.TaxCode,
                    lineNetAmount = line.LineNetAmount,
                    lineTaxAmount = line.LineTaxAmount,
                    lineTotalAmount = line.LineTotalAmount
                })
                .ToArray(),
            statutoryEvidence = new
            {
                sourceModule = "FINANCE",
                sourceDocumentId = invoice.Id,
                supportedInvoiceType = profile is not null,
                generalLedgerBatchId = invoice.GeneralLedgerBatchId,
                etaStatus = invoice.EtaStatus,
                isStatutoryReady = validationMessages.Length == 0,
                validationMessages,
                generatedAtUtc = DateTime.UtcNow
            }
        };

        return new EtaPayloadBuildResult(
            normalizedDocumentType,
            JsonSerializer.Serialize(payload, JsonOptions),
            validationMessages.Length == 0,
            validationMessages,
            validationRules);
    }

    private static IReadOnlyCollection<EtaSubmissionValidationRuleView> BuildValidationRules(
        AccountsReceivableInvoice invoice,
        BusinessParty? customer,
        Branch? branch,
        EtaDocumentProfile? profile)
    {
        var rules = new List<EtaSubmissionValidationRuleView>();
        var lineNetTotal = invoice.Lines.Sum(line => line.LineNetAmount);
        var lineTaxTotal = invoice.Lines.Sum(line => line.LineTaxAmount);
        var lineGrossTotal = invoice.Lines.Sum(line => line.LineTotalAmount);

        rules.Add(new EtaSubmissionValidationRuleView(
            "DOC-001",
            "DOCUMENT_TYPE",
            profile is not null,
            profile is not null
                ? $"Invoice type '{invoice.InvoiceType}' is supported and normalized to '{profile.DocumentTypeCode}'."
                : $"Invoice type '{invoice.InvoiceType}' is not supported by the current ETA payload builder."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "DOC-002",
            "DOCUMENT_TYPE",
            invoice.StatusId == WorkflowStatusSeedIds.Posted,
            invoice.StatusId == WorkflowStatusSeedIds.Posted
                ? "Invoice is posted and can be evaluated for ETA submission."
                : "Invoice must be posted before ETA submission."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "DOC-003",
            "DOCUMENT_TYPE",
            profile?.RequiresTaxDate != true || invoice.TaxDate is not null,
            profile?.RequiresTaxDate != true || invoice.TaxDate is not null
                ? "Tax date requirement is satisfied for the selected ETA document type."
                : $"{profile!.DocumentLabel} requires tax date before ETA submission."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "DOC-004",
            "DOCUMENT_TYPE",
            profile?.RequiresAdjustmentReference != true || invoice.SalesOrderId is not null || invoice.DispatchOrderId is not null,
            profile?.RequiresAdjustmentReference != true || invoice.SalesOrderId is not null || invoice.DispatchOrderId is not null
                ? "Document-type source reference rule is satisfied."
                : $"{profile!.DocumentLabel} requires source reference evidence such as sales order or dispatch linkage."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "BRN-001",
            "BRANCH",
            branch is not null,
            branch is not null
                ? $"Seller branch '{branch.BranchCode}' resolved successfully."
                : "Seller branch could not be resolved from linked sales or dispatch records."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "BRN-002",
            "BRANCH",
            branch?.IsActive == true,
            branch?.IsActive == true
                ? "Seller branch is active."
                : "Seller branch must be active for ETA submission."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "BRN-003",
            "BRANCH",
            !string.IsNullOrWhiteSpace(branch?.TaxBranchCode),
            !string.IsNullOrWhiteSpace(branch?.TaxBranchCode)
                ? "Seller branch tax branch code is available."
                : "Seller branch tax branch code is required for ETA submission."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "BUY-001",
            "BUYER",
            customer is not null,
            customer is not null
                ? $"Buyer '{customer.PartyCode}' resolved successfully."
                : "Customer party record is required for ETA buyer evidence."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "BUY-002",
            "BUYER",
            customer?.IsActive == true,
            customer?.IsActive == true
                ? "Buyer party is active."
                : "Buyer party must be active for ETA submission."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "BUY-003",
            "BUYER",
            string.Equals(customer?.PartyType, "CUSTOMER", StringComparison.OrdinalIgnoreCase),
            string.Equals(customer?.PartyType, "CUSTOMER", StringComparison.OrdinalIgnoreCase)
                ? "Buyer party type is valid for customer invoicing."
                : "Buyer party type must be CUSTOMER for ETA submission."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "BUY-004",
            "BUYER",
            !string.IsNullOrWhiteSpace(customer?.PartyName),
            !string.IsNullOrWhiteSpace(customer?.PartyName)
                ? "Buyer legal/display name is available."
                : "Buyer name is required for ETA buyer evidence."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "BUY-005",
            "BUYER",
            !string.IsNullOrWhiteSpace(customer?.CountryCode),
            !string.IsNullOrWhiteSpace(customer?.CountryCode)
                ? "Buyer country code is available."
                : "Buyer country code is required for ETA buyer evidence."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "BUY-006",
            "BUYER",
            !string.Equals(customer?.CountryCode, "EG", StringComparison.OrdinalIgnoreCase)
                || !string.IsNullOrWhiteSpace(customer?.TaxRegistrationNumber),
            !string.Equals(customer?.CountryCode, "EG", StringComparison.OrdinalIgnoreCase)
                || !string.IsNullOrWhiteSpace(customer?.TaxRegistrationNumber)
                ? "Buyer tax registration rule is satisfied."
                : "Egyptian buyer tax registration number is required for ETA submission."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "TAX-001",
            "TAX_TOTALS",
            invoice.Lines.Count > 0,
            invoice.Lines.Count > 0
                ? "Invoice contains at least one line for ETA totals."
                : "Invoice must contain at least one line before ETA submission."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "TAX-002",
            "TAX_TOTALS",
            lineNetTotal == invoice.SubtotalAmount,
            lineNetTotal == invoice.SubtotalAmount
                ? "Header subtotal matches summed line net amount."
                : $"Header subtotal {invoice.SubtotalAmount} does not match summed line net amount {lineNetTotal}."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "TAX-003",
            "TAX_TOTALS",
            lineTaxTotal == invoice.TaxAmount,
            lineTaxTotal == invoice.TaxAmount
                ? "Header tax total matches summed line tax amount."
                : $"Header tax total {invoice.TaxAmount} does not match summed line tax amount {lineTaxTotal}."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "TAX-004",
            "TAX_TOTALS",
            lineGrossTotal == invoice.TotalAmount,
            lineGrossTotal == invoice.TotalAmount
                ? "Header total matches summed line gross amount."
                : $"Header total {invoice.TotalAmount} does not match summed line gross amount {lineGrossTotal}."));

        rules.Add(new EtaSubmissionValidationRuleView(
            "TAX-005",
            "TAX_TOTALS",
            invoice.SubtotalAmount + invoice.TaxAmount == invoice.TotalAmount,
            invoice.SubtotalAmount + invoice.TaxAmount == invoice.TotalAmount
                ? "Header subtotal plus tax equals header total."
                : $"Header subtotal plus tax does not equal header total ({invoice.SubtotalAmount} + {invoice.TaxAmount} != {invoice.TotalAmount})."));

        var unknownTaxCodes = new List<string>();
        foreach (var line in invoice.Lines.OrderBy(entry => entry.LineNumber))
        {
            if (string.IsNullOrWhiteSpace(line.TaxCode))
            {
                unknownTaxCodes.Add($"line {line.LineNumber}: missing tax code");
                continue;
            }

            if (!StatutoryTaxPolicy.IsRecognized(line.TaxCode))
            {
                unknownTaxCodes.Add($"line {line.LineNumber}: unsupported tax code '{line.TaxCode}'");
            }
        }

        rules.Add(new EtaSubmissionValidationRuleView(
            "TAX-006",
            "TAX_TOTALS",
            unknownTaxCodes.Count == 0,
            unknownTaxCodes.Count == 0
                ? "All invoice lines use recognized statutory tax codes."
                : $"Tax-code validation failed for {string.Join(", ", unknownTaxCodes)}."));

        return rules;
    }

    private static EtaDocumentProfile? TryResolveDocumentProfile(string? invoiceType)
    {
        var normalized = invoiceType?.Trim().ToUpperInvariant();

        return normalized switch
        {
            "TAX" or "TAX_INVOICE" => new EtaDocumentProfile("TAX_INVOICE", "COMMERCIAL_INVOICE", "Tax invoice", true, false),
            "DEBIT" or "DEBIT_NOTE" or "DEBIT_MEMO" => new EtaDocumentProfile("DEBIT_NOTE", "ADJUSTMENT_NOTE", "Debit note", true, true),
            "CREDIT" or "CREDIT_NOTE" or "CREDIT_MEMO" => new EtaDocumentProfile("CREDIT_NOTE", "ADJUSTMENT_NOTE", "Credit note", true, true),
            _ => null
        };
    }

    private sealed record EtaDocumentProfile(
        string DocumentTypeCode,
        string DocumentNature,
        string DocumentLabel,
        bool RequiresTaxDate,
        bool RequiresAdjustmentReference);
}
