using Books.Api.EventFlow.ReadModels;
using Books.Api.EventFlow.Repositories;
using Microsoft.Extensions.Logging;

namespace Books.Api.Invoicing.Pdf;

/// <summary>
/// Service for generating invoice PDFs, orchestrating data fetching and PDF generation.
/// </summary>
public class InvoicePdfService(
    IInvoiceRepository invoiceRepository,
    ICustomerRepository customerRepository,
    ICompanyRepository companyRepository,
    IInvoicePdfGenerator pdfGenerator,
    ILogger<InvoicePdfService> logger) : IInvoicePdfService
{
    public async Task<InvoicePdfResult> GenerateAsync(string invoiceId, CancellationToken ct = default)
    {
        try
        {
            // 1. Fetch invoice
            var invoice = await invoiceRepository.GetByIdAsync(invoiceId, ct);
            if (invoice == null)
            {
                logger.LogWarning("Invoice not found: {InvoiceId}", invoiceId);
                return InvoicePdfResult.Failed("INVOICE_NOT_FOUND", $"Faktura '{invoiceId}' blev ikke fundet");
            }

            // 2. Fetch company
            var company = await companyRepository.GetByIdAsync(invoice.CompanyId, ct);
            if (company == null)
            {
                logger.LogWarning("Company not found for invoice: {InvoiceId}, CompanyId: {CompanyId}",
                    invoiceId, invoice.CompanyId);
                return InvoicePdfResult.Failed("COMPANY_NOT_FOUND", "Virksomhed blev ikke fundet");
            }

            // 3. Fetch customer (for full address details, fallback to invoice snapshot if deleted)
            var customer = await customerRepository.GetByIdAsync(invoice.CustomerId, ct);

            // 4. Build PDF data
            var pdfData = BuildPdfData(invoice, company, customer);

            // 5. Generate PDF
            var pdfBytes = pdfGenerator.Generate(pdfData);

            // 6. Generate filename
            var fileName = $"Faktura_{invoice.InvoiceNumber}_{invoice.InvoiceDate:yyyy-MM-dd}.pdf";

            logger.LogInformation(
                "Generated PDF for invoice {InvoiceNumber}, size: {Size} bytes",
                invoice.InvoiceNumber, pdfBytes.Length);

            return InvoicePdfResult.Succeeded(pdfBytes, fileName);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Failed to generate PDF for invoice {InvoiceId}", invoiceId);
            return InvoicePdfResult.Failed("PDF_GENERATION_ERROR", $"PDF generering fejlede: {ex.Message}");
        }
    }

    private static InvoicePdfData BuildPdfData(
        InvoiceReadModelDto invoice,
        CompanyReadModelDto company,
        CustomerReadModelDto? customer)
    {
        var lines = invoice.GetLines();

        // Group VAT by code for summary
        var vatSummary = lines
            .GroupBy(l => l.VatCode)
            .Select(g => new VatSummaryLine
            {
                VatCode = g.Key,
                VatRate = GetVatRate(g.Key),
                BasisAmount = g.Sum(l => l.AmountExVat),
                VatAmount = g.Sum(l => l.AmountVat)
            })
            .Where(v => v.VatAmount > 0 || v.BasisAmount > 0) // Include 0% VAT lines if they have basis
            .OrderByDescending(v => v.VatRate)
            .ToList();

        return new InvoicePdfData
        {
            // Company
            CompanyName = company.Name,
            CompanyCvr = company.Cvr,
            CompanyAddress = company.Address,
            CompanyPostalCode = company.PostalCode,
            CompanyCity = company.City,
            CompanyCountry = company.Country,

            // Company bank details
            CompanyBankName = company.BankName,
            CompanyBankRegNo = company.BankRegNo,
            CompanyBankAccountNo = company.BankAccountNo,
            CompanyIban = company.BankIban,
            CompanyBic = company.BankBic,

            // Customer (prefer fresh data, fallback to invoice snapshot)
            CustomerName = customer?.Name ?? invoice.CustomerName,
            CustomerCvr = customer?.Cvr,
            CustomerAddress = customer?.Address,
            CustomerPostalCode = customer?.PostalCode,
            CustomerCity = customer?.City,
            CustomerCountry = customer?.Country ?? "DK",
            CustomerNumber = invoice.CustomerNumber,

            // Invoice
            InvoiceNumber = invoice.InvoiceNumber,
            InvoiceDate = invoice.InvoiceDate.HasValue
                ? DateOnly.FromDateTime(invoice.InvoiceDate.Value)
                : DateOnly.FromDateTime(DateTime.Today),
            DueDate = invoice.DueDate.HasValue
                ? DateOnly.FromDateTime(invoice.DueDate.Value)
                : DateOnly.FromDateTime(DateTime.Today.AddDays(invoice.PaymentTermsDays)),
            PaymentTermsDays = invoice.PaymentTermsDays,
            Currency = invoice.Currency,
            Reference = invoice.Reference,
            Notes = invoice.Notes,

            // Lines
            Lines = lines.Select(l => new InvoicePdfLineItem
            {
                LineNumber = l.LineNumber,
                Description = l.Description,
                Quantity = l.Quantity,
                Unit = l.Unit,
                UnitPrice = l.UnitPrice,
                DiscountPercent = l.DiscountPercent,
                VatCode = l.VatCode,
                AmountExVat = l.AmountExVat,
                AmountVat = l.AmountVat,
                AmountTotal = l.AmountTotal
            }).ToList(),

            // Totals
            AmountExVat = invoice.AmountExVat,
            AmountVat = invoice.AmountVat,
            AmountTotal = invoice.AmountTotal,
            VatSummary = vatSummary
        };
    }

    private static decimal GetVatRate(string vatCode) => vatCode switch
    {
        "U25" or "I25" => 0.25m,
        "UEU" or "IEU" => 0m,
        "UEXP" or "IEXP" => 0m,
        "INGEN" => 0m,
        _ => 0.25m
    };
}
