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

namespace Books.Api.Invoicing.Services;

/// <summary>
/// Implementation of invoice posting to the Ledger.
/// Uses the existing Ledger service for creating transactions.
/// </summary>
public class InvoicePostingService(
    ILedgerService ledgerService,
    ICustomerRepository customerRepository,
    IAccountRepository accountRepository,
    ILogger<InvoicePostingService> logger) : IInvoicePostingService
{
    private const string OutputVatAccountNumber = "5611"; // Salgsmoms

    public async Task<InvoicePostingResult> PostInvoiceAsync(
        string invoiceId,
        string customerId,
        string fiscalYearId,
        string invoiceNumber,
        IReadOnlyList<InvoiceLine> lines,
        string defaultRevenueAccountId,
        CancellationToken cancellationToken = default)
    {
        try
        {
            // Get customer to find sub-ledger account
            var customer = await customerRepository.GetByIdAsync(customerId, cancellationToken);
            if (customer == null)
            {
                return InvoicePostingResult.Failed("CUSTOMER_NOT_FOUND", $"Customer '{customerId}' not found");
            }

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

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

            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 = lines.Sum(l => l.AmountExVat);
            var totalVat = lines.Sum(l => l.AmountVat);
            var totalAmount = lines.Sum(l => l.AmountTotal);

            // 1. Debit customer sub-ledger account (1900-XXXX) for total amount
            legs.Add(new TransactionLeg
            {
                AccountId = customerAccountGuid,
                EntryType = EntryType.Debit,
                Amount = totalAmount,
                Currency = "DKK"
            });

            // 2. Credit revenue accounts for each line (ex VAT)
            // Group by account to consolidate legs
            var linesByAccount = lines.GroupBy(l => l.AccountId ?? defaultRevenueAccountId);

            foreach (var group in linesByAccount)
            {
                var accountId = group.Key;
                if (!TryParseAccountGuid(accountId, out var revenueAccountGuid))
                {
                    return InvoicePostingResult.Failed("INVALID_REVENUE_ACCOUNT",
                        $"Invalid revenue account format: {accountId}");
                }

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

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

            // Parse fiscal year ID for period
            Guid? periodId = null;
            if (fiscalYearId.StartsWith("fiscalyear-"))
            {
                periodId = Guid.Parse(fiscalYearId.Replace("fiscalyear-", ""));
            }
            else if (Guid.TryParse(fiscalYearId, out var parsedPeriodId))
            {
                periodId = parsedPeriodId;
            }

            // Create transaction request
            var request = new TransactionRequest
            {
                IdempotencyKey = $"invoice-send-{invoiceId}",
                End2EndId = Guid.NewGuid().ToString(),
                TransactionType = TransactionType.Auto,
                ReferenceId = invoiceId,
                Description = $"Faktura {invoiceNumber} - {customer.Name}",
                Legs = legs,
                PeriodId = periodId
            };

            logger.LogInformation(
                "Posting invoice {InvoiceNumber} to Ledger: Debit {CustomerAccount} {Total:N2}, Credit revenue {ExVat:N2}, Credit VAT {Vat:N2}",
                invoiceNumber, customer.SubLedgerAccountId, totalAmount, totalExVat, totalVat);

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

            if (!result.Success)
            {
                logger.LogError(
                    "Failed to post invoice {InvoiceNumber} to Ledger: {ErrorCode} - {ErrorMessage}",
                    invoiceNumber, result.ErrorCode, result.ErrorMessage);
                return InvoicePostingResult.Failed(
                    result.ErrorCode ?? "LEDGER_ERROR",
                    result.ErrorMessage ?? "Unknown ledger error");
            }

            logger.LogInformation(
                "Successfully posted invoice {InvoiceNumber} to Ledger with transaction {TransactionId}",
                invoiceNumber, result.TransactionId);

            return InvoicePostingResult.Succeeded(result.TransactionId!.Value.ToString());
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Exception while posting invoice {InvoiceId} to Ledger", invoiceId);
            return InvoicePostingResult.Failed("POSTING_EXCEPTION", ex.Message);
        }
    }

    public async Task<InvoicePostingResult> ReverseInvoiceAsync(
        string invoiceId,
        string originalTransactionId,
        string reason,
        CancellationToken cancellationToken = default)
    {
        try
        {
            // For reversal, we need to query the original transaction and create opposite entries
            // This is a simplified implementation - in production, you might want to use Ledger's
            // built-in reversal functionality if available

            logger.LogInformation(
                "Reversing invoice {InvoiceId} transaction {TransactionId}: {Reason}",
                invoiceId, originalTransactionId, reason);

            // Create a reversal transaction request
            // Note: This is a placeholder - the actual implementation depends on how the Ledger
            // service handles reversals. You might need to:
            // 1. Query the original transaction
            // 2. Create opposite entries
            // 3. Link to the original transaction

            // For now, we'll create a simple request that marks this as a reversal
            var request = new TransactionRequest
            {
                IdempotencyKey = $"invoice-void-{invoiceId}",
                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, cancellationToken);

            if (!result.Success)
            {
                logger.LogError(
                    "Failed to reverse invoice {InvoiceId}: {ErrorCode} - {ErrorMessage}",
                    invoiceId, result.ErrorCode, result.ErrorMessage);
                return InvoicePostingResult.Failed(
                    result.ErrorCode ?? "REVERSAL_ERROR",
                    result.ErrorMessage ?? "Unknown reversal error");
            }

            return InvoicePostingResult.Succeeded(result.TransactionId!.Value.ToString());
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Exception while reversing invoice {InvoiceId}", invoiceId);
            return InvoicePostingResult.Failed("REVERSAL_EXCEPTION", ex.Message);
        }
    }

    public async Task<InvoicePostingResult> PostPaymentAsync(
        string invoiceId,
        string customerId,
        string bankAccountId,
        decimal amount,
        string fiscalYearId,
        string? reference,
        CancellationToken cancellationToken = default)
    {
        try
        {
            // Get customer to find sub-ledger account
            var customer = await customerRepository.GetByIdAsync(customerId, cancellationToken);
            if (customer == null)
            {
                return InvoicePostingResult.Failed("CUSTOMER_NOT_FOUND", $"Customer '{customerId}' not found");
            }

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

            if (!TryParseAccountGuid(bankAccountId, out var bankAccountGuid))
            {
                return InvoicePostingResult.Failed("INVALID_BANK_ACCOUNT",
                    $"Invalid bank account format: {bankAccountId}");
            }

            // Parse fiscal year ID for period
            Guid? periodId = null;
            if (fiscalYearId.StartsWith("fiscalyear-"))
            {
                periodId = Guid.Parse(fiscalYearId.Replace("fiscalyear-", ""));
            }
            else if (Guid.TryParse(fiscalYearId, out var parsedPeriodId))
            {
                periodId = parsedPeriodId;
            }

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

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

            logger.LogInformation(
                "Posting payment for invoice {InvoiceId}: {Amount:N2} DKK",
                invoiceId, amount);

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

            if (!result.Success)
            {
                logger.LogError(
                    "Failed to post payment for invoice {InvoiceId}: {ErrorCode} - {ErrorMessage}",
                    invoiceId, result.ErrorCode, result.ErrorMessage);
                return InvoicePostingResult.Failed(
                    result.ErrorCode ?? "PAYMENT_ERROR",
                    result.ErrorMessage ?? "Unknown payment error");
            }

            return InvoicePostingResult.Succeeded(result.TransactionId!.Value.ToString());
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Exception while posting payment for invoice {InvoiceId}", invoiceId);
            return InvoicePostingResult.Failed("PAYMENT_EXCEPTION", ex.Message);
        }
    }

    public async Task<InvoicePostingResult> PostCreditNoteAsync(
        string creditNoteId,
        string customerId,
        string fiscalYearId,
        string creditNoteNumber,
        IReadOnlyList<InvoiceLine> lines,
        string defaultRevenueAccountId,
        CancellationToken cancellationToken = default)
    {
        try
        {
            // Get customer to find sub-ledger account
            var customer = await customerRepository.GetByIdAsync(customerId, cancellationToken);
            if (customer == null)
            {
                return InvoicePostingResult.Failed("CUSTOMER_NOT_FOUND", $"Customer '{customerId}' not found");
            }

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

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

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

            // Build transaction legs (OPPOSITE of invoice posting)
            var legs = new List<TransactionLeg>();

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

            // 1. CREDIT customer sub-ledger account (opposite of invoice)
            legs.Add(new TransactionLeg
            {
                AccountId = customerAccountGuid,
                EntryType = EntryType.Credit,
                Amount = totalAmount,
                Currency = "DKK"
            });

            // 2. DEBIT revenue accounts for each line (opposite of invoice)
            var linesByAccount = lines.GroupBy(l => l.AccountId ?? defaultRevenueAccountId);

            foreach (var group in linesByAccount)
            {
                var accountId = group.Key;
                if (!TryParseAccountGuid(accountId, out var revenueAccountGuid))
                {
                    return InvoicePostingResult.Failed("INVALID_REVENUE_ACCOUNT",
                        $"Invalid revenue account format: {accountId}");
                }

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

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

            // Parse fiscal year ID for period
            Guid? periodId = null;
            if (fiscalYearId.StartsWith("fiscalyear-"))
            {
                periodId = Guid.Parse(fiscalYearId.Replace("fiscalyear-", ""));
            }
            else if (Guid.TryParse(fiscalYearId, out var parsedPeriodId))
            {
                periodId = parsedPeriodId;
            }

            // Create transaction request
            var request = new TransactionRequest
            {
                IdempotencyKey = $"creditnote-issue-{creditNoteId}",
                End2EndId = Guid.NewGuid().ToString(),
                TransactionType = TransactionType.Auto,
                ReferenceId = creditNoteId,
                Description = $"Kreditnota {creditNoteNumber} - {customer.Name}",
                Legs = legs,
                PeriodId = periodId
            };

            logger.LogInformation(
                "Posting credit note {CreditNoteNumber} to Ledger: Credit {CustomerAccount} {Total:N2}, Debit revenue {ExVat:N2}, Debit VAT {Vat:N2}",
                creditNoteNumber, customer.SubLedgerAccountId, totalAmount, totalExVat, totalVat);

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

            if (!result.Success)
            {
                logger.LogError(
                    "Failed to post credit note {CreditNoteNumber} to Ledger: {ErrorCode} - {ErrorMessage}",
                    creditNoteNumber, result.ErrorCode, result.ErrorMessage);
                return InvoicePostingResult.Failed(
                    result.ErrorCode ?? "LEDGER_ERROR",
                    result.ErrorMessage ?? "Unknown ledger error");
            }

            logger.LogInformation(
                "Successfully posted credit note {CreditNoteNumber} to Ledger with transaction {TransactionId}",
                creditNoteNumber, result.TransactionId);

            return InvoicePostingResult.Succeeded(result.TransactionId!.Value.ToString());
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Exception while posting credit note {CreditNoteId} to Ledger", creditNoteId);
            return InvoicePostingResult.Failed("POSTING_EXCEPTION", ex.Message);
        }
    }

    public async Task<InvoicePostingResult> PostCreditApplicationAsync(
        string creditNoteId,
        string invoiceId,
        string customerId,
        decimal amount,
        string fiscalYearId,
        CancellationToken cancellationToken = default)
    {
        try
        {
            // Get customer to find sub-ledger account
            var customer = await customerRepository.GetByIdAsync(customerId, cancellationToken);
            if (customer == null)
            {
                return InvoicePostingResult.Failed("CUSTOMER_NOT_FOUND", $"Customer '{customerId}' not found");
            }

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

            // Parse fiscal year ID for period
            Guid? periodId = null;
            if (fiscalYearId.StartsWith("fiscalyear-"))
            {
                periodId = Guid.Parse(fiscalYearId.Replace("fiscalyear-", ""));
            }
            else if (Guid.TryParse(fiscalYearId, out var parsedPeriodId))
            {
                periodId = parsedPeriodId;
            }

            // Credit application is an internal offset:
            // The credit note's credit to customer account offsets the invoice's debit
            // This is a zero-sum transaction within the same customer sub-ledger account
            // We create a memo transaction to track the application

            var legs = new List<TransactionLeg>
            {
                // Debit customer (reduces credit note credit balance)
                new()
                {
                    AccountId = customerAccountGuid,
                    EntryType = EntryType.Debit,
                    Amount = amount,
                    Currency = "DKK"
                },
                // Credit customer (reduces invoice debit balance)
                new()
                {
                    AccountId = customerAccountGuid,
                    EntryType = EntryType.Credit,
                    Amount = amount,
                    Currency = "DKK"
                }
            };

            var request = new TransactionRequest
            {
                IdempotencyKey = $"credit-application-{creditNoteId}-{invoiceId}-{Guid.NewGuid():N}",
                End2EndId = Guid.NewGuid().ToString(),
                TransactionType = TransactionType.Auto,
                ReferenceId = $"{creditNoteId}:{invoiceId}",
                Description = $"Kredit anvendt fra kreditnota til faktura - {customer.Name}",
                Legs = legs,
                PeriodId = periodId
            };

            logger.LogInformation(
                "Posting credit application from {CreditNoteId} to {InvoiceId}: {Amount:N2} DKK",
                creditNoteId, invoiceId, amount);

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

            if (!result.Success)
            {
                logger.LogError(
                    "Failed to post credit application: {ErrorCode} - {ErrorMessage}",
                    result.ErrorCode, result.ErrorMessage);
                return InvoicePostingResult.Failed(
                    result.ErrorCode ?? "APPLICATION_ERROR",
                    result.ErrorMessage ?? "Unknown application error");
            }

            return InvoicePostingResult.Succeeded(result.TransactionId!.Value.ToString());
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Exception while posting credit application");
            return InvoicePostingResult.Failed("APPLICATION_EXCEPTION", ex.Message);
        }
    }

    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);
    }
}
