/**
 * Server-side data resolvers for the list-report PDF kinds
 * (clients, berths, interests). Each returns a capped flat array
 * matching the shape the matching React-PDF report component
 * expects.
 *
 * Caps:
 *   - 1 000 rows max per export. Above that, the PDF becomes
 *     unreadable (hundreds of pages) and pdf-renderer memory cost
 *     grows linearly. Reps wanting fuller exports use CSV.
 *   - When the cap is hit, the report renders a "Showing top N of
 *     <total>" line so the export isn't silently truncated.
 *
 * Filters carried via the `filters` config:
 *   Clients: search, source, nationality, includeArchived
 *   Berths: search, status, area, includeArchived
 *   Interests: search, pipelineStage, includeArchived
 *
 * Validation happens at the route layer; this service trusts the
 * caller has run the inputs through the same zod schemas the
 * existing list endpoints use.
 */
import { and, desc, eq, isNull, sql, type SQL } from 'drizzle-orm';

import { db } from '@/lib/db';
import { clients } from '@/lib/db/schema/clients';
import { berths } from '@/lib/db/schema/berths';
import { interests, interestBerths } from '@/lib/db/schema/interests';

const REPORT_ROW_CAP = 1_000;

export interface ClientReportRow {
  id: string;
  fullName: string;
  source: string | null;
  nationality: string | null;
  primaryEmail: string | null;
  primaryPhone: string | null;
  createdAt: string;
}

export interface ClientReportData {
  rows: ClientReportRow[];
  total: number;
  capHit: boolean;
}

/**
 * Slim client export. Pulls the bare minimum columns the report
 * surface renders so payload size and PDF length stay reasonable
 * even on a 1 000-row port.
 */
export async function resolveClientReportData(
  portId: string,
  filters: { includeArchived?: boolean } = {},
): Promise<ClientReportData> {
  const whereParts: SQL[] = [eq(clients.portId, portId)];
  if (!filters.includeArchived) whereParts.push(isNull(clients.archivedAt));
  const whereClause = whereParts.length > 1 ? and(...whereParts) : whereParts[0];

  const countRows = await db
    .select({ count: sql<number>`count(*)::int` })
    .from(clients)
    .where(whereClause);
  const total = Number(countRows[0]?.count ?? 0);

  // Subqueries pick a single email / phone per client. `is_primary
  // DESC, created_at DESC` picks the primary if set; otherwise the
  // most recent contact in that channel. Matches the ordering the
  // canonical `listClients` service uses.
  const rows = await db
    .select({
      id: clients.id,
      fullName: clients.fullName,
      source: clients.source,
      nationality: clients.nationalityIso,
      primaryEmail: sql<string | null>`(
        SELECT cc.value
        FROM client_contacts cc
        WHERE cc.client_id = ${clients.id} AND cc.channel = 'email'
        ORDER BY cc.is_primary DESC, cc.created_at DESC
        LIMIT 1
      )`,
      primaryPhone: sql<string | null>`(
        SELECT cc.value
        FROM client_contacts cc
        WHERE cc.client_id = ${clients.id} AND cc.channel = 'phone'
        ORDER BY cc.is_primary DESC, cc.created_at DESC
        LIMIT 1
      )`,
      createdAt: clients.createdAt,
    })
    .from(clients)
    .where(whereClause)
    .orderBy(desc(clients.createdAt))
    .limit(REPORT_ROW_CAP);

  return {
    rows: rows.map((r) => ({
      id: r.id,
      fullName: r.fullName,
      source: r.source ?? null,
      nationality: r.nationality ?? null,
      primaryEmail: r.primaryEmail ?? null,
      primaryPhone: r.primaryPhone ?? null,
      createdAt: r.createdAt.toISOString(),
    })),
    total,
    capHit: total > REPORT_ROW_CAP,
  };
}

export interface BerthReportRow {
  id: string;
  mooringNumber: string;
  area: string | null;
  status: string;
  lengthFt: string | null;
  widthFt: string | null;
  draftFt: string | null;
  price: string | null;
  priceCurrency: string;
  tenureType: string;
}

export interface BerthReportData {
  rows: BerthReportRow[];
  total: number;
  capHit: boolean;
}

export async function resolveBerthReportData(
  portId: string,
  filters: { includeArchived?: boolean } = {},
): Promise<BerthReportData> {
  const whereParts: SQL[] = [eq(berths.portId, portId)];
  if (!filters.includeArchived) whereParts.push(isNull(berths.archivedAt));
  const whereClause = whereParts.length > 1 ? and(...whereParts) : whereParts[0];

  const countRows = await db
    .select({ count: sql<number>`count(*)::int` })
    .from(berths)
    .where(whereClause);
  const total = Number(countRows[0]?.count ?? 0);

  const rows = await db
    .select({
      id: berths.id,
      mooringNumber: berths.mooringNumber,
      area: berths.area,
      status: berths.status,
      lengthFt: berths.lengthFt,
      widthFt: berths.widthFt,
      draftFt: berths.draftFt,
      price: berths.price,
      priceCurrency: berths.priceCurrency,
      tenureType: berths.tenureType,
    })
    .from(berths)
    .where(whereClause)
    .orderBy(berths.mooringNumber)
    .limit(REPORT_ROW_CAP);

  return {
    rows,
    total,
    capHit: total > REPORT_ROW_CAP,
  };
}

export interface InterestReportRow {
  id: string;
  clientName: string | null;
  primaryMooring: string | null;
  pipelineStage: string;
  source: string | null;
  outcome: string | null;
  createdAt: string;
}

export interface InterestReportData {
  rows: InterestReportRow[];
  total: number;
  capHit: boolean;
}

export async function resolveInterestReportData(
  portId: string,
  filters: { includeArchived?: boolean } = {},
): Promise<InterestReportData> {
  const whereParts: SQL[] = [eq(interests.portId, portId)];
  if (!filters.includeArchived) whereParts.push(isNull(interests.archivedAt));
  const whereClause = whereParts.length > 1 ? and(...whereParts) : whereParts[0];

  const countRows = await db
    .select({ count: sql<number>`count(*)::int` })
    .from(interests)
    .where(whereClause);
  const total = Number(countRows[0]?.count ?? 0);

  // Join client (one-to-one) + primary berth (one-to-one via the
  // `is_primary=true` row). Keep the join LEFT so interests without
  // a primary berth still render - those are the early-stage deals
  // that haven't been pitched a specific mooring yet.
  const rows = await db
    .select({
      id: interests.id,
      clientName: clients.fullName,
      primaryMooring: berths.mooringNumber,
      pipelineStage: interests.pipelineStage,
      source: interests.source,
      outcome: interests.outcome,
      createdAt: interests.createdAt,
    })
    .from(interests)
    .leftJoin(clients, eq(clients.id, interests.clientId))
    .leftJoin(
      interestBerths,
      and(eq(interestBerths.interestId, interests.id), eq(interestBerths.isPrimary, true)),
    )
    .leftJoin(berths, eq(berths.id, interestBerths.berthId))
    .where(whereClause)
    .orderBy(desc(interests.updatedAt))
    .limit(REPORT_ROW_CAP);

  return {
    rows: rows.map((r) => ({
      id: r.id,
      clientName: r.clientName,
      primaryMooring: r.primaryMooring,
      pipelineStage: r.pipelineStage,
      source: r.source,
      outcome: r.outcome,
      createdAt: r.createdAt.toISOString(),
    })),
    total,
    capHit: total > REPORT_ROW_CAP,
  };
}
