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

import { db } from '@/lib/db';
import { berths } from '@/lib/db/schema/berths';
import { clients, clientAddresses, clientContacts } from '@/lib/db/schema/clients';
import { companies, companyAddresses } from '@/lib/db/schema/companies';
import { interests } from '@/lib/db/schema/interests';
import { ports } from '@/lib/db/schema/ports';
import { yachts } from '@/lib/db/schema/yachts';
import { getCountryName } from '@/lib/i18n/countries';
import { NotFoundError, ValidationError } from '@/lib/errors';

// ─── Types ────────────────────────────────────────────────────────────────────

export type EoiContext = {
  client: {
    fullName: string;
    nationality: string | null;
    primaryEmail: string | null;
    primaryPhone: string | null;
    address: { street: string; city: string; country: string } | null;
  };
  yacht: {
    name: string;
    lengthFt: string | null;
    widthFt: string | null;
    draftFt: string | null;
    lengthM: string | null;
    widthM: string | null;
    draftM: string | null;
    hullNumber: string | null;
    flag: string | null;
    yearBuilt: number | null;
  };
  company: {
    name: string;
    legalName: string | null;
    taxId: string | null;
    billingAddress: string | null;
  } | null;
  owner: {
    type: 'client' | 'company';
    name: string;
    legalName?: string;
  };
  berth: {
    mooringNumber: string;
    area: string | null;
    lengthFt: string | null;
    price: string | null;
    priceCurrency: string;
    tenureType: string;
  };
  interest: {
    stage: string;
    leadCategory: string | null;
    dateFirstContact: Date | null;
    notes: string | null;
  };
  port: {
    name: string;
    defaultCurrency: string;
  };
  date: {
    today: string;
    year: string;
  };
};

// ─── buildEoiContext ──────────────────────────────────────────────────────────

/**
 * Assembles the shared context object used by EOI generation, templates, and
 * any other flow that needs a denormalised snapshot of an interest + its
 * surrounding entities (client, yacht, berth, owner, port, etc.).
 *
 * Pure read-only: no audit logs, no socket emits, no mutations.
 *
 * Tenant-scoped: every fetch is gated by `portId`, and missing rows surface
 * as NotFoundError. Missing yacht/berth references on the interest surface as
 * ValidationError, because EOI flows cannot proceed without them.
 */
export async function buildEoiContext(interestId: string, portId: string): Promise<EoiContext> {
  // 1. Interest (tenant-scoped)
  const interest = await db.query.interests.findFirst({
    where: and(eq(interests.id, interestId), eq(interests.portId, portId)),
  });
  if (!interest) {
    throw new NotFoundError('Interest');
  }

  // 2. Yacht reference must exist on the interest
  if (!interest.yachtId) {
    throw new ValidationError('interest has no yacht');
  }

  // 3. Berth reference must exist on the interest
  if (!interest.berthId) {
    throw new ValidationError('interest has no berth');
  }

  // 2 + 3 + 4 + 9: parallelise independent reads.
  const [yacht, berth, client, port] = await Promise.all([
    db.query.yachts.findFirst({
      where: and(eq(yachts.id, interest.yachtId), eq(yachts.portId, portId)),
    }),
    db.query.berths.findFirst({
      where: and(eq(berths.id, interest.berthId), eq(berths.portId, portId)),
    }),
    db.query.clients.findFirst({
      where: and(eq(clients.id, interest.clientId), eq(clients.portId, portId)),
    }),
    db.query.ports.findFirst({
      where: eq(ports.id, portId),
    }),
  ]);

  if (!yacht) throw new NotFoundError('Yacht');
  if (!berth) throw new NotFoundError('Berth');
  if (!client) throw new NotFoundError('Client');
  if (!port) throw new NotFoundError('Port');

  // 5. Primary contacts — email + phone for the interest's client.
  const contactRows = await db
    .select({
      channel: clientContacts.channel,
      value: clientContacts.value,
      isPrimary: clientContacts.isPrimary,
      updatedAt: clientContacts.updatedAt,
    })
    .from(clientContacts)
    .where(eq(clientContacts.clientId, client.id))
    .orderBy(desc(clientContacts.isPrimary), desc(clientContacts.updatedAt));

  const firstEmail = contactRows.find((c) => c.channel === 'email');
  const firstPhone =
    contactRows.find((c) => c.channel === 'phone') ??
    contactRows.find((c) => c.channel === 'whatsapp');

  // 6. Primary address. Country is rendered as the localized name (English by
  // default for documents) from the ISO code.
  const [primaryAddress] = await db
    .select({
      streetAddress: clientAddresses.streetAddress,
      city: clientAddresses.city,
      countryIso: clientAddresses.countryIso,
    })
    .from(clientAddresses)
    .where(and(eq(clientAddresses.clientId, client.id), eq(clientAddresses.isPrimary, true)))
    .limit(1);

  const clientAddress = primaryAddress
    ? {
        street: primaryAddress.streetAddress ?? '',
        city: primaryAddress.city ?? '',
        country: primaryAddress.countryIso ? getCountryName(primaryAddress.countryIso, 'en') : '',
      }
    : null;

  // 7 + 8. Yacht owner (polymorphic) + optional company billing address.
  let ownerBlock: EoiContext['owner'];
  let companyBlock: EoiContext['company'] = null;

  if (yacht.currentOwnerType === 'client') {
    // The yacht-owning client may or may not be the same as the interest's client.
    const ownerClient =
      yacht.currentOwnerId === client.id
        ? client
        : await db.query.clients.findFirst({
            where: and(eq(clients.id, yacht.currentOwnerId), eq(clients.portId, portId)),
          });
    if (!ownerClient) throw new NotFoundError('Client');
    ownerBlock = { type: 'client', name: ownerClient.fullName };
  } else if (yacht.currentOwnerType === 'company') {
    const company = await db.query.companies.findFirst({
      where: and(eq(companies.id, yacht.currentOwnerId), eq(companies.portId, portId)),
    });
    if (!company) throw new NotFoundError('Company');

    ownerBlock = {
      type: 'company',
      name: company.name,
      ...(company.legalName ? { legalName: company.legalName } : {}),
    };

    const [companyPrimaryAddress] = await db
      .select({
        streetAddress: companyAddresses.streetAddress,
        city: companyAddresses.city,
        countryIso: companyAddresses.countryIso,
      })
      .from(companyAddresses)
      .where(and(eq(companyAddresses.companyId, company.id), eq(companyAddresses.isPrimary, true)))
      .limit(1);

    const billingAddress = companyPrimaryAddress
      ? [
          companyPrimaryAddress.streetAddress,
          companyPrimaryAddress.city,
          companyPrimaryAddress.countryIso
            ? getCountryName(companyPrimaryAddress.countryIso, 'en')
            : null,
        ]
          .filter((s): s is string => Boolean(s))
          .join(', ') || null
      : null;

    companyBlock = {
      name: company.name,
      legalName: company.legalName,
      taxId: company.taxId,
      billingAddress,
    };
  } else {
    throw new ValidationError(`unknown yacht owner type: ${String(yacht.currentOwnerType)}`);
  }

  // 10. Date.
  const now = new Date();
  const today = now.toISOString().slice(0, 10);
  const year = String(now.getFullYear());

  return {
    client: {
      fullName: client.fullName,
      nationality: client.nationalityIso ? getCountryName(client.nationalityIso, 'en') : null,
      primaryEmail: firstEmail?.value ?? null,
      primaryPhone: firstPhone?.value ?? null,
      address: clientAddress,
    },
    yacht: {
      name: yacht.name,
      lengthFt: yacht.lengthFt,
      widthFt: yacht.widthFt,
      draftFt: yacht.draftFt,
      lengthM: yacht.lengthM,
      widthM: yacht.widthM,
      draftM: yacht.draftM,
      hullNumber: yacht.hullNumber,
      flag: yacht.flag,
      yearBuilt: yacht.yearBuilt,
    },
    company: companyBlock,
    owner: ownerBlock,
    berth: {
      mooringNumber: berth.mooringNumber,
      area: berth.area,
      lengthFt: berth.lengthFt,
      price: berth.price,
      priceCurrency: berth.priceCurrency,
      tenureType: berth.tenureType,
    },
    interest: {
      stage: interest.pipelineStage,
      leadCategory: interest.leadCategory,
      dateFirstContact: interest.dateFirstContact,
      notes: interest.notes,
    },
    port: {
      name: port.name,
      defaultCurrency: port.defaultCurrency,
    },
    date: {
      today,
      year,
    },
  };
}
