using Books.Api.EventFlow.Repositories;
using Ledger.Core.Models;
using Ledger.Core.Services;
using Microsoft.Extensions.Logging;

namespace Books.Api.Invoicing.Services;

/// <summary>
/// Unified implementation for posting sales documents (invoices and credit notes) to the Ledger.
/// Uses the document type to determine entry direction - invoice and credit note logic
/// is identical except for debit/credit being swapped.
/// </summary>
public class DocumentPostingService(
    ILedgerService ledgerService,
    ICustomerRepository customerRepository,
    IAccountRepository accountRepository,
    ILogger<DocumentPostingService> logger) : IDocumentPostingService
{
    private const string OutputVatAccountNumber = "5611"; // Salgsmoms

    public async Task<PostingResult> PostSalesDocumentAsync(
        SalesDocumentPostingRequest request,
        CancellationToken ct = default)
    {
        try
        {
            // Get customer to find sub-ledger account
            var customer = await customerRepository.GetByIdAsync(request.CustomerId, ct);
            if (customer == null)
            {
                return PostingResult.Failed("CUSTOMER_NOT_FOUND",
                    $"Kunde med ID {request.CustomerId} blev ikke fundet");
            }

            // Parse sub-ledger account ID to GUID
            if (!TryParseAccountGuid(customer.SubLedgerAccountId, out var customerAccountGuid))
            {
                return PostingResult.Failed("INVALID_SUBLEDGER_ACCOUNT",
                    $"Ugyldigt sub-ledger konto format: {customer.SubLedgerAccountId}");
            }

            // Get output VAT account
            var vatAccount = await accountRepository.GetByCompanyAndNumberAsync(
                customer.CompanyId, OutputVatAccountNumber, ct);

            Guid? vatAccountGuid = null;
            if (vatAccount != null && TryParseAccountGuid(vatAccount.Id, out var parsedVatGuid))
            {
                vatAccountGuid = parsedVatGuid;
            }

            // Build transaction legs
            var legs = new List<TransactionLeg>();

            // Calculate totals
            var totalExVat = request.Lines.Sum(l => l.AmountExVat);
            var totalVat = request.Lines.Sum(l => l.AmountVat);
            var totalAmount = request.Lines.Sum(l => l.AmountTotal);

            // Determine entry types based on document type
            // Invoice: Debit customer, Credit revenue/VAT
            // Credit Note: Credit customer, Debit revenue/VAT
            var isInvoice = request.DocumentType == SalesDocumentType.Invoice;
            var customerEntryType = isInvoice ? EntryType.Debit : EntryType.Credit;
            var revenueVatEntryType = isInvoice ? EntryType.Credit : EntryType.Debit;

            // 1. Customer sub-ledger account
            legs.Add(new TransactionLeg
            {
                AccountId = customerAccountGuid,
                EntryType = customerEntryType,
                Amount = totalAmount,
                Currency = "DKK"
            });

            // 2. Revenue accounts for each line (group by account to consolidate)
            var defaultRevenueAccountId = request.DefaultRevenueAccountId ?? customer.DefaultRevenueAccountId;
            var linesByAccount = request.Lines.GroupBy(l => l.AccountId ?? defaultRevenueAccountId);

            foreach (var group in linesByAccount)
            {
                var accountId = group.Key;
                if (string.IsNullOrEmpty(accountId))
                {
                    // Fallback to standard revenue account
                    var defaultRevenue = await accountRepository.GetByCompanyAndNumberAsync(
                        request.CompanyId, "1000", ct);
                    accountId = defaultRevenue?.Id;
                }

                if (string.IsNullOrEmpty(accountId) || !TryParseAccountGuid(accountId, out var revenueAccountGuid))
                {
                    return PostingResult.Failed("INVALID_REVENUE_ACCOUNT",
                        $"Ugyldigt omsætningskonto format: {accountId}");
                }

                var amount = group.Sum(l => l.AmountExVat);
                if (amount > 0)
                {
                    legs.Add(new TransactionLeg
                    {
                        AccountId = revenueAccountGuid,
                        EntryType = revenueVatEntryType,
                        Amount = amount,
                        Currency = "DKK"
                    });
                }
            }

            // 3. VAT account for total VAT
            if (totalVat > 0 && vatAccountGuid.HasValue)
            {
                legs.Add(new TransactionLeg
                {
                    AccountId = vatAccountGuid.Value,
                    EntryType = revenueVatEntryType,
                    Amount = totalVat,
                    Currency = "DKK"
                });
            }

            // Parse fiscal year ID for period
            var periodId = ParseFiscalYearId(request.FiscalYearId);

            // Determine description and idempotency key based on document type
            var documentTypePrefix = isInvoice ? "invoice" : "creditnote";
            var documentTypeLabel = isInvoice ? "Faktura" : "Kreditnota";
            var description = request.Description ?? $"{documentTypeLabel} {request.DocumentNumber} - {customer.Name}";

            // Create transaction request
            var txRequest = new TransactionRequest
            {
                IdempotencyKey = $"{documentTypePrefix}-post-{request.DocumentId}",
                End2EndId = Guid.NewGuid().ToString(),
                TransactionType = TransactionType.Auto,
                ReferenceId = request.DocumentId,
                Description = description,
                Legs = legs,
                PeriodId = periodId
            };

            logger.LogInformation(
                "Posting {DocumentType} {DocumentNumber} to Ledger: {CustomerEntry} {CustomerAccount} {Total:N2}, {RevenueEntry} revenue {ExVat:N2}, {RevenueEntry} VAT {Vat:N2}",
                documentTypeLabel, request.DocumentNumber,
                customerEntryType, customer.SubLedgerAccountId,
                totalAmount, revenueVatEntryType, totalExVat, revenueVatEntryType, totalVat);

            var result = await ledgerService.ProcessTransactionAsync(txRequest, ct);

            if (!result.Success)
            {
                logger.LogError(
                    "Failed to post {DocumentType} {DocumentNumber} to Ledger: {ErrorCode} - {ErrorMessage}",
                    documentTypeLabel, request.DocumentNumber, result.ErrorCode, result.ErrorMessage);
                return PostingResult.Failed(
                    result.ErrorCode ?? "LEDGER_ERROR",
                    result.ErrorMessage ?? "Ukendt fejl");
            }

            logger.LogInformation(
                "Successfully posted {DocumentType} {DocumentNumber} to Ledger with transaction {TransactionId}",
                documentTypeLabel, request.DocumentNumber, result.TransactionId);

            return PostingResult.Succeeded(result.TransactionId!.Value.ToString());
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Exception while posting document {DocumentId} to Ledger", request.DocumentId);
            return PostingResult.Failed("POSTING_EXCEPTION", ex.Message);
        }
    }

    public async Task<PostingResult> ReverseSalesDocumentAsync(
        string documentId,
        string originalTransactionId,
        string reason,
        CancellationToken ct = default)
    {
        try
        {
            if (string.IsNullOrEmpty(originalTransactionId))
            {
                // Draft document, no reversal needed
                return PostingResult.Succeeded(string.Empty);
            }

            logger.LogInformation(
                "Reversing document {DocumentId} transaction {TransactionId}: {Reason}",
                documentId, originalTransactionId, reason);

            var request = new TransactionRequest
            {
                IdempotencyKey = $"document-void-{documentId}",
                End2EndId = Guid.NewGuid().ToString(),
                TransactionType = TransactionType.Void,
                ReferenceId = originalTransactionId,
                Description = $"Annullering: {reason}",
                Legs = [] // Ledger should handle reversal based on ReferenceId
            };

            var result = await ledgerService.ProcessTransactionAsync(request, ct);

            if (!result.Success)
            {
                logger.LogError(
                    "Failed to reverse document {DocumentId}: {ErrorCode} - {ErrorMessage}",
                    documentId, result.ErrorCode, result.ErrorMessage);
                return PostingResult.Failed(
                    result.ErrorCode ?? "REVERSAL_ERROR",
                    result.ErrorMessage ?? "Ukendt fejl ved tilbageførsel");
            }

            return PostingResult.Succeeded(result.TransactionId!.Value.ToString());
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Exception while reversing document {DocumentId}", documentId);
            return PostingResult.Failed("REVERSAL_EXCEPTION", ex.Message);
        }
    }

    public async Task<PostingResult> PostPaymentAsync(
        PaymentPostingRequest request,
        CancellationToken ct = default)
    {
        try
        {
            // Get customer to find sub-ledger account
            var customer = await customerRepository.GetByIdAsync(request.CustomerId, ct);
            if (customer == null)
            {
                return PostingResult.Failed("CUSTOMER_NOT_FOUND",
                    $"Kunde med ID {request.CustomerId} blev ikke fundet");
            }

            // Parse account IDs
            if (!TryParseAccountGuid(customer.SubLedgerAccountId, out var customerAccountGuid))
            {
                return PostingResult.Failed("INVALID_SUBLEDGER_ACCOUNT",
                    $"Ugyldigt sub-ledger konto format: {customer.SubLedgerAccountId}");
            }

            if (!TryParseAccountGuid(request.BankAccountId, out var bankAccountGuid))
            {
                return PostingResult.Failed("INVALID_BANK_ACCOUNT",
                    $"Ugyldigt bankkonto format: {request.BankAccountId}");
            }

            // Parse fiscal year ID for period
            var periodId = ParseFiscalYearId(request.FiscalYearId);

            // Build payment transaction:
            // Debit: Bank account
            // Credit: Customer sub-ledger account
            var legs = new List<TransactionLeg>
            {
                new()
                {
                    AccountId = bankAccountGuid,
                    EntryType = EntryType.Debit,
                    Amount = request.Amount,
                    Currency = "DKK"
                },
                new()
                {
                    AccountId = customerAccountGuid,
                    EntryType = EntryType.Credit,
                    Amount = request.Amount,
                    Currency = "DKK"
                }
            };

            var txRequest = new TransactionRequest
            {
                IdempotencyKey = $"payment-{request.DocumentId}-{Guid.NewGuid():N}",
                End2EndId = Guid.NewGuid().ToString(),
                TransactionType = TransactionType.Auto,
                ReferenceId = request.DocumentId,
                Description = $"Betaling - {customer.Name}" + (request.Reference != null ? $" ({request.Reference})" : ""),
                Legs = legs,
                PeriodId = periodId
            };

            logger.LogInformation(
                "Posting payment for document {DocumentId}: {Amount:N2} DKK",
                request.DocumentId, request.Amount);

            var result = await ledgerService.ProcessTransactionAsync(txRequest, ct);

            if (!result.Success)
            {
                logger.LogError(
                    "Failed to post payment for document {DocumentId}: {ErrorCode} - {ErrorMessage}",
                    request.DocumentId, result.ErrorCode, result.ErrorMessage);
                return PostingResult.Failed(
                    result.ErrorCode ?? "PAYMENT_ERROR",
                    result.ErrorMessage ?? "Ukendt betalingsfejl");
            }

            return PostingResult.Succeeded(result.TransactionId!.Value.ToString());
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Exception while posting payment for document {DocumentId}", request.DocumentId);
            return PostingResult.Failed("PAYMENT_EXCEPTION", ex.Message);
        }
    }

    private static Guid? ParseFiscalYearId(string? fiscalYearId)
    {
        if (string.IsNullOrEmpty(fiscalYearId))
            return null;

        if (fiscalYearId.StartsWith("fiscalyear-"))
        {
            return Guid.Parse(fiscalYearId.Replace("fiscalyear-", ""));
        }

        return Guid.TryParse(fiscalYearId, out var parsed) ? parsed : null;
    }

    private static bool TryParseAccountGuid(string accountId, out Guid guid)
    {
        guid = Guid.Empty;

        // AccountId format: "account-{guid}"
        if (accountId.StartsWith("account-", StringComparison.Ordinal))
        {
            var guidString = accountId["account-".Length..];
            return Guid.TryParse(guidString, out guid);
        }

        // Try parsing as raw GUID
        return Guid.TryParse(accountId, out guid);
    }
}
