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

import { db } from '@/lib/db';
import { clients, clientContacts } from '@/lib/db/schema/clients';
import { interests, interestBerths } from '@/lib/db/schema/interests';
import { berths, berthWaitingList, berthMaintenanceLog } from '@/lib/db/schema/berths';
import {
  getPrimaryBerth,
  getPrimaryBerthsForInterests,
} from '@/lib/services/interest-berths.service';
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 { renderPdf } from '@/lib/pdf/render';
import { resolvePortLogo } from '@/lib/pdf/brand-kit/logo';
import { BerthSpecPdf } from '@/lib/pdf/templates/berth-spec';
import { ClientSummaryPdf } from '@/lib/pdf/templates/client-summary';
import { InterestSummaryPdf } from '@/lib/pdf/templates/interest-summary';

// ─── 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 primary-berth mooring numbers (plan §3.4 - the
  // legacy interest.berth_id column has been replaced by the junction).
  const primaryBerthMap = await getPrimaryBerthsForInterests(interestList.map((i) => i.id));

  const enrichedInterests = interestList.map((i) => {
    const primary = primaryBerthMap.get(i.id);
    return {
      ...i,
      berthId: primary?.berthId ?? null,
      berthMooringNumber: primary?.mooringNumber ?? 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 logo = await resolvePortLogo(portId);

  return renderPdf(
    <ClientSummaryPdf
      portName={port?.name ?? '(port)'}
      logoBuffer={logo.buffer}
      client={{
        fullName: client.fullName,
        nationality: client.nationalityIso,
        source: client.source,
        createdAt: client.createdAt,
      }}
      contacts={contactList.map((c) => ({
        channel: c.channel,
        value: c.value,
        label: c.label,
        isPrimary: c.isPrimary,
      }))}
      yachts={ownedYachts}
      interests={enrichedInterests.map((i) => ({
        id: i.id,
        pipelineStage: i.pipelineStage,
        leadCategory: i.leadCategory,
        berthMooringNumber: i.berthMooringNumber,
        createdAt: i.createdAt,
      }))}
      activity={activity.map((a) => ({
        action: a.action,
        entityType: a.entityType,
        fieldChanged: a.fieldChanged,
        createdAt: a.createdAt,
      }))}
    />,
  );
}

// ─── 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 - "this berth is linked to this interest in any role"
  // (plan §3.4 - EXISTS against the junction).
  const linkedInterests = await db
    .select()
    .from(interests)
    .where(
      and(
        eq(interests.portId, portId),
        exists(
          db
            .select({ one: sql`1` })
            .from(interestBerths)
            .where(
              and(eq(interestBerths.interestId, interests.id), eq(interestBerths.berthId, berthId)),
            ),
        ),
      ),
    )
    .orderBy(desc(interests.updatedAt))
    .limit(20);

  // linkedInterests not currently surfaced in the PDF (the old template
  // also omitted them); kept the query to preserve the existing data
  // contract until a future revision wants to surface them.
  void linkedInterests;

  const logo = await resolvePortLogo(portId);

  return renderPdf(
    <BerthSpecPdf
      portName={port?.name ?? '(port)'}
      logoBuffer={logo.buffer}
      berth={{
        mooringNumber: berth.mooringNumber,
        area: berth.area,
        status: berth.status,
        nominalBoatSize: berth.nominalBoatSize,
        bowFacing: berth.bowFacing,
        lengthFt: berth.lengthFt,
        lengthM: berth.lengthM,
        widthFt: berth.widthFt,
        widthM: berth.widthM,
        widthIsMinimum: berth.widthIsMinimum,
        draftFt: berth.draftFt,
        draftM: berth.draftM,
        waterDepth: berth.waterDepth,
        waterDepthM: berth.waterDepthM,
        waterDepthIsMinimum: berth.waterDepthIsMinimum,
        price: berth.price,
        priceCurrency: berth.priceCurrency,
        tenureType: berth.tenureType,
        tenureYears: berth.tenureYears,
        tenureStartDate: berth.tenureStartDate,
        tenureEndDate: berth.tenureEndDate,
        mooringType: berth.mooringType,
        powerCapacity: berth.powerCapacity,
        voltage: berth.voltage,
        cleatType: berth.cleatType,
        cleatCapacity: berth.cleatCapacity,
        bollardType: berth.bollardType,
        bollardCapacity: berth.bollardCapacity,
        sidePontoon: berth.sidePontoon,
        access: berth.access,
      }}
      waitingList={enrichedWaitingList.map((w) => ({
        position: w.position,
        priority: w.priority,
        clientName: w.clientName,
        notes: w.notes,
      }))}
      maintenance={maintenance.map((m) => ({
        performedDate: m.performedDate,
        category: m.category,
        description: m.description,
        cost: m.cost,
        costCurrency: m.costCurrency,
      }))}
    />,
  );
}

// ─── 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) }),
  ]);

  // Resolve primary berth via the junction (plan §3.4).
  const primaryBerth = await getPrimaryBerth(interest.id);
  let berth = null;
  if (primaryBerth?.berthId) {
    berth = await db.query.berths.findFirst({ where: eq(berths.id, primaryBerth.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 logo = await resolvePortLogo(portId);

  return renderPdf(
    <InterestSummaryPdf
      portName={port?.name ?? '(port)'}
      logoBuffer={logo.buffer}
      interest={{
        id: interest.id,
        pipelineStage: interest.pipelineStage,
        leadCategory: interest.leadCategory,
        source: interest.source,
        eoiStatus: interest.eoiStatus,
        contractStatus: interest.contractStatus,
        depositStatus: interest.depositStatus,
        dateFirstContact: interest.dateFirstContact,
        dateLastContact: interest.dateLastContact,
        dateEoiSent: interest.dateEoiSent,
        dateEoiSigned: interest.dateEoiSigned,
        dateContractSent: interest.dateContractSent,
        dateContractSigned: interest.dateContractSigned,
        dateDepositReceived: interest.dateDepositReceived,
      }}
      client={client ? { fullName: client.fullName } : { fullName: null }}
      yacht={
        yacht
          ? {
              name: yacht.name,
              lengthFt: yacht.lengthFt,
              lengthM: yacht.lengthM,
              widthFt: yacht.widthFt,
              draftFt: yacht.draftFt,
            }
          : null
      }
      berth={
        berth
          ? {
              mooringNumber: berth.mooringNumber,
              area: berth.area,
              lengthFt: berth.lengthFt,
              price: berth.price,
              priceCurrency: berth.priceCurrency,
              status: berth.status,
            }
          : null
      }
      timeline={timeline.map((t) => ({
        createdAt: t.createdAt,
        action: t.action,
        entityType: t.entityType,
        fieldChanged: t.fieldChanged,
      }))}
    />,
  );
}
