import { describe, it, expect } from 'vitest';
import { eq, and } from 'drizzle-orm';

import { listHandler, addHandler } from '@/app/api/v1/interests/[id]/berths/handlers';
import { patchHandler, deleteHandler } from '@/app/api/v1/interests/[id]/berths/[berthId]/handlers';
import { withPermission } from '@/lib/api/helpers';
import { db } from '@/lib/db';
import { interestBerths, interests } from '@/lib/db/schema/interests';
import { makeMockCtx, makeMockRequest } from '../../helpers/route-tester';
import {
  makePort,
  makeClient,
  makeBerth,
  makeFullPermissions,
  makeViewerPermissions,
} from '../../helpers/factories';

async function makeInterest(args: {
  portId: string;
  clientId: string;
  eoiStatus?: 'waiting_for_signatures' | 'signed' | 'expired' | null;
}) {
  const [row] = await db
    .insert(interests)
    .values({
      portId: args.portId,
      clientId: args.clientId,
      pipelineStage: 'open',
      eoiStatus: args.eoiStatus ?? null,
    })
    .returning();
  return row!;
}

// ─── GET /api/v1/interests/[id]/berths ──────────────────────────────────────

describe('GET /api/v1/interests/[id]/berths (listHandler)', () => {
  it('returns linked berths and surfaces eoiStatus in meta', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const interest = await makeInterest({
      portId: port.id,
      clientId: client.id,
      eoiStatus: 'signed',
    });
    const berth = await makeBerth({ portId: port.id });
    await db.insert(interestBerths).values({
      interestId: interest.id,
      berthId: berth.id,
      isPrimary: true,
      isSpecificInterest: true,
      isInEoiBundle: true,
    });

    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });
    const req = makeMockRequest('GET', `http://localhost/api/v1/interests/${interest.id}/berths`);
    const res = await listHandler(req, ctx, { id: interest.id });
    expect(res.status).toBe(200);
    const body = (await res.json()) as any;
    expect(body.data).toHaveLength(1);
    expect(body.data[0].berthId).toBe(berth.id);
    expect(body.data[0].mooringNumber).toBe(berth.mooringNumber);
    expect(body.meta.eoiStatus).toBe('signed');
  });

  it('returns 404 for a cross-port interest (tenant isolation)', async () => {
    const portA = await makePort();
    const portB = await makePort();
    const client = await makeClient({ portId: portA.id });
    const interest = await makeInterest({ portId: portA.id, clientId: client.id });

    const ctx = makeMockCtx({ portId: portB.id, permissions: makeFullPermissions() });
    const req = makeMockRequest('GET', `http://localhost/api/v1/interests/${interest.id}/berths`);
    const res = await listHandler(req, ctx, { id: interest.id });
    expect(res.status).toBe(404);
  });

  it('viewer with interests.view can list', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const interest = await makeInterest({ portId: port.id, clientId: client.id });
    const ctx = makeMockCtx({ portId: port.id, permissions: makeViewerPermissions() });
    const gated = withPermission('interests', 'view', listHandler);
    const req = makeMockRequest('GET', `http://localhost/api/v1/interests/${interest.id}/berths`);
    const res = await gated(req, ctx, { id: interest.id });
    expect(res.status).toBe(200);
  });
});

// ─── POST /api/v1/interests/[id]/berths ─────────────────────────────────────

describe('POST /api/v1/interests/[id]/berths (addHandler)', () => {
  it('links a berth to an interest with isSpecificInterest', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const interest = await makeInterest({ portId: port.id, clientId: client.id });
    const berth = await makeBerth({ portId: port.id });

    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });
    const req = makeMockRequest('POST', `http://localhost/api/v1/interests/${interest.id}/berths`, {
      body: { berthId: berth.id, isSpecificInterest: true },
    });
    const res = await addHandler(req, ctx, { id: interest.id });
    expect(res.status).toBe(201);
    const body = (await res.json()) as any;
    expect(body.data.berthId).toBe(berth.id);
    expect(body.data.isSpecificInterest).toBe(true);
  });

  it('rejects a berth from a different port (404 on the interest scope)', async () => {
    const portA = await makePort();
    const portB = await makePort();
    const clientA = await makeClient({ portId: portA.id });
    const interestA = await makeInterest({ portId: portA.id, clientId: clientA.id });
    const berthB = await makeBerth({ portId: portB.id });

    const ctx = makeMockCtx({ portId: portA.id, permissions: makeFullPermissions() });
    const req = makeMockRequest(
      'POST',
      `http://localhost/api/v1/interests/${interestA.id}/berths`,
      { body: { berthId: berthB.id, isSpecificInterest: true } },
    );
    const res = await addHandler(req, ctx, { id: interestA.id });
    // berth doesn't belong to caller's port → 400 ValidationError
    expect(res.status).toBe(400);
  });

  it('viewer (no interests.edit) receives 403 through the permission gate', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const interest = await makeInterest({ portId: port.id, clientId: client.id });
    const berth = await makeBerth({ portId: port.id });

    const gated = withPermission('interests', 'edit', addHandler);
    const ctx = makeMockCtx({ portId: port.id, permissions: makeViewerPermissions() });
    const req = makeMockRequest('POST', `http://localhost/api/v1/interests/${interest.id}/berths`, {
      body: { berthId: berth.id, isSpecificInterest: true },
    });
    const res = await gated(req, ctx, { id: interest.id });
    expect(res.status).toBe(403);
  });
});

// ─── PATCH /api/v1/interests/[id]/berths/[berthId] ──────────────────────────

describe('PATCH /api/v1/interests/[id]/berths/[berthId] (patchHandler)', () => {
  it('updates flags and returns the latest junction row', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const interest = await makeInterest({ portId: port.id, clientId: client.id });
    const berth = await makeBerth({ portId: port.id });
    await db.insert(interestBerths).values({
      interestId: interest.id,
      berthId: berth.id,
      isPrimary: false,
      isSpecificInterest: true,
      isInEoiBundle: false,
    });

    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });
    const req = makeMockRequest(
      'PATCH',
      `http://localhost/api/v1/interests/${interest.id}/berths/${berth.id}`,
      { body: { isInEoiBundle: true, isSpecificInterest: false } },
    );
    const res = await patchHandler(req, ctx, { id: interest.id, berthId: berth.id });
    expect(res.status).toBe(200);
    const body = (await res.json()) as any;
    expect(body.data.isInEoiBundle).toBe(true);
    expect(body.data.isSpecificInterest).toBe(false);
  });

  it('promoting one row to primary demotes the prior primary in the same interest', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const interest = await makeInterest({ portId: port.id, clientId: client.id });
    const berthA = await makeBerth({ portId: port.id });
    const berthB = await makeBerth({ portId: port.id });
    await db.insert(interestBerths).values([
      { interestId: interest.id, berthId: berthA.id, isPrimary: true },
      { interestId: interest.id, berthId: berthB.id, isPrimary: false },
    ]);

    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });
    const req = makeMockRequest(
      'PATCH',
      `http://localhost/api/v1/interests/${interest.id}/berths/${berthB.id}`,
      { body: { isPrimary: true } },
    );
    const res = await patchHandler(req, ctx, { id: interest.id, berthId: berthB.id });
    expect(res.status).toBe(200);

    const rows = await db
      .select()
      .from(interestBerths)
      .where(eq(interestBerths.interestId, interest.id));
    const rowA = rows.find((r) => r.berthId === berthA.id);
    const rowB = rows.find((r) => r.berthId === berthB.id);
    expect(rowA?.isPrimary).toBe(false);
    expect(rowB?.isPrimary).toBe(true);
  });

  it('records bypass fields when interest has signed primary EOI', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const interest = await makeInterest({
      portId: port.id,
      clientId: client.id,
      eoiStatus: 'signed',
    });
    const berth = await makeBerth({ portId: port.id });
    await db.insert(interestBerths).values({
      interestId: interest.id,
      berthId: berth.id,
      isPrimary: true,
    });

    const ctx = makeMockCtx({
      portId: port.id,
      permissions: makeFullPermissions(),
      userId: 'rep-1',
    });
    const req = makeMockRequest(
      'PATCH',
      `http://localhost/api/v1/interests/${interest.id}/berths/${berth.id}`,
      { body: { eoiBypassReason: 'covered under bundle EOI' } },
    );
    const res = await patchHandler(req, ctx, { id: interest.id, berthId: berth.id });
    expect(res.status).toBe(200);

    const [row] = await db
      .select()
      .from(interestBerths)
      .where(and(eq(interestBerths.interestId, interest.id), eq(interestBerths.berthId, berth.id)));
    expect(row?.eoiBypassReason).toBe('covered under bundle EOI');
    expect(row?.eoiBypassedBy).toBe('rep-1');
    expect(row?.eoiBypassedAt).toBeInstanceOf(Date);
  });

  it('rejects bypass when the interest does not have a signed EOI', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const interest = await makeInterest({
      portId: port.id,
      clientId: client.id,
      eoiStatus: 'waiting_for_signatures',
    });
    const berth = await makeBerth({ portId: port.id });
    await db.insert(interestBerths).values({
      interestId: interest.id,
      berthId: berth.id,
    });

    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });
    const req = makeMockRequest(
      'PATCH',
      `http://localhost/api/v1/interests/${interest.id}/berths/${berth.id}`,
      { body: { eoiBypassReason: 'too early' } },
    );
    const res = await patchHandler(req, ctx, { id: interest.id, berthId: berth.id });
    expect(res.status).toBe(400);
  });

  it('clears bypass when reason is null', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const interest = await makeInterest({
      portId: port.id,
      clientId: client.id,
      eoiStatus: 'signed',
    });
    const berth = await makeBerth({ portId: port.id });
    await db.insert(interestBerths).values({
      interestId: interest.id,
      berthId: berth.id,
      eoiBypassReason: 'previously bypassed',
      eoiBypassedBy: 'someone',
      eoiBypassedAt: new Date(),
    });

    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });
    const req = makeMockRequest(
      'PATCH',
      `http://localhost/api/v1/interests/${interest.id}/berths/${berth.id}`,
      { body: { eoiBypassReason: null } },
    );
    const res = await patchHandler(req, ctx, { id: interest.id, berthId: berth.id });
    expect(res.status).toBe(200);

    const [row] = await db
      .select()
      .from(interestBerths)
      .where(and(eq(interestBerths.interestId, interest.id), eq(interestBerths.berthId, berth.id)));
    expect(row?.eoiBypassReason).toBeNull();
    expect(row?.eoiBypassedBy).toBeNull();
    expect(row?.eoiBypassedAt).toBeNull();
  });

  it('returns 404 for a cross-port interest', async () => {
    const portA = await makePort();
    const portB = await makePort();
    const client = await makeClient({ portId: portA.id });
    const interest = await makeInterest({ portId: portA.id, clientId: client.id });
    const berth = await makeBerth({ portId: portA.id });
    await db.insert(interestBerths).values({ interestId: interest.id, berthId: berth.id });

    const ctx = makeMockCtx({ portId: portB.id, permissions: makeFullPermissions() });
    const req = makeMockRequest(
      'PATCH',
      `http://localhost/api/v1/interests/${interest.id}/berths/${berth.id}`,
      { body: { isPrimary: true } },
    );
    const res = await patchHandler(req, ctx, { id: interest.id, berthId: berth.id });
    expect(res.status).toBe(404);
  });

  it('returns 404 when the junction row does not exist', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const interest = await makeInterest({ portId: port.id, clientId: client.id });
    const berth = await makeBerth({ portId: port.id });

    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });
    const req = makeMockRequest(
      'PATCH',
      `http://localhost/api/v1/interests/${interest.id}/berths/${berth.id}`,
      { body: { isPrimary: true } },
    );
    const res = await patchHandler(req, ctx, { id: interest.id, berthId: berth.id });
    expect(res.status).toBe(404);
  });

  it('returns 400 when the body is empty', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const interest = await makeInterest({ portId: port.id, clientId: client.id });
    const berth = await makeBerth({ portId: port.id });
    await db.insert(interestBerths).values({ interestId: interest.id, berthId: berth.id });

    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });
    const req = makeMockRequest(
      'PATCH',
      `http://localhost/api/v1/interests/${interest.id}/berths/${berth.id}`,
      { body: {} },
    );
    const res = await patchHandler(req, ctx, { id: interest.id, berthId: berth.id });
    expect(res.status).toBe(400);
  });

  it('viewer (no interests.edit) receives 403 through the permission gate', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const interest = await makeInterest({ portId: port.id, clientId: client.id });
    const berth = await makeBerth({ portId: port.id });
    await db.insert(interestBerths).values({ interestId: interest.id, berthId: berth.id });

    const gated = withPermission('interests', 'edit', patchHandler);
    const ctx = makeMockCtx({ portId: port.id, permissions: makeViewerPermissions() });
    const req = makeMockRequest(
      'PATCH',
      `http://localhost/api/v1/interests/${interest.id}/berths/${berth.id}`,
      { body: { isPrimary: true } },
    );
    const res = await gated(req, ctx, { id: interest.id, berthId: berth.id });
    expect(res.status).toBe(403);
  });
});

// ─── DELETE /api/v1/interests/[id]/berths/[berthId] ─────────────────────────

describe('DELETE /api/v1/interests/[id]/berths/[berthId] (deleteHandler)', () => {
  it('removes the junction row and returns 204', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const interest = await makeInterest({ portId: port.id, clientId: client.id });
    const berth = await makeBerth({ portId: port.id });
    await db.insert(interestBerths).values({ interestId: interest.id, berthId: berth.id });

    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });
    const req = makeMockRequest(
      'DELETE',
      `http://localhost/api/v1/interests/${interest.id}/berths/${berth.id}`,
    );
    const res = await deleteHandler(req, ctx, { id: interest.id, berthId: berth.id });
    expect(res.status).toBe(204);

    const rows = await db
      .select()
      .from(interestBerths)
      .where(and(eq(interestBerths.interestId, interest.id), eq(interestBerths.berthId, berth.id)));
    expect(rows).toHaveLength(0);
  });

  it('returns 404 for a cross-port interest', async () => {
    const portA = await makePort();
    const portB = await makePort();
    const client = await makeClient({ portId: portA.id });
    const interest = await makeInterest({ portId: portA.id, clientId: client.id });
    const berth = await makeBerth({ portId: portA.id });
    await db.insert(interestBerths).values({ interestId: interest.id, berthId: berth.id });

    const ctx = makeMockCtx({ portId: portB.id, permissions: makeFullPermissions() });
    const req = makeMockRequest(
      'DELETE',
      `http://localhost/api/v1/interests/${interest.id}/berths/${berth.id}`,
    );
    const res = await deleteHandler(req, ctx, { id: interest.id, berthId: berth.id });
    expect(res.status).toBe(404);
  });

  it('viewer (no interests.edit) receives 403 through the permission gate', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const interest = await makeInterest({ portId: port.id, clientId: client.id });
    const berth = await makeBerth({ portId: port.id });
    await db.insert(interestBerths).values({ interestId: interest.id, berthId: berth.id });

    const gated = withPermission('interests', 'edit', deleteHandler);
    const ctx = makeMockCtx({ portId: port.id, permissions: makeViewerPermissions() });
    const req = makeMockRequest(
      'DELETE',
      `http://localhost/api/v1/interests/${interest.id}/berths/${berth.id}`,
    );
    const res = await gated(req, ctx, { id: interest.id, berthId: berth.id });
    expect(res.status).toBe(403);
  });
});
