import { NextRequest, NextResponse } from 'next/server';
import { sql } from 'drizzle-orm';

import { withAuth } from '@/lib/api/helpers';
import { parseBody } from '@/lib/api/route-helpers';
import { db } from '@/lib/db';
import { errorResponse } from '@/lib/errors';
import { getRecentlyViewed, trackView } from '@/lib/services/recently-viewed.service';
import { trackViewSchema } from '@/lib/validators/search';

/**
 * Hydrated row returned by GET - contains the same `type` + `id` from
 * the Redis sorted set, plus the labels needed to render the row in the
 * dropdown ("Client · Jane Smith") without an extra round-trip.
 *
 * Rows whose underlying entity has been hard-deleted are filtered out
 * (Redis keeps the membership; the hydration JOIN drops the orphan).
 */
interface HydratedRow {
  type: string;
  id: string;
  label: string;
  sub: string | null;
  href: string;
  viewedAt: number;
}

/** Batch-resolve labels for a list of (type, id) pairs grouped by type. */
async function hydrate(
  portSlug: string,
  portId: string,
  pairs: Array<{ type: string; id: string; viewedAt: number }>,
): Promise<HydratedRow[]> {
  if (pairs.length === 0) return [];

  const byType = new Map<string, Array<{ id: string; viewedAt: number }>>();
  for (const p of pairs) {
    if (!byType.has(p.type)) byType.set(p.type, []);
    byType.get(p.type)!.push({ id: p.id, viewedAt: p.viewedAt });
  }

  const rows: HydratedRow[] = [];

  await Promise.all(
    Array.from(byType.entries()).map(async ([type, items]) => {
      const ids = items.map((i) => i.id);
      const idVal = sql`(${sql.join(
        ids.map((id) => sql`${id}`),
        sql`, `,
      )})`;
      const idAt = new Map(items.map((i) => [i.id, i.viewedAt] as const));

      switch (type) {
        case 'client': {
          const dbRows = await db.execute<{ id: string; full_name: string }>(sql`
            SELECT id, full_name FROM clients
            WHERE port_id = ${portId} AND archived_at IS NULL AND id IN ${idVal}
          `);
          for (const r of Array.from(dbRows)) {
            rows.push({
              type,
              id: r.id,
              label: r.full_name,
              sub: 'Client',
              href: `/${portSlug}/clients/${r.id}`,
              viewedAt: idAt.get(r.id) ?? 0,
            });
          }
          return;
        }
        case 'residential-client': {
          const dbRows = await db.execute<{ id: string; full_name: string }>(sql`
            SELECT id, full_name FROM residential_clients
            WHERE port_id = ${portId} AND archived_at IS NULL AND id IN ${idVal}
          `);
          for (const r of Array.from(dbRows)) {
            rows.push({
              type,
              id: r.id,
              label: r.full_name,
              sub: 'Residential client',
              href: `/${portSlug}/residential/clients/${r.id}`,
              viewedAt: idAt.get(r.id) ?? 0,
            });
          }
          return;
        }
        case 'yacht': {
          const dbRows = await db.execute<{ id: string; name: string; hull_number: string | null }>(
            sql`SELECT id, name, hull_number FROM yachts
                WHERE port_id = ${portId} AND archived_at IS NULL AND id IN ${idVal}`,
          );
          for (const r of Array.from(dbRows)) {
            rows.push({
              type,
              id: r.id,
              label: r.name,
              sub: r.hull_number ? `Yacht · ${r.hull_number}` : 'Yacht',
              href: `/${portSlug}/yachts/${r.id}`,
              viewedAt: idAt.get(r.id) ?? 0,
            });
          }
          return;
        }
        case 'company': {
          const dbRows = await db.execute<{ id: string; name: string }>(
            sql`SELECT id, name FROM companies
                WHERE port_id = ${portId} AND archived_at IS NULL AND id IN ${idVal}`,
          );
          for (const r of Array.from(dbRows)) {
            rows.push({
              type,
              id: r.id,
              label: r.name,
              sub: 'Company',
              href: `/${portSlug}/companies/${r.id}`,
              viewedAt: idAt.get(r.id) ?? 0,
            });
          }
          return;
        }
        case 'interest': {
          const dbRows = await db.execute<{
            id: string;
            full_name: string;
            mooring_number: string | null;
          }>(sql`
            SELECT i.id, c.full_name, b.mooring_number
            FROM interests i
            JOIN clients c ON i.client_id = c.id
            LEFT JOIN interest_berths ib ON ib.interest_id = i.id AND ib.is_primary = true
            LEFT JOIN berths b ON ib.berth_id = b.id
            WHERE i.port_id = ${portId} AND i.archived_at IS NULL AND i.id IN ${idVal}
          `);
          for (const r of Array.from(dbRows)) {
            rows.push({
              type,
              id: r.id,
              label: r.full_name,
              sub: r.mooring_number ? `Interest · ${r.mooring_number}` : 'Interest',
              href: `/${portSlug}/interests/${r.id}`,
              viewedAt: idAt.get(r.id) ?? 0,
            });
          }
          return;
        }
        case 'residential-interest': {
          const dbRows = await db.execute<{ id: string; full_name: string }>(sql`
            SELECT ri.id, rc.full_name
            FROM residential_interests ri
            JOIN residential_clients rc ON ri.residential_client_id = rc.id
            WHERE ri.port_id = ${portId} AND ri.archived_at IS NULL AND ri.id IN ${idVal}
          `);
          for (const r of Array.from(dbRows)) {
            rows.push({
              type,
              id: r.id,
              label: r.full_name,
              sub: 'Residential interest',
              href: `/${portSlug}/residential/interests/${r.id}`,
              viewedAt: idAt.get(r.id) ?? 0,
            });
          }
          return;
        }
        case 'berth': {
          const dbRows = await db.execute<{ id: string; mooring_number: string }>(sql`
            SELECT id, mooring_number FROM berths
            WHERE port_id = ${portId} AND id IN ${idVal}
          `);
          for (const r of Array.from(dbRows)) {
            rows.push({
              type,
              id: r.id,
              label: r.mooring_number,
              sub: 'Berth',
              href: `/${portSlug}/berths/${r.id}`,
              viewedAt: idAt.get(r.id) ?? 0,
            });
          }
          return;
        }
        case 'invoice': {
          const dbRows = await db.execute<{
            id: string;
            invoice_number: string;
            client_name: string;
          }>(sql`
            SELECT id, invoice_number, client_name FROM invoices
            WHERE port_id = ${portId} AND id IN ${idVal}
          `);
          for (const r of Array.from(dbRows)) {
            rows.push({
              type,
              id: r.id,
              label: r.invoice_number,
              sub: `Invoice · ${r.client_name}`,
              href: `/${portSlug}/invoices/${r.id}`,
              viewedAt: idAt.get(r.id) ?? 0,
            });
          }
          return;
        }
        case 'expense': {
          const dbRows = await db.execute<{
            id: string;
            description: string | null;
            establishment_name: string | null;
          }>(sql`
            SELECT id, description, establishment_name FROM expenses
            WHERE port_id = ${portId} AND id IN ${idVal}
          `);
          for (const r of Array.from(dbRows)) {
            rows.push({
              type,
              id: r.id,
              label: r.description ?? r.establishment_name ?? 'Expense',
              sub: 'Expense',
              href: `/${portSlug}/expenses/${r.id}`,
              viewedAt: idAt.get(r.id) ?? 0,
            });
          }
          return;
        }
        case 'document': {
          const dbRows = await db.execute<{ id: string; title: string }>(sql`
            SELECT id, title FROM documents
            WHERE port_id = ${portId} AND id IN ${idVal}
          `);
          for (const r of Array.from(dbRows)) {
            rows.push({
              type,
              id: r.id,
              label: r.title,
              sub: 'Document',
              href: `/${portSlug}/documents/${r.id}`,
              viewedAt: idAt.get(r.id) ?? 0,
            });
          }
          return;
        }
      }
    }),
  );

  // Re-sort by viewedAt DESC since the per-type queries lose the order.
  rows.sort((a, b) => b.viewedAt - a.viewedAt);
  return rows;
}

export const GET = withAuth(async (req: NextRequest, ctx) => {
  try {
    const limitParam = req.nextUrl.searchParams.get('limit');
    const limit = limitParam ? Math.min(Math.max(Number(limitParam), 1), 20) : 10;

    const pairs = await getRecentlyViewed(ctx.userId, ctx.portId, limit);
    const items = await hydrate(ctx.portSlug, ctx.portId, pairs);

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

export const POST = withAuth(async (req: NextRequest, ctx) => {
  try {
    const parsed = await parseBody(req, trackViewSchema);
    trackView(ctx.userId, ctx.portId, parsed.type, parsed.id);
    return new NextResponse(null, { status: 204 });
  } catch (error) {
    return errorResponse(error);
  }
});
