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

import type { AuthContext } from '@/lib/api/helpers';
import { parseBody } from '@/lib/api/route-helpers';
import { db } from '@/lib/db';
import { clients, clientMergeCandidates } from '@/lib/db/schema/clients';
import { errorResponse, NotFoundError, ValidationError } from '@/lib/errors';
import {
  listPendingMergeCandidates,
  mergeClients,
  type MergeFieldChoices,
} from '@/lib/services/client-merge.service';

const confirmMergeSchema = z.object({
  winnerId: z.string().min(1),
  fieldChoices: z.record(z.string(), z.string()).optional(),
});

/**
 * GET /api/v1/admin/duplicates
 *
 * Pending merge candidates for the current port, sorted by score.
 * Each row hydrates its two client summaries so the review-queue UI
 * can render side-by-side cards without an N+1 fetch.
 */
export async function listHandler(_req: Request, ctx: AuthContext): Promise<NextResponse> {
  try {
    const pairs = await listPendingMergeCandidates(ctx.portId);
    if (pairs.length === 0) return NextResponse.json({ data: [] });

    const ids = Array.from(new Set(pairs.flatMap((p) => [p.clientAId, p.clientBId])));
    const clientRows = await db
      .select({
        id: clients.id,
        fullName: clients.fullName,
        archivedAt: clients.archivedAt,
        mergedIntoClientId: clients.mergedIntoClientId,
        createdAt: clients.createdAt,
      })
      .from(clients)
      .where(inArray(clients.id, ids));
    const clientById = new Map(clientRows.map((c) => [c.id, c]));

    const data = pairs
      .map((p) => {
        const a = clientById.get(p.clientAId);
        const b = clientById.get(p.clientBId);
        if (!a || !b) return null; // FK orphan - shouldn't happen, but be defensive
        // Skip pairs where one side has already been merged or archived.
        if (a.mergedIntoClientId || b.mergedIntoClientId) return null;
        return {
          id: p.id,
          score: p.score,
          reasons: p.reasons,
          createdAt: p.createdAt,
          clientA: { id: a.id, fullName: a.fullName, createdAt: a.createdAt },
          clientB: { id: b.id, fullName: b.fullName, createdAt: b.createdAt },
        };
      })
      .filter((row): row is NonNullable<typeof row> => row !== null);

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

/**
 * POST /api/v1/admin/duplicates/[id]/merge
 *
 * Body: { winnerId: string, fieldChoices?: MergeFieldChoices }
 *
 * Confirms a merge candidate. The winner is the one the user picked
 * to keep; the other side becomes the loser. Calls into the merge
 * service which is the only path that touches client_merge_log.
 */
export async function confirmMergeHandler(
  req: NextRequest,
  ctx: AuthContext,
  params: { id?: string },
): Promise<NextResponse> {
  try {
    const id = params.id ?? '';
    const body = await parseBody(req, confirmMergeSchema);

    const [candidate] = await db
      .select()
      .from(clientMergeCandidates)
      .where(
        and(
          eq(clientMergeCandidates.id, id),
          eq(clientMergeCandidates.portId, ctx.portId),
          eq(clientMergeCandidates.status, 'pending'),
        ),
      );
    if (!candidate) throw new NotFoundError('Merge candidate');

    const loserId =
      body.winnerId === candidate.clientAId
        ? candidate.clientBId
        : body.winnerId === candidate.clientBId
          ? candidate.clientAId
          : null;
    if (!loserId) {
      throw new ValidationError('winnerId must match one of the candidate clients');
    }

    const result = await mergeClients({
      winnerId: body.winnerId,
      loserId,
      mergedBy: ctx.userId,
      callerPortId: ctx.portId,
      fieldChoices: body.fieldChoices as MergeFieldChoices | undefined,
    });

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

/**
 * POST /api/v1/admin/duplicates/[id]/dismiss
 *
 * Mark a merge candidate as dismissed. The background scoring job
 * skips dismissed pairs on subsequent runs (a future score increase
 * can re-create them).
 */
export async function dismissHandler(
  _req: Request,
  ctx: AuthContext,
  params: { id?: string },
): Promise<NextResponse> {
  try {
    const id = params.id ?? '';
    const result = await db
      .update(clientMergeCandidates)
      .set({
        status: 'dismissed',
        resolvedAt: new Date(),
        resolvedBy: ctx.userId,
      })
      .where(
        and(
          eq(clientMergeCandidates.id, id),
          eq(clientMergeCandidates.portId, ctx.portId),
          eq(clientMergeCandidates.status, 'pending'),
        ),
      )
      .returning({ id: clientMergeCandidates.id });

    if (result.length === 0) throw new NotFoundError('Merge candidate');
    return NextResponse.json({ data: { id: result[0]!.id, status: 'dismissed' } });
  } catch (error) {
    return errorResponse(error);
  }
}
