/**
 * Custom-report entity registry — server query layer.
 *
 * The custom builder is the catch-all for slices the four canonical
 * reports don't cover — pick an entity, pick columns, optionally
 * filter by date, get a CSV. v1 ships with the four highest-value
 * entities (clients, interests, berths, tenancies); the remaining six
 * from the launch-readiness scope (companies, yachts, invoices,
 * payments, deals, sends) layer in as their schemas are wired.
 *
 * This module is SERVER-ONLY: it pulls in `@/lib/db` + drizzle to run
 * the underlying queries. The client-safe metadata (entity keys,
 * column allowlists, labels/descriptions, the filter/column type
 * contracts) lives in `registry-meta.ts` and is imported here. Client
 * components (e.g. the column-picker UI) MUST import from
 * `registry-meta.ts`, never this file, or they drag the DB layer into
 * the browser bundle.
 *
 * Each entity defines:
 *   - `columns`: an allowlist of column keys + human labels (sourced
 *     from `registry-meta.ts`). The allowlist gates which fields a rep
 *     can pull into a CSV, so PII columns can be opt-in per role later.
 *   - `runQuery`: a Drizzle select that joins whatever the columns
 *     need, applies the port filter + optional date range, and
 *     returns raw rows.
 *
 * Adding a new entity:
 *   1. Append it to ENTITY_KEYS (in registry-meta.ts).
 *   2. Add its column array + ENTITY_META entry in registry-meta.ts.
 *   3. Add the matching `runQuery` + ENTITY_REGISTRY entry here.
 *   4. The UI's entity-picker reads ENTITY_META directly.
 */

import { and, asc, desc, eq, gte, lte, sql, type SQL } from 'drizzle-orm';

import { db } from '@/lib/db';
import { berths } from '@/lib/db/schema/berths';
import { clients } from '@/lib/db/schema/clients';
import { interests, interestBerths } from '@/lib/db/schema/interests';
import { berthTenancies as tenancies } from '@/lib/db/schema/tenancies';
import { STAGE_LABELS, type PipelineStage } from '@/lib/constants';

import {
  ENTITY_KEYS,
  ENTITY_META,
  type ColumnDefinition,
  type CustomEntityMeta,
  type CustomFilter,
  type EntityKey,
} from './registry-meta';

// Re-export the client-safe contracts so existing SERVER imports of
// this module keep working unchanged.
export { ENTITY_KEYS };
export type { ColumnDefinition, CustomFilter, EntityKey };

/**
 * Full server-side entity definition: the client-safe metadata plus
 * the server-only `runQuery`.
 */
export interface CustomEntityDefinition extends CustomEntityMeta {
  /** Execute the underlying query and return raw rows keyed by column
   *  key. The runner is responsible for the joins + port scoping;
   *  callers only pass which columns they want + the filter. */
  runQuery: (input: {
    portId: string;
    columns: string[];
    filter: CustomFilter;
  }) => Promise<Array<Record<string, unknown>>>;
}

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

function applyDateRange(column: ReturnType<typeof sql<Date>>, filter: CustomFilter): SQL[] {
  const conds: SQL[] = [];
  if (filter.from) conds.push(gte(column as never, filter.from));
  if (filter.to) conds.push(lte(column as never, filter.to));
  return conds;
}

// ─── Clients ─────────────────────────────────────────────────────────────────

async function runClientsQuery({
  portId,
  filter,
}: {
  portId: string;
  columns: string[];
  filter: CustomFilter;
}): Promise<Array<Record<string, unknown>>> {
  const conds = [eq(clients.portId, portId), ...applyDateRange(clients.createdAt as never, filter)];
  const rows = await db
    .select({
      fullName: clients.fullName,
      nationalityIso: clients.nationalityIso,
      preferredLanguage: clients.preferredLanguage,
      preferredContactMethod: clients.preferredContactMethod,
      source: clients.source,
      createdAt: clients.createdAt,
      archivedAt: clients.archivedAt,
    })
    .from(clients)
    .where(and(...conds))
    .orderBy(asc(clients.fullName))
    .limit(10_000);
  return rows.map((r) => ({ ...r }));
}

// ─── Interests ───────────────────────────────────────────────────────────────

async function runInterestsQuery({
  portId,
  filter,
}: {
  portId: string;
  columns: string[];
  filter: CustomFilter;
}): Promise<Array<Record<string, unknown>>> {
  const conds = [
    eq(interests.portId, portId),
    ...applyDateRange(interests.createdAt as never, filter),
  ];
  const rows = await db
    .select({
      clientName: clients.fullName,
      primaryBerth: berths.mooringNumber,
      pipelineStage: interests.pipelineStage,
      leadCategory: interests.leadCategory,
      outcome: interests.outcome,
      source: interests.source,
      depositExpectedAmount: interests.depositExpectedAmount,
      depositExpectedCurrency: interests.depositExpectedCurrency,
      dateFirstContact: interests.dateFirstContact,
      dateLastContact: interests.dateLastContact,
      createdAt: interests.createdAt,
    })
    .from(interests)
    .innerJoin(clients, eq(interests.clientId, clients.id))
    .leftJoin(
      interestBerths,
      and(eq(interestBerths.interestId, interests.id), eq(interestBerths.isPrimary, true)),
    )
    .leftJoin(berths, eq(interestBerths.berthId, berths.id))
    .where(and(...conds))
    .orderBy(desc(interests.createdAt))
    .limit(10_000);
  return rows.map((r) => ({
    ...r,
    // Re-label stage to the human form so the CSV is readable;
    // analysts can still join back via the raw enum on display.
    pipelineStage: r.pipelineStage
      ? (STAGE_LABELS[r.pipelineStage as PipelineStage] ?? r.pipelineStage)
      : null,
  }));
}

// ─── Berths ──────────────────────────────────────────────────────────────────

async function runBerthsQuery({
  portId,
  filter,
}: {
  portId: string;
  columns: string[];
  filter: CustomFilter;
}): Promise<Array<Record<string, unknown>>> {
  const conds = [eq(berths.portId, portId), ...applyDateRange(berths.createdAt as never, filter)];
  const rows = await db
    .select({
      mooringNumber: berths.mooringNumber,
      area: berths.area,
      status: berths.status,
      length: berths.lengthM,
      width: berths.widthM,
      draft: berths.draftM,
      price: berths.price,
      priceCurrency: berths.priceCurrency,
      createdAt: berths.createdAt,
    })
    .from(berths)
    .where(and(...conds))
    .orderBy(asc(berths.mooringNumber))
    .limit(10_000);
  return rows.map((r) => ({ ...r }));
}

// ─── Tenancies ───────────────────────────────────────────────────────────────

async function runTenanciesQuery({
  portId,
  filter,
}: {
  portId: string;
  columns: string[];
  filter: CustomFilter;
}): Promise<Array<Record<string, unknown>>> {
  const conds = [
    eq(tenancies.portId, portId),
    ...applyDateRange(tenancies.createdAt as never, filter),
  ];
  const rows = await db
    .select({
      clientName: clients.fullName,
      mooringNumber: berths.mooringNumber,
      tenureType: tenancies.tenureType,
      startDate: tenancies.startDate,
      endDate: tenancies.endDate,
      status: tenancies.status,
      createdAt: tenancies.createdAt,
    })
    .from(tenancies)
    .leftJoin(clients, eq(tenancies.clientId, clients.id))
    .leftJoin(berths, eq(tenancies.berthId, berths.id))
    .where(and(...conds))
    .orderBy(desc(tenancies.startDate))
    .limit(10_000);
  return rows.map((r) => ({ ...r }));
}

// ─── Registry ────────────────────────────────────────────────────────────────

const RUN_QUERIES: Record<EntityKey, CustomEntityDefinition['runQuery']> = {
  clients: runClientsQuery,
  interests: runInterestsQuery,
  berths: runBerthsQuery,
  tenancies: runTenanciesQuery,
};

/**
 * Full server registry: client-safe metadata (from `registry-meta.ts`)
 * composed with the matching `runQuery` for each entity.
 */
export const ENTITY_REGISTRY: Record<EntityKey, CustomEntityDefinition> = ENTITY_KEYS.reduce(
  (acc, key) => {
    acc[key] = { ...ENTITY_META[key], runQuery: RUN_QUERIES[key] };
    return acc;
  },
  {} as Record<EntityKey, CustomEntityDefinition>,
);
