/**
 * Public interest creation - extracted from `/api/public/interests/route.ts`
 * per the C.4 audit finding ("Public POST routes bypass service layer"). The
 * pre-extraction route was 346 lines of inline DB logic + audit + email
 * fan-out, which made unit testing the dedup, ownership, and address rules
 * effectively impossible without spinning up a full HTTP request fixture.
 *
 * After extraction:
 *   - The route handles HTTP concerns: rate-limit, port resolution from
 *     headers, parseBody validation, audit-log + email side-effect dispatch.
 *   - This service handles the transactional trio creation (client + yacht
 *     + interest, plus optional company + membership + address).
 *
 * The companion routes - `/api/public/website-inquiries/route.ts` (pure raw
 * capture; no entity creation) and `/api/public/residential-inquiries/route.ts`
 * (residential funnel, separate schema) - were intentionally NOT extracted
 * here. Their bodies are smaller and their concerns don't overlap with the
 * marina-funnel logic this service encapsulates.
 */
import { and, eq, isNull, sql } from 'drizzle-orm';

import { db } from '@/lib/db';
import { withTransaction } from '@/lib/db/utils';
import { interests } from '@/lib/db/schema/interests';
import { upsertInterestBerthTx } from '@/lib/services/interest-berths.service';
import { clients, clientContacts, clientAddresses } from '@/lib/db/schema/clients';
import { berths } from '@/lib/db/schema/berths';
import { yachts, yachtOwnershipHistory } from '@/lib/db/schema/yachts';
import { companies, companyMemberships } from '@/lib/db/schema/companies';
import { parsePhone } from '@/lib/i18n/phone';
import type { CountryCode } from '@/lib/i18n/countries';
import type { publicInterestSchema } from '@/lib/validators/interests';
import type { z } from 'zod';

type PublicInterestData = z.infer<typeof publicInterestSchema>;
type Tx = typeof db;

export interface CreatePublicInterestArgs {
  portId: string;
  data: PublicInterestData;
}

export interface CreatePublicInterestResult {
  interestId: string;
  clientId: string;
  yachtId: string;
  companyId: string | null;
  berthId: string | null;
  resolvedMooringNumber: string | null;
  fullName: string;
  firstName: string;
}

export async function createPublicInterest(
  args: CreatePublicInterestArgs,
): Promise<CreatePublicInterestResult> {
  const { portId, data } = args;

  // Server-side phone normalization for older website builds that post raw
  // international/national strings. Newer builds may pre-fill phoneE164/Country.
  let phoneE164 = data.phoneE164 ?? null;
  let phoneCountry: CountryCode | null = (data.phoneCountry as CountryCode | null) ?? null;
  if (!phoneE164) {
    const parsed = parsePhone(data.phone, phoneCountry ?? undefined);
    phoneE164 = parsed.e164;
    phoneCountry = parsed.country ?? phoneCountry;
  }

  const fullName =
    data.firstName && data.lastName
      ? `${data.firstName} ${data.lastName}`
      : (data.fullName ?? 'Unknown');

  const firstName = data.firstName ?? fullName.split(/\s+/)[0] ?? 'Valued Guest';

  // Resolve berth by mooring number (if provided). Read-only lookup - safe
  // to do outside the transaction.
  let berthId: string | null = null;
  let resolvedMooringNumber: string | null = data.mooringNumber ?? null;
  if (data.mooringNumber) {
    const berth = await db.query.berths.findFirst({
      where: and(eq(berths.mooringNumber, data.mooringNumber), eq(berths.portId, portId)),
    });
    if (berth) {
      berthId = berth.id;
      resolvedMooringNumber = berth.mooringNumber;
    }
  }

  // ─── Transactional trio creation ────────────────────────────────────────
  const result = await withTransaction(async (tx) => {
    // 1. Find or create client by email. The inquiry-funnel audit
    //    flagged that the previous exact match was case-sensitive -
    //    capital-letter resubmissions spawned duplicate client+yacht+
    //    interest rows. Match LOWER(value) instead so foo@x.com and
    //    Foo@X.COM dedupe to the same client.
    let clientId: string;
    const normalizedEmail = data.email.trim().toLowerCase();
    const existingContact = await tx.query.clientContacts.findFirst({
      where: and(
        eq(clientContacts.channel, 'email'),
        sql`LOWER(${clientContacts.value}) = ${normalizedEmail}`,
      ),
    });
    if (existingContact) {
      const existingClient = await tx.query.clients.findFirst({
        where: eq(clients.id, existingContact.clientId),
      });
      if (existingClient && existingClient.portId === portId) {
        clientId = existingClient.id;
        const updates: Partial<typeof clients.$inferInsert> = {};
        if (data.preferredContactMethod) {
          updates.preferredContactMethod = data.preferredContactMethod;
        }
        if (data.nationalityIso && !existingClient.nationalityIso) {
          updates.nationalityIso = data.nationalityIso;
        }
        if (Object.keys(updates).length > 0) {
          await tx.update(clients).set(updates).where(eq(clients.id, clientId));
        }
      } else {
        clientId = await createClientInTx(tx, portId, fullName, data, phoneE164, phoneCountry);
      }
    } else {
      clientId = await createClientInTx(tx, portId, fullName, data, phoneE164, phoneCountry);
    }

    // 2. Optional: upsert company + add membership
    let companyId: string | null = null;
    if (data.company) {
      const existingCompany = await tx.query.companies.findFirst({
        where: and(
          eq(companies.portId, portId),
          sql`lower(${companies.name}) = lower(${data.company.name})`,
        ),
      });
      if (existingCompany) {
        companyId = existingCompany.id;
      } else {
        const [newCompany] = await tx
          .insert(companies)
          .values({
            portId,
            name: data.company.name,
            legalName: data.company.legalName ?? null,
            taxId: data.company.taxId ?? null,
            incorporationCountryIso: data.company.incorporationCountryIso ?? null,
            incorporationSubdivisionIso: data.company.incorporationSubdivisionIso ?? null,
            status: 'active',
          })
          .returning();
        companyId = newCompany!.id;
      }

      // Add active membership only if one doesn't already exist (open row).
      const existingMembership = await tx.query.companyMemberships.findFirst({
        where: and(
          eq(companyMemberships.companyId, companyId),
          eq(companyMemberships.clientId, clientId),
          isNull(companyMemberships.endDate),
        ),
      });
      if (!existingMembership) {
        await tx.insert(companyMemberships).values({
          companyId,
          clientId,
          role: data.company.role ?? 'representative',
          startDate: new Date(),
          isPrimary: false,
        });
      }
    }

    // 3. Create yacht. Owner is the company when provided, else the client.
    const ownerType: 'client' | 'company' = companyId ? 'company' : 'client';
    const ownerId = companyId ?? clientId;
    const [newYacht] = await tx
      .insert(yachts)
      .values({
        portId,
        name: data.yacht.name,
        hullNumber: data.yacht.hullNumber ?? null,
        registration: data.yacht.registration ?? null,
        flag: data.yacht.flag ?? null,
        yearBuilt: data.yacht.yearBuilt ?? null,
        lengthFt: data.yacht.lengthFt != null ? String(data.yacht.lengthFt) : null,
        widthFt: data.yacht.widthFt != null ? String(data.yacht.widthFt) : null,
        draftFt: data.yacht.draftFt != null ? String(data.yacht.draftFt) : null,
        currentOwnerType: ownerType,
        currentOwnerId: ownerId,
        status: 'active',
      })
      .returning();
    const yachtId = newYacht!.id;

    // 3a. Open ownership_history row for the new yacht.
    await tx.insert(yachtOwnershipHistory).values({
      yachtId,
      ownerType,
      ownerId,
      startDate: new Date(),
      endDate: null,
      createdBy: 'public-submission',
    });

    // 4. Store address if provided AND no primary address exists yet.
    if (data.address && Object.values(data.address).some(Boolean)) {
      const existingAddr = await tx.query.clientAddresses.findFirst({
        where: and(eq(clientAddresses.clientId, clientId), eq(clientAddresses.isPrimary, true)),
      });
      if (!existingAddr) {
        await tx.insert(clientAddresses).values({
          clientId,
          portId,
          label: 'Primary',
          streetAddress: data.address.street ?? null,
          city: data.address.city ?? null,
          subdivisionIso: data.address.subdivisionIso ?? null,
          postalCode: data.address.postalCode ?? null,
          countryIso: data.address.countryIso ?? null,
          isPrimary: true,
        });
      }
    }

    // 5. Create interest with yachtId wired up.
    const [newInterest] = await tx
      .insert(interests)
      .values({
        portId,
        clientId,
        yachtId,
        source: 'website',
        pipelineStage: 'enquiry',
      })
      .returning();

    if (berthId) {
      // Route through the canonical junction helper (audit M20 + L27) rather
      // than a raw insert. The helper:
      //   • forces `is_in_eoi_bundle=true` for the primary berth, so the
      //     website-originated interest doesn't violate the primary↔bundle
      //     invariant that migration 0083 had to repair (audit M20);
      //   • applies the cross-port guard the raw insert bypassed (audit L27).
      await upsertInterestBerthTx(tx, newInterest!.id, berthId, {
        isPrimary: true,
        isSpecificInterest: true,
        addedBy: 'public-submission',
      });
    }

    return {
      interestId: newInterest!.id,
      clientId,
      yachtId,
      companyId,
    };
  });

  return {
    ...result,
    berthId,
    resolvedMooringNumber,
    fullName,
    firstName,
  };
}

async function createClientInTx(
  tx: Tx,
  portId: string,
  fullName: string,
  data: Pick<PublicInterestData, 'email' | 'phone' | 'preferredContactMethod' | 'nationalityIso'>,
  phoneE164: string | null,
  phoneCountry: CountryCode | null,
): Promise<string> {
  const [newClient] = await tx
    .insert(clients)
    .values({
      portId,
      fullName,
      preferredContactMethod: data.preferredContactMethod,
      nationalityIso: data.nationalityIso ?? null,
      source: 'website',
    })
    .returning();
  const clientId = newClient!.id;

  await tx.insert(clientContacts).values({
    clientId,
    channel: 'email',
    // Store lowercased so the case-insensitive dedup match above always
    // hits on subsequent submissions.
    value: data.email.trim().toLowerCase(),
    isPrimary: true,
  });

  await tx.insert(clientContacts).values({
    clientId,
    channel: 'phone',
    value: data.phone,
    valueE164: phoneE164,
    valueCountry: phoneCountry,
    isPrimary: false,
  });

  return clientId;
}
