import { and, desc, eq, inArray, isNull, or } from 'drizzle-orm';

import { db } from '@/lib/db';
import { clients, clientContacts } from '@/lib/db/schema/clients';
import { interests } from '@/lib/db/schema/interests';
import { berths, berthWaitingList, berthMaintenanceLog } from '@/lib/db/schema/berths';
import { yachts } from '@/lib/db/schema/yachts';
import { companyMemberships } from '@/lib/db/schema/companies';
import { auditLogs } from '@/lib/db/schema/system';
import { ports } from '@/lib/db/schema/ports';
import { NotFoundError } from '@/lib/errors';
import { generatePdf } from '@/lib/pdf/generate';
import {
  clientSummaryTemplate,
  buildClientSummaryInputs,
} from '@/lib/pdf/templates/client-summary-template';
import { berthSpecTemplate, buildBerthSpecInputs } from '@/lib/pdf/templates/berth-spec-template';
import {
  interestSummaryTemplate,
  buildInterestSummaryInputs,
} from '@/lib/pdf/templates/interest-summary-template';

// ─── Export Client PDF ────────────────────────────────────────────────────────

export async function exportClientPdf(clientId: string, portId: string): Promise<Uint8Array> {
  const client = await db.query.clients.findFirst({
    where: eq(clients.id, clientId),
  });

  if (!client || client.portId !== portId) {
    throw new NotFoundError('Client');
  }

  const [contactList, port] = await Promise.all([
    db.query.clientContacts.findMany({
      where: eq(clientContacts.clientId, clientId),
      orderBy: (t, { desc }) => [desc(t.isPrimary), desc(t.createdAt)],
    }),
    db.query.ports.findFirst({ where: eq(ports.id, portId) }),
  ]);

  // Fetch last 20 interests for this client in this port
  const interestList = await db
    .select()
    .from(interests)
    .where(and(eq(interests.clientId, clientId), eq(interests.portId, portId)))
    .orderBy(desc(interests.updatedAt))
    .limit(20);

  // Fetch last 20 audit logs for this client
  const activity = await db
    .select()
    .from(auditLogs)
    .where(
      and(
        eq(auditLogs.portId, portId),
        eq(auditLogs.entityType, 'client'),
        eq(auditLogs.entityId, clientId),
      ),
    )
    .orderBy(desc(auditLogs.createdAt))
    .limit(20);

  // Enrich interests with berth mooring numbers
  const berthIds = interestList.map((i) => i.berthId).filter(Boolean) as string[];

  let berthsMap: Record<string, string> = {};
  if (berthIds.length > 0) {
    const berthRows = await db
      .select({ id: berths.id, mooringNumber: berths.mooringNumber })
      .from(berths)
      .where(inArray(berths.id, berthIds));
    berthsMap = Object.fromEntries(berthRows.map((b) => [b.id, b.mooringNumber]));
  }

  const enrichedInterests = interestList.map((i) => ({
    ...i,
    berthMooringNumber: i.berthId ? (berthsMap[i.berthId] ?? null) : null,
  }));

  // Yachts owned by the client directly OR by a company they're an active
  // member of. Active membership = no end date.
  const memberCompanies = await db
    .select({ companyId: companyMemberships.companyId })
    .from(companyMemberships)
    .where(and(eq(companyMemberships.clientId, clientId), isNull(companyMemberships.endDate)));
  const companyIds = memberCompanies.map((m) => m.companyId);

  const ownerConditions = [
    and(eq(yachts.currentOwnerType, 'client'), eq(yachts.currentOwnerId, clientId))!,
  ];
  if (companyIds.length > 0) {
    ownerConditions.push(
      and(eq(yachts.currentOwnerType, 'company'), inArray(yachts.currentOwnerId, companyIds))!,
    );
  }

  const ownedYachts = await db
    .select({
      name: yachts.name,
      lengthFt: yachts.lengthFt,
      widthFt: yachts.widthFt,
      draftFt: yachts.draftFt,
      lengthM: yachts.lengthM,
      widthM: yachts.widthM,
      draftM: yachts.draftM,
    })
    .from(yachts)
    .where(and(eq(yachts.portId, portId), isNull(yachts.archivedAt), or(...ownerConditions)));

  const inputs = buildClientSummaryInputs(
    client,
    contactList,
    ownedYachts,
    enrichedInterests,
    activity,
    port ?? {},
  );

  return generatePdf(clientSummaryTemplate, [inputs]);
}

// ─── Export Berth PDF ─────────────────────────────────────────────────────────

export async function exportBerthPdf(berthId: string, portId: string): Promise<Uint8Array> {
  const berth = await db.query.berths.findFirst({
    where: eq(berths.id, berthId),
  });

  if (!berth || berth.portId !== portId) {
    throw new NotFoundError('Berth');
  }

  const port = await db.query.ports.findFirst({ where: eq(ports.id, portId) });

  // Waiting list with client names
  const waitingListRows = await db
    .select({
      id: berthWaitingList.id,
      position: berthWaitingList.position,
      priority: berthWaitingList.priority,
      notes: berthWaitingList.notes,
      clientId: berthWaitingList.clientId,
    })
    .from(berthWaitingList)
    .where(eq(berthWaitingList.berthId, berthId))
    .orderBy(berthWaitingList.position);

  const clientIds = waitingListRows.map((w) => w.clientId);
  let clientsMap: Record<string, string> = {};
  if (clientIds.length > 0) {
    const clientRows = await db
      .select({ id: clients.id, fullName: clients.fullName })
      .from(clients)
      .where(inArray(clients.id, clientIds));
    clientsMap = Object.fromEntries(clientRows.map((c) => [c.id, c.fullName]));
  }

  const enrichedWaitingList = waitingListRows.map((w) => ({
    ...w,
    clientName: clientsMap[w.clientId] ?? 'Unknown',
  }));

  // Maintenance log (last 20)
  const maintenance = await db
    .select()
    .from(berthMaintenanceLog)
    .where(eq(berthMaintenanceLog.berthId, berthId))
    .orderBy(desc(berthMaintenanceLog.performedDate))
    .limit(20);

  // Linked interests
  const linkedInterests = await db
    .select()
    .from(interests)
    .where(and(eq(interests.berthId, berthId), eq(interests.portId, portId)))
    .orderBy(desc(interests.updatedAt))
    .limit(20);

  const inputs = buildBerthSpecInputs(
    berth,
    enrichedWaitingList,
    maintenance,
    linkedInterests,
    port ?? {},
  );

  return generatePdf(berthSpecTemplate, [inputs]);
}

// ─── Export Interest PDF ──────────────────────────────────────────────────────

export async function exportInterestPdf(interestId: string, portId: string): Promise<Uint8Array> {
  const interest = await db.query.interests.findFirst({
    where: eq(interests.id, interestId),
  });

  if (!interest || interest.portId !== portId) {
    throw new NotFoundError('Interest');
  }

  const [client, port] = await Promise.all([
    db.query.clients.findFirst({ where: eq(clients.id, interest.clientId) }),
    db.query.ports.findFirst({ where: eq(ports.id, portId) }),
  ]);

  let berth = null;
  if (interest.berthId) {
    berth = await db.query.berths.findFirst({ where: eq(berths.id, interest.berthId) });
  }

  let yacht = null;
  if (interest.yachtId) {
    yacht = await db.query.yachts.findFirst({ where: eq(yachts.id, interest.yachtId) });
  }

  // Audit timeline (last 20 events for this interest)
  const timeline = await db
    .select()
    .from(auditLogs)
    .where(
      and(
        eq(auditLogs.portId, portId),
        eq(auditLogs.entityType, 'interest'),
        eq(auditLogs.entityId, interestId),
      ),
    )
    .orderBy(desc(auditLogs.createdAt))
    .limit(20);

  const inputs = buildInterestSummaryInputs(
    interest,
    client ?? {},
    yacht ?? null,
    berth ?? null,
    timeline,
    port ?? {},
  );

  return generatePdf(interestSummaryTemplate, [inputs]);
}
