import { NextResponse } from 'next/server';
import { and, eq, inArray } from 'drizzle-orm';

import type { AuthContext } from '@/lib/api/helpers';
import { db } from '@/lib/db';
import { clients, clientContacts } from '@/lib/db/schema/clients';
import { interests } from '@/lib/db/schema/interests';
import { errorResponse } from '@/lib/errors';
import { findClientMatches, type MatchCandidate } from '@/lib/dedup/find-matches';
import { normalizeEmail, normalizeName, normalizePhone } from '@/lib/dedup/normalize';
import type { CountryCode } from '@/lib/i18n/countries';

/**
 * GET /api/v1/clients/match-candidates
 *
 * Query parameters (any combination):
 *   email      Free-text email; gets normalized server-side.
 *   phone      Free-text phone; gets normalized to E.164 server-side.
 *   name       Free-text full name; used for surname-token blocking.
 *   country    Optional ISO country hint (default: AI for Port Nimara).
 *
 * Returns the top candidates that scored above the soft-warn threshold,
 * each with a small client summary the form's suggestion card can
 * render. Confidence tiers and rules are applied server-side from the
 * port's `system_settings` (when wired) or sensible defaults otherwise.
 *
 * Used by `useDedupSuggestion` in the new-client form. Debounced on
 * the client; this endpoint must be cheap (single port pool fetch +
 * an in-memory dedup pass).
 */
export async function getMatchCandidatesHandler(
  req: Request,
  ctx: AuthContext,
): Promise<NextResponse> {
  try {
    const url = new URL(req.url);
    const rawEmail = url.searchParams.get('email');
    const rawPhone = url.searchParams.get('phone');
    const rawName = url.searchParams.get('name');
    const country = (url.searchParams.get('country') ?? 'AI') as CountryCode;

    const email = rawEmail ? normalizeEmail(rawEmail) : null;
    const phoneResult = rawPhone ? normalizePhone(rawPhone, country) : null;
    const nameResult = rawName ? normalizeName(rawName) : null;

    // If the caller didn't give us anything useful to match on, return empty
    // - short-circuit rather than scan every client for nothing.
    if (!email && !phoneResult?.e164 && !nameResult?.surnameToken) {
      return NextResponse.json({ data: [] });
    }

    // Build the input candidate.
    const input: MatchCandidate = {
      id: '__incoming__',
      fullName: nameResult?.display ?? null,
      surnameToken: nameResult?.surnameToken ?? null,
      emails: email ? [email] : [],
      phonesE164: phoneResult?.e164 ? [phoneResult.e164] : [],
      countryIso: country,
    };

    // Fetch the live pool for this port. We keep this O(N) over clients
    // since the dedup library does its own blocking; for ports with
    // thousands of clients we can later restrict by surname-token /
    // contact lookups, but for current scale the simple full-pool fetch
    // is fine.
    const liveClients = await db
      .select({
        id: clients.id,
        fullName: clients.fullName,
        nationalityIso: clients.nationalityIso,
        archivedAt: clients.archivedAt,
      })
      .from(clients)
      .where(and(eq(clients.portId, ctx.portId)));

    if (liveClients.length === 0) {
      return NextResponse.json({ data: [] });
    }

    const clientIds = liveClients.map((c) => c.id);
    const contactRows = await db
      .select({
        clientId: clientContacts.clientId,
        channel: clientContacts.channel,
        value: clientContacts.value,
        valueE164: clientContacts.valueE164,
      })
      .from(clientContacts)
      .where(inArray(clientContacts.clientId, clientIds));

    // Group contacts by client for the candidate map.
    const emailsByClient = new Map<string, string[]>();
    const phonesByClient = new Map<string, string[]>();
    for (const c of contactRows) {
      if (c.channel === 'email') {
        const arr = emailsByClient.get(c.clientId) ?? [];
        arr.push(c.value.toLowerCase());
        emailsByClient.set(c.clientId, arr);
      } else if (c.channel === 'phone' || c.channel === 'whatsapp') {
        if (c.valueE164) {
          const arr = phonesByClient.get(c.clientId) ?? [];
          arr.push(c.valueE164);
          phonesByClient.set(c.clientId, arr);
        }
      }
    }

    const pool: MatchCandidate[] = liveClients.map((c) => {
      const named = normalizeName(c.fullName);
      return {
        id: c.id,
        fullName: c.fullName,
        surnameToken: named.surnameToken ?? null,
        emails: emailsByClient.get(c.id) ?? [],
        phonesE164: phonesByClient.get(c.id) ?? [],
        countryIso: (c.nationalityIso as CountryCode | null) ?? null,
      };
    });

    const matches = findClientMatches(input, pool, {
      highScore: 90,
      mediumScore: 50,
    });

    // Only return medium+ - low-confidence noise isn't useful at the
    // create-form layer (background scoring queue picks those up).
    const useful = matches.filter((m) => m.confidence !== 'low');
    if (useful.length === 0) {
      return NextResponse.json({ data: [] });
    }

    // Pull a quick summary for each surfaced candidate so the suggestion
    // card has enough to render ("Marcus Laurent · 2 interests · last
    // contact 9d ago").
    const summarizedIds = useful.map((m) => m.candidate.id);
    const interestCounts = await db
      .select({ clientId: interests.clientId })
      .from(interests)
      .where(inArray(interests.clientId, summarizedIds));
    const interestsByClient = new Map<string, number>();
    for (const r of interestCounts) {
      interestsByClient.set(r.clientId, (interestsByClient.get(r.clientId) ?? 0) + 1);
    }

    // Build a lookup from the original pool for archived flag - the dedup
    // candidate type intentionally doesn't carry it, but the suggestion card
    // needs to differentiate "use this live client" from "restore this
    // archived client". Without this the UX swallows soft-deleted dupes.
    const archivedById = new Map<string, Date | null>();
    for (const c of liveClients) archivedById.set(c.id, c.archivedAt ?? null);

    const data = useful.map((m) => ({
      clientId: m.candidate.id,
      fullName: m.candidate.fullName,
      score: m.score,
      confidence: m.confidence,
      reasons: m.reasons,
      interestCount: interestsByClient.get(m.candidate.id) ?? 0,
      emails: m.candidate.emails,
      phonesE164: m.candidate.phonesE164,
      archivedAt: archivedById.get(m.candidate.id)?.toISOString() ?? null,
    }));

    return NextResponse.json({ data });
  } catch (error) {
    return errorResponse(error);
  }
}
