using Microsoft.AspNetCore.Authorization;
using PowderCoating.Shared.Constants;
using Microsoft.AspNetCore.Mvc;
using PowderCoating.Core.Interfaces;
using System.IO.Compression;
using System.Text;

namespace PowderCoating.Web.Controllers;

[Authorize(Policy = AppConstants.Policies.CanManageJobs)]
public class AccountingExportController : Controller
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly ITenantContext _tenantContext;
    private readonly PowderCoating.Application.Interfaces.IAuditService _auditService;

    public AccountingExportController(IUnitOfWork unitOfWork, ITenantContext tenantContext,
        PowderCoating.Application.Interfaces.IAuditService auditService)
    {
        _unitOfWork = unitOfWork;
        _tenantContext = tenantContext;
        _auditService = auditService;
    }

    /// <summary>
    /// Displays the accounting data export page. Pre-fills the start date to the first day of
    /// the current month and the end date to today so the user sees a sensible default range
    /// without having to type dates.
    /// </summary>
    public IActionResult Index()
    {
        ViewBag.DefaultStart = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1).ToString("yyyy-MM-dd");
        ViewBag.DefaultEnd   = DateTime.Now.ToString("yyyy-MM-dd");
        return View();
    }

    /// <summary>
    /// Produces a ZIP archive containing the company's financial data for the requested date
    /// range in either QuickBooks Desktop IIF format or generic CSV format.
    /// <para>
    /// <b>QuickBooks IIF</b>: Generates three files that must be imported into QuickBooks
    /// Desktop in order (customers → invoices/payments → expenses/bills). IIF is a tab-delimited
    /// format with header rows (<c>!CUST</c>, <c>!TRNS</c>, <c>!SPL</c>, <c>!ENDTRNS</c>) that
    /// QuickBooks parses as transaction records. Account names in the IIF files must exactly match
    /// the company's QuickBooks chart of accounts, so users are advised to review account names
    /// before importing.
    /// </para>
    /// <para>
    /// <b>CSV</b>: Generates seven files covering customers, invoice headers, invoice line items,
    /// payments received, expenses, vendor bills, and bill payments. Suitable for import into any
    /// spreadsheet or accounting tool that accepts CSV.
    /// </para>
    /// The end date is extended to end-of-day (<c>AddDays(1).AddTicks(-1)</c>) so that records
    /// created at any time on the end date are included. The ZIP is streamed directly from a
    /// <c>MemoryStream</c> (not written to disk) to avoid temporary file management on the server.
    /// </summary>
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Export(DateTime startDate, DateTime endDate, string format)
    {
        var start         = startDate.Date;
        var end           = endDate.Date.AddDays(1).AddTicks(-1);
        var companyId     = _tenantContext.GetCurrentCompanyId() ?? 0;

        // ── Load data ─────────────────────────────────────────────────────────
        var invoices = (await _unitOfWork.Invoices.FindAsync(
                i => i.CompanyId == companyId && i.InvoiceDate >= start && i.InvoiceDate <= end,
                false,
                i => i.InvoiceItems,
                i => i.Payments,
                i => i.Customer))
            .OrderBy(i => i.InvoiceDate)
            .ToList();

        var expenses = (await _unitOfWork.Expenses.FindAsync(
                e => e.CompanyId == companyId && e.Date >= start && e.Date <= end,
                false,
                e => e.Vendor,
                e => e.ExpenseAccount,
                e => e.PaymentAccount))
            .OrderBy(e => e.Date)
            .ToList();

        var bills = await _unitOfWork.Bills.GetForDateRangeAsync(companyId, start, end);

        var customers = (await _unitOfWork.Customers.FindAsync(c => c.CompanyId == companyId))
            .OrderBy(c => c.CompanyName ?? c.ContactFirstName)
            .ToList();

        // ── Build ZIP ─────────────────────────────────────────────────────────
        using var ms = new MemoryStream();
        using (var zip = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true))
        {
            if (format == "quickbooks")
            {
                AddEntry(zip, "1_customers.iif",               BuildIifCustomers(customers));
                AddEntry(zip, "2_invoices_payments.iif",       BuildIifInvoicesAndPayments(invoices));
                AddEntry(zip, "3_expenses_bills.iif",          BuildIifExpensesAndBills(expenses, bills));
                AddEntry(zip, "README.txt",                    BuildReadme("QuickBooks Desktop (IIF)", startDate, endDate));
            }
            else
            {
                AddEntry(zip, "customers.csv",                 BuildCsvCustomers(customers));
                AddEntry(zip, "invoices.csv",                  BuildCsvInvoices(invoices));
                AddEntry(zip, "invoice_line_items.csv",        BuildCsvInvoiceLineItems(invoices));
                AddEntry(zip, "payments_received.csv",         BuildCsvPayments(invoices));
                AddEntry(zip, "expenses.csv",                  BuildCsvExpenses(expenses));
                AddEntry(zip, "bills.csv",                     BuildCsvBills(bills));
                AddEntry(zip, "bill_payments.csv",             BuildCsvBillPayments(bills));
                AddEntry(zip, "README.txt",                    BuildReadme("CSV", startDate, endDate));
            }
        }

        var fileName = $"accounting-export-{startDate:yyyy-MM-dd}-to-{endDate:yyyy-MM-dd}.zip";
        await _auditService.LogAsync("Exported", "AccountingExport",
            $"{format} export {startDate:yyyy-MM-dd} to {endDate:yyyy-MM-dd}",
            new { format, startDate = startDate.ToString("yyyy-MM-dd"), endDate = endDate.ToString("yyyy-MM-dd"),
                  invoiceCount = invoices.Count, billCount = bills.Count, expenseCount = expenses.Count });
        return File(ms.ToArray(), "application/zip", fileName);
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    /// <summary>
    /// Writes a UTF-8 text file entry into a <see cref="ZipArchive"/>. Using
    /// <c>CompressionLevel.Optimal</c> yields significant size reductions on repetitive CSV/IIF
    /// text without meaningful latency for export files of this scale.
    /// </summary>
    private static void AddEntry(ZipArchive zip, string name, string content)
    {
        var entry = zip.CreateEntry(name, CompressionLevel.Optimal);
        using var writer = new StreamWriter(entry.Open(), Encoding.UTF8);
        writer.Write(content);
    }

    /// <summary>
    /// Formats a single value for inclusion in a CSV field. Values containing commas, double
    /// quotes, or newlines are wrapped in double quotes and internal double quotes are escaped by
    /// doubling them (RFC 4180 compliance).
    /// </summary>
    private static string CsvVal(object? v)
    {
        if (v == null) return "";
        var s = v.ToString() ?? "";
        if (s.Contains(',') || s.Contains('"') || s.Contains('\n'))
            return $"\"{s.Replace("\"", "\"\"")}\"";
        return s;
    }

    /// <summary>
    /// Joins a set of field values into a single RFC 4180-compliant CSV row by applying
    /// <see cref="CsvVal"/> to each field and joining with commas.
    /// </summary>
    private static string CsvRow(params object?[] fields)
        => string.Join(",", fields.Select(CsvVal));

    /// <summary>
    /// Formats a date in the <c>MM/dd/yyyy</c> format required by QuickBooks IIF transaction
    /// records. QuickBooks Desktop does not accept ISO 8601 dates in IIF imports.
    /// </summary>
    private static string IifDate(DateTime d) => d.ToString("MM/dd/yyyy");

    /// <summary>
    /// Returns the display name for a customer: company name when available, otherwise
    /// first + last name concatenated. This mirrors the display logic in the UI and ensures
    /// IIF/CSV records identify the customer consistently across all export files.
    /// </summary>
    private static string CustomerName(PowderCoating.Core.Entities.Customer? c)
    {
        if (c == null) return "Unknown";
        return !string.IsNullOrWhiteSpace(c.CompanyName)
            ? c.CompanyName
            : $"{c.ContactFirstName} {c.ContactLastName}".Trim();
    }

    // ── IIF Builders ──────────────────────────────────────────────────────────

    /// <summary>
    /// Builds the QuickBooks IIF customer list file. Each customer is emitted as a <c>CUST</c>
    /// record row with the <c>!CUST</c> header. The <c>TAXABLE</c> field is the inverse of
    /// <c>IsTaxExempt</c>: a tax-exempt customer is "N" (not taxable), consistent with how
    /// QuickBooks represents taxability.
    /// </summary>
    private static string BuildIifCustomers(List<PowderCoating.Core.Entities.Customer> customers)
    {
        var sb = new StringBuilder();
        sb.AppendLine("!CUST\tNAME\tCOMPANYNAME\tFIRSTNAME\tLASTNAME\tBILLADDR1\tBILLADDR2\tPHONE1\tEMAIL\tTERMS\tTAXABLE\tLIMIT");
        foreach (var c in customers)
        {
            var name = CustomerName(c);
            var addr2 = !string.IsNullOrWhiteSpace(c.City)
                ? $"{c.City}, {c.State} {c.ZipCode}".Trim()
                : "";
            sb.Append("CUST\t");
            sb.Append($"{name}\t");
            sb.Append($"{c.CompanyName ?? ""}\t");
            sb.Append($"{c.ContactFirstName ?? ""}\t");
            sb.Append($"{c.ContactLastName ?? ""}\t");
            sb.Append($"{c.Address ?? ""}\t");
            sb.Append($"{addr2}\t");
            sb.Append($"{c.Phone ?? ""}\t");
            sb.Append($"{c.Email ?? ""}\t");
            sb.Append($"{c.PaymentTerms ?? ""}\t");
            sb.Append(c.IsTaxExempt ? "N\t" : "Y\t");
            sb.AppendLine($"{c.CreditLimit}");
        }
        return sb.ToString();
    }

    /// <summary>
    /// Builds the IIF file for invoices and their associated payments. Each invoice is emitted
    /// as an <c>INVOICE</c> transaction: a <c>TRNS</c> header row debiting Accounts Receivable,
    /// followed by <c>SPL</c> lines crediting the Sales account for each line item (amounts are
    /// negated on split lines because IIF splits use the opposite sign from the transaction
    /// header). Tax and discount lines are appended as additional split rows when applicable.
    /// Each payment is emitted as a separate <c>PAYMENT</c> transaction immediately after its
    /// invoice block. The bank account is determined by
    /// <see cref="MapPaymentMethodToAccount"/> which maps payment method to a QB account name.
    /// </summary>
    private static string BuildIifInvoicesAndPayments(List<PowderCoating.Core.Entities.Invoice> invoices)
    {
        var sb = new StringBuilder();
        sb.AppendLine("!TRNS\tTRNSTYPE\tDATE\tACCNT\tNAME\tAMOUNT\tDOCNUM\tMEMO\tDUEDATE\tTERMS");
        sb.AppendLine("!SPL\tTRNSTYPE\tDATE\tACCNT\tNAME\tAMOUNT\tDOCNUM\tMEMO\tQNTY\tPRICE\tINVITEM");
        sb.AppendLine("!ENDTRNS");

        foreach (var inv in invoices)
        {
            var cust     = CustomerName(inv.Customer);
            var date     = IifDate(inv.InvoiceDate);
            var due      = inv.DueDate.HasValue ? IifDate(inv.DueDate.Value) : date;
            var terms    = inv.Terms ?? "";

            sb.AppendLine($"TRNS\tINVOICE\t{date}\tAccounts Receivable\t{cust}\t{inv.Total}\t{inv.InvoiceNumber}\t{inv.Notes ?? ""}\t{due}\t{terms}");

            foreach (var item in inv.InvoiceItems.OrderBy(i => i.DisplayOrder))
            {
                sb.AppendLine($"SPL\tINVOICE\t{date}\tSales\t{cust}\t{-item.TotalPrice}\t{inv.InvoiceNumber}\t{item.Description}\t{item.Quantity}\t{item.UnitPrice}\t{item.Description}");
            }

            if (inv.TaxAmount > 0)
                sb.AppendLine($"SPL\tINVOICE\t{date}\tSales Tax Payable\t{cust}\t{-inv.TaxAmount}\t{inv.InvoiceNumber}\tSales Tax\t1\t{inv.TaxAmount}\tSales Tax");

            if (inv.DiscountAmount > 0)
                sb.AppendLine($"SPL\tINVOICE\t{date}\tDiscounts Given\t{cust}\t{inv.DiscountAmount}\t{inv.InvoiceNumber}\tDiscount\t1\t{-inv.DiscountAmount}\tDiscount");

            sb.AppendLine("ENDTRNS");

            // Payments against this invoice
            foreach (var pmt in inv.Payments)
            {
                var pmtDate   = IifDate(pmt.PaymentDate);
                var pmtMethod = MapPaymentMethodToAccount(pmt.PaymentMethod);
                var memo      = $"Payment for {inv.InvoiceNumber}";

                sb.AppendLine($"TRNS\tPAYMENT\t{pmtDate}\t{pmtMethod}\t{cust}\t{pmt.Amount}\t{pmt.Reference ?? ""}\t{memo}");
                sb.AppendLine($"SPL\tPAYMENT\t{pmtDate}\tAccounts Receivable\t{cust}\t{-pmt.Amount}\t{pmt.Reference ?? ""}\t{memo}");
                sb.AppendLine("ENDTRNS");
            }
        }

        return sb.ToString();
    }

    /// <summary>
    /// Builds the IIF file for direct expenses and vendor bills. Direct expenses are emitted as
    /// <c>CHECK</c> transactions (payment account → expense account), while vendor bills are
    /// emitted as <c>BILL</c> transactions (Accounts Payable → per-line expense accounts). Each
    /// bill payment is appended as a <c>BILLPMT</c> transaction after its parent bill, referencing
    /// the actual bank account stored on the <c>BillPayment</c> record (or "Checking" as a
    /// fallback when no bank account is linked).
    /// </summary>
    private static string BuildIifExpensesAndBills(
        List<PowderCoating.Core.Entities.Expense> expenses,
        List<PowderCoating.Core.Entities.Bill> bills)
    {
        var sb = new StringBuilder();
        sb.AppendLine("!TRNS\tTRNSTYPE\tDATE\tACCNT\tNAME\tAMOUNT\tDOCNUM\tMEMO");
        sb.AppendLine("!SPL\tTRNSTYPE\tDATE\tACCNT\tNAME\tAMOUNT\tDOCNUM\tMEMO");
        sb.AppendLine("!ENDTRNS");

        foreach (var exp in expenses)
        {
            var date    = IifDate(exp.Date);
            var vendor  = exp.Vendor?.CompanyName ?? "Unknown Vendor";
            var payAcct = exp.PaymentAccount?.Name ?? "Checking";
            var expAcct = exp.ExpenseAccount?.Name ?? "General Expenses";

            sb.AppendLine($"TRNS\tCHECK\t{date}\t{payAcct}\t{vendor}\t{-exp.Amount}\t{exp.ExpenseNumber}\t{exp.Memo ?? ""}");
            sb.AppendLine($"SPL\tCHECK\t{date}\t{expAcct}\t{vendor}\t{exp.Amount}\t{exp.ExpenseNumber}\t{exp.Memo ?? ""}");
            sb.AppendLine("ENDTRNS");
        }

        foreach (var bill in bills)
        {
            var date   = IifDate(bill.BillDate);
            var vendor = bill.Vendor?.CompanyName ?? "Unknown Vendor";

            sb.AppendLine($"TRNS\tBILL\t{date}\tAccounts Payable\t{vendor}\t{-bill.Total}\t{bill.BillNumber}\t{bill.Memo ?? ""}");
            foreach (var line in bill.LineItems.OrderBy(l => l.DisplayOrder))
            {
                var acct = line.Account?.Name ?? "General Expenses";
                sb.AppendLine($"SPL\tBILL\t{date}\t{acct}\t{vendor}\t{line.Amount}\t{bill.BillNumber}\t{line.Description}");
            }
            sb.AppendLine("ENDTRNS");

            foreach (var pmt in bill.Payments)
            {
                var pmtDate = IifDate(pmt.PaymentDate);
                sb.AppendLine($"TRNS\tBILLPMT\t{pmtDate}\t{pmt.BankAccount?.Name ?? "Checking"}\t{vendor}\t{-pmt.Amount}\t{pmt.PaymentNumber}\t{pmt.Memo ?? ""}");
                sb.AppendLine($"SPL\tBILLPMT\t{pmtDate}\tAccounts Payable\t{vendor}\t{pmt.Amount}\t{pmt.PaymentNumber}\t{pmt.Memo ?? ""}");
                sb.AppendLine("ENDTRNS");
            }
        }

        return sb.ToString();
    }

    // ── CSV Builders ──────────────────────────────────────────────────────────

    /// <summary>
    /// Builds a CSV file of all customers in the company with contact info, tax status, credit
    /// limit, and current balance. Suitable for import into any CRM or accounting system.
    /// </summary>
    private static string BuildCsvCustomers(List<PowderCoating.Core.Entities.Customer> customers)
    {
        var sb = new StringBuilder();
        sb.AppendLine(CsvRow("Name", "Company", "First Name", "Last Name", "Email", "Phone",
            "Address", "City", "State", "Zip", "Country", "Payment Terms",
            "Tax Exempt", "Credit Limit", "Current Balance"));
        foreach (var c in customers)
        {
            sb.AppendLine(CsvRow(
                CustomerName(c), c.CompanyName, c.ContactFirstName, c.ContactLastName,
                c.Email, c.Phone, c.Address, c.City, c.State, c.ZipCode, c.Country,
                c.PaymentTerms, c.IsTaxExempt ? "Yes" : "No",
                c.CreditLimit.ToString("F2"), c.CurrentBalance.ToString("F2")));
        }
        return sb.ToString();
    }

    /// <summary>
    /// Builds a CSV of invoice headers with subtotals, tax, discounts, and balance due. Line
    /// items are exported separately in <see cref="BuildCsvInvoiceLineItems"/> to keep the header
    /// CSV clean and allow joining by invoice number in the target system.
    /// </summary>
    private static string BuildCsvInvoices(List<PowderCoating.Core.Entities.Invoice> invoices)
    {
        var sb = new StringBuilder();
        sb.AppendLine(CsvRow("Invoice Number", "Customer", "Invoice Date", "Due Date",
            "Terms", "Status", "Sub Total", "Tax %", "Tax Amount",
            "Discount", "Total", "Amount Paid", "Balance Due", "Notes", "Customer PO"));
        foreach (var inv in invoices)
        {
            sb.AppendLine(CsvRow(
                inv.InvoiceNumber, CustomerName(inv.Customer),
                inv.InvoiceDate.ToString("yyyy-MM-dd"),
                inv.DueDate?.ToString("yyyy-MM-dd"),
                inv.Terms, inv.Status.ToString(),
                inv.SubTotal.ToString("F2"), inv.TaxPercent.ToString("F2"),
                inv.TaxAmount.ToString("F2"), inv.DiscountAmount.ToString("F2"),
                inv.Total.ToString("F2"), inv.AmountPaid.ToString("F2"),
                inv.BalanceDue.ToString("F2"), inv.Notes, inv.CustomerPO));
        }
        return sb.ToString();
    }

    /// <summary>
    /// Builds a CSV of invoice line items with invoice number and customer name repeated on each
    /// row so the file can be used standalone without requiring a join to the invoice header CSV.
    /// Items are ordered by <c>DisplayOrder</c> to preserve the sequence from the original invoice.
    /// </summary>
    private static string BuildCsvInvoiceLineItems(List<PowderCoating.Core.Entities.Invoice> invoices)
    {
        var sb = new StringBuilder();
        sb.AppendLine(CsvRow("Invoice Number", "Customer", "Invoice Date", "Description",
            "Quantity", "Unit Price", "Total Price", "Notes"));
        foreach (var inv in invoices)
        {
            var cust = CustomerName(inv.Customer);
            var date = inv.InvoiceDate.ToString("yyyy-MM-dd");
            foreach (var item in inv.InvoiceItems.OrderBy(i => i.DisplayOrder))
            {
                sb.AppendLine(CsvRow(
                    inv.InvoiceNumber, cust, date,
                    item.Description, item.Quantity.ToString("F2"),
                    item.UnitPrice.ToString("F2"), item.TotalPrice.ToString("F2"),
                    item.Notes));
            }
        }
        return sb.ToString();
    }

    /// <summary>
    /// Builds a CSV of customer payments received, one row per payment. Derived from the same
    /// invoice collection as other invoice CSVs so the data is consistent for the export date
    /// range. Payments are ordered by date within each invoice.
    /// </summary>
    private static string BuildCsvPayments(List<PowderCoating.Core.Entities.Invoice> invoices)
    {
        var sb = new StringBuilder();
        sb.AppendLine(CsvRow("Invoice Number", "Customer", "Payment Date",
            "Amount", "Payment Method", "Reference", "Notes"));
        foreach (var inv in invoices)
        {
            var cust = CustomerName(inv.Customer);
            foreach (var pmt in inv.Payments.OrderBy(p => p.PaymentDate))
            {
                sb.AppendLine(CsvRow(
                    inv.InvoiceNumber, cust,
                    pmt.PaymentDate.ToString("yyyy-MM-dd"),
                    pmt.Amount.ToString("F2"),
                    FormatPaymentMethod(pmt.PaymentMethod),
                    pmt.Reference, pmt.Notes));
            }
        }
        return sb.ToString();
    }

    /// <summary>
    /// Builds a CSV of direct expenses with vendor, account categorisation, and payment method.
    /// These represent cash/card purchases that do not go through the AP workflow.
    /// </summary>
    private static string BuildCsvExpenses(List<PowderCoating.Core.Entities.Expense> expenses)
    {
        var sb = new StringBuilder();
        sb.AppendLine(CsvRow("Expense Number", "Date", "Vendor", "Expense Account",
            "Payment Account", "Payment Method", "Amount", "Memo"));
        foreach (var e in expenses)
        {
            sb.AppendLine(CsvRow(
                e.ExpenseNumber, e.Date.ToString("yyyy-MM-dd"),
                e.Vendor?.CompanyName ?? "",
                e.ExpenseAccount?.Name ?? "",
                e.PaymentAccount?.Name ?? "",
                FormatPaymentMethod(e.PaymentMethod),
                e.Amount.ToString("F2"), e.Memo));
        }
        return sb.ToString();
    }

    /// <summary>
    /// Builds a CSV of vendor bill headers (AP entries) with status, amounts, and balance due.
    /// Line item detail is not included here to keep the file flat; use the IIF export for
    /// per-line categorisation data or query the database directly.
    /// </summary>
    private static string BuildCsvBills(List<PowderCoating.Core.Entities.Bill> bills)
    {
        var sb = new StringBuilder();
        sb.AppendLine(CsvRow("Bill Number", "Vendor Invoice #", "Vendor", "Bill Date",
            "Due Date", "Status", "Sub Total", "Tax Amount", "Total",
            "Amount Paid", "Balance Due", "Memo"));
        foreach (var b in bills)
        {
            sb.AppendLine(CsvRow(
                b.BillNumber, b.VendorInvoiceNumber, b.Vendor?.CompanyName ?? "",
                b.BillDate.ToString("yyyy-MM-dd"), b.DueDate?.ToString("yyyy-MM-dd"),
                b.Status.ToString(),
                b.SubTotal.ToString("F2"), b.TaxAmount.ToString("F2"),
                b.Total.ToString("F2"), b.AmountPaid.ToString("F2"),
                b.BalanceDue.ToString("F2"), b.Memo));
        }
        return sb.ToString();
    }

    /// <summary>
    /// Builds a CSV of vendor bill payments, one row per payment. The bill number and vendor name
    /// are repeated on each row so the file is self-contained for AP payment reconciliation
    /// without requiring a join to the bills CSV.
    /// </summary>
    private static string BuildCsvBillPayments(List<PowderCoating.Core.Entities.Bill> bills)
    {
        var sb = new StringBuilder();
        sb.AppendLine(CsvRow("Payment Number", "Bill Number", "Vendor", "Payment Date",
            "Amount", "Payment Method", "Check Number", "Memo"));
        foreach (var b in bills)
        {
            foreach (var pmt in b.Payments.OrderBy(p => p.PaymentDate))
            {
                sb.AppendLine(CsvRow(
                    pmt.PaymentNumber, b.BillNumber, b.Vendor?.CompanyName ?? "",
                    pmt.PaymentDate.ToString("yyyy-MM-dd"),
                    pmt.Amount.ToString("F2"),
                    FormatPaymentMethod(pmt.PaymentMethod),
                    pmt.CheckNumber, pmt.Memo));
            }
        }
        return sb.ToString();
    }

    // ── README ────────────────────────────────────────────────────────────────

    /// <summary>
    /// Generates a human-readable README.txt file included in the export ZIP. For QuickBooks
    /// exports it documents the required import order (customers → invoices → expenses/bills)
    /// and warns that IIF account names must match the company's QB chart of accounts exactly.
    /// For CSV exports it lists all files and their contents.
    /// </summary>
    private static string BuildReadme(string format, DateTime start, DateTime end)
    {
        var sb = new StringBuilder();
        sb.AppendLine("ACCOUNTING DATA EXPORT");
        sb.AppendLine("======================");
        sb.AppendLine($"Format:     {format}");
        sb.AppendLine($"Period:     {start:MMMM d, yyyy} – {end:MMMM d, yyyy}");
        sb.AppendLine($"Generated:  {DateTime.Now:MMMM d, yyyy h:mm tt}");
        sb.AppendLine();

        if (format.Contains("QuickBooks"))
        {
            sb.AppendLine("IMPORT ORDER (QuickBooks Desktop)");
            sb.AppendLine("---------------------------------");
            sb.AppendLine("1. File > Utilities > Import > IIF Files");
            sb.AppendLine("2. Import in this order:");
            sb.AppendLine("   a. 1_customers.iif");
            sb.AppendLine("   b. 2_invoices_payments.iif");
            sb.AppendLine("   c. 3_expenses_bills.iif");
            sb.AppendLine();
            sb.AppendLine("NOTE: Review your Chart of Accounts in QuickBooks before importing.");
            sb.AppendLine("      Account names in the IIF files must match your QB accounts exactly.");
        }
        else
        {
            sb.AppendLine("FILES INCLUDED");
            sb.AppendLine("--------------");
            sb.AppendLine("customers.csv          – Customer list with balances");
            sb.AppendLine("invoices.csv           – Invoice headers");
            sb.AppendLine("invoice_line_items.csv – Invoice line item detail");
            sb.AppendLine("payments_received.csv  – Customer payments");
            sb.AppendLine("expenses.csv           – Direct expenses");
            sb.AppendLine("bills.csv              – Vendor bills (AP)");
            sb.AppendLine("bill_payments.csv      – Payments made to vendors");
        }

        return sb.ToString();
    }

    // ── Enum helpers ──────────────────────────────────────────────────────────

    /// <summary>
    /// Maps a <see cref="PaymentMethod"/> enum value to the standard QuickBooks account name
    /// used in IIF transaction records. QuickBooks uses account names (not IDs) in IIF imports,
    /// so these strings must match the company's QB chart of accounts exactly. Cash maps to
    /// "Petty Cash" and card/digital payments map to "Merchant Account" per common QB conventions.
    /// </summary>
    private static string MapPaymentMethodToAccount(PowderCoating.Core.Enums.PaymentMethod method) => method switch
    {
        PowderCoating.Core.Enums.PaymentMethod.Cash              => "Petty Cash",
        PowderCoating.Core.Enums.PaymentMethod.Check             => "Checking",
        PowderCoating.Core.Enums.PaymentMethod.CreditDebitCard   => "Merchant Account",
        PowderCoating.Core.Enums.PaymentMethod.BankTransferACH   => "Checking",
        PowderCoating.Core.Enums.PaymentMethod.DigitalPayment    => "Merchant Account",
        _                                                          => "Undeposited Funds"
    };

    /// <summary>
    /// Formats a <see cref="PaymentMethod"/> enum value as a human-readable string for CSV
    /// output. Unlike <see cref="MapPaymentMethodToAccount"/>, this returns a descriptive label
    /// (e.g. "ACH / Bank Transfer") rather than a QB account name, suitable for display in
    /// spreadsheets and non-QB accounting tools.
    /// </summary>
    private static string FormatPaymentMethod(PowderCoating.Core.Enums.PaymentMethod method) => method switch
    {
        PowderCoating.Core.Enums.PaymentMethod.Cash              => "Cash",
        PowderCoating.Core.Enums.PaymentMethod.Check             => "Check",
        PowderCoating.Core.Enums.PaymentMethod.CreditDebitCard   => "Credit/Debit Card",
        PowderCoating.Core.Enums.PaymentMethod.BankTransferACH   => "ACH / Bank Transfer",
        PowderCoating.Core.Enums.PaymentMethod.DigitalPayment    => "Digital Payment",
        _                                                          => method.ToString()
    };
}
