import Papa from 'papaparse';
import { eq, and, gte, lte, isNull, or, ilike } from 'drizzle-orm';

import { sanitizeCsvCell } from '@/lib/csv/sanitize-csv-cell';
import { db } from '@/lib/db';
import { expenses } from '@/lib/db/schema/financial';
import { ports } from '@/lib/db/schema/ports';
import { renderPdf } from '@/lib/pdf/render';
import { resolvePortLogo } from '@/lib/pdf/brand-kit/logo';
import { ParentCompanyExpensePdf } from '@/lib/pdf/templates/parent-company-expense';
import { getRate } from '@/lib/services/currency';
import { logger } from '@/lib/logger';
import type { ListExpensesInput } from '@/lib/validators/expenses';

async function fetchAllExpenses(portId: string, query: ListExpensesInput) {
  const conditions: ReturnType<typeof eq>[] = [
    eq(expenses.portId, portId) as ReturnType<typeof eq>,
  ];

  if (!query.includeArchived) {
    conditions.push(isNull(expenses.archivedAt) as unknown as ReturnType<typeof eq>);
  }
  if (query.category) {
    conditions.push(eq(expenses.category, query.category) as ReturnType<typeof eq>);
  }
  if (query.paymentStatus) {
    conditions.push(eq(expenses.paymentStatus, query.paymentStatus) as ReturnType<typeof eq>);
  }
  if (query.currency) {
    conditions.push(eq(expenses.currency, query.currency) as ReturnType<typeof eq>);
  }
  if (query.payer) {
    conditions.push(eq(expenses.payer, query.payer) as ReturnType<typeof eq>);
  }
  if (query.dateFrom) {
    conditions.push(
      gte(expenses.expenseDate, new Date(query.dateFrom)) as unknown as ReturnType<typeof eq>,
    );
  }
  if (query.dateTo) {
    conditions.push(
      lte(expenses.expenseDate, new Date(query.dateTo)) as unknown as ReturnType<typeof eq>,
    );
  }
  if (query.search) {
    conditions.push(
      or(
        ilike(expenses.establishmentName, `%${query.search}%`),
        ilike(expenses.description, `%${query.search}%`),
      ) as unknown as ReturnType<typeof eq>,
    );
  }

  return db
    .select()
    .from(expenses)
    .where(and(...conditions));
}

export async function exportCsv(portId: string, query: ListExpensesInput): Promise<string> {
  const rows = await fetchAllExpenses(portId, query);

  // papaparse handles all the CSV edge cases (commas in fields, embedded
  // quotes, newlines, BOM) that the hand-rolled escape-and-quote version
  // missed. Keyed objects let us define column order via `columns` and
  // get matching headers for free.
  // Neutralize spreadsheet formula triggers on user-controlled free-text
  // fields before papaparse serializes them (papaparse has no built-in
  // CSV-injection guard). Numeric/derived columns are not attacker-seeded
  // free text, so they keep their native values and formatting.
  return Papa.unparse(
    rows.map((r) => ({
      Date: r.expenseDate ? new Date(r.expenseDate).toISOString().split('T')[0] : '',
      Establishment: sanitizeCsvCell(r.establishmentName ?? ''),
      Category: sanitizeCsvCell(r.category ?? ''),
      Amount: r.amount,
      Currency: r.currency,
      'Amount USD': r.amountUsd ?? 'N/A',
      'Payment Status': sanitizeCsvCell(r.paymentStatus ?? ''),
      'Payment Method': sanitizeCsvCell(r.paymentMethod ?? ''),
      Description: sanitizeCsvCell(r.description ?? ''),
    })),
    {
      columns: [
        'Date',
        'Establishment',
        'Category',
        'Amount',
        'Currency',
        'Amount USD',
        'Payment Status',
        'Payment Method',
        'Description',
      ],
    },
  );
}

/**
 * Legacy text-only PDF export superseded by the streaming
 * `streamExpensePdf` in `src/lib/services/expense-pdf.service.ts`.
 * The new service supports receipt-image embedding, sharp resize for
 * stupidly-large attachments, and streaming output so hundreds of
 * expenses no longer OOM the process.
 *
 * See `src/app/api/v1/expenses/export/pdf/route.ts` for the live route.
 */

export async function exportParentCompany(
  portId: string,
  query: ListExpensesInput,
): Promise<Buffer> {
  // BR-043: Convert all amounts to EUR, add 5% management fee
  const rows = await fetchAllExpenses(portId, query);
  const eurRate = await getRate('USD', 'EUR');

  if (!eurRate) {
    logger.warn('EUR rate unavailable for parent company export, using 1:1 fallback');
  }

  const rate = eurRate ?? 1;

  const convertedRows = rows.map((r) => {
    const amountUsd = r.amountUsd ? Number(r.amountUsd) : Number(r.amount);
    const amountEur = Number((amountUsd * rate).toFixed(2));
    return {
      date: r.expenseDate ? (new Date(r.expenseDate).toISOString().split('T')[0] ?? '') : '',
      establishment: r.establishmentName ?? '-',
      category: r.category ?? '-',
      amountEur,
    };
  });

  const subtotal = convertedRows.reduce((sum, r) => sum + r.amountEur, 0);
  const fee = Number((subtotal * 0.05).toFixed(2));
  const total = Number((subtotal + fee).toFixed(2));

  const [port, logo] = await Promise.all([
    db.query.ports.findFirst({ where: eq(ports.id, portId) }),
    resolvePortLogo(portId),
  ]);
  if (!port) {
    throw new Error(`Cannot render expense export: port ${portId} not found.`);
  }

  return renderPdf(
    <ParentCompanyExpensePdf
      portName={port.name}
      logoBuffer={logo.buffer}
      rows={convertedRows}
      subtotal={subtotal}
      managementFee={fee}
      total={total}
      dateFrom={query.dateFrom}
      dateTo={query.dateTo}
      rateAvailable={Boolean(eurRate)}
    />,
  );
}
