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

import { listHandler, createHandler } from '@/app/api/v1/companies/[id]/members/handlers';
import { patchHandler, deleteHandler } from '@/app/api/v1/companies/[id]/members/[mid]/handlers';
import { setPrimaryHandler } from '@/app/api/v1/companies/[id]/members/[mid]/set-primary/handlers';
import { db } from '@/lib/db';
import { companyMemberships } from '@/lib/db/schema';
import { makeMockCtx, makeMockRequest } from '../../helpers/route-tester';
import { makeClient, makeCompany, makeFullPermissions, makePort } from '../../helpers/factories';

describe('GET /api/v1/companies/[id]/members', () => {
  it('returns active memberships for the company', async () => {
    const port = await makePort();
    const company = await makeCompany({ portId: port.id });
    const client1 = await makeClient({ portId: port.id });
    const client2 = await makeClient({ portId: port.id });
    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });

    await createHandler(
      makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
        body: {
          clientId: client1.id,
          role: 'director',
          startDate: new Date().toISOString(),
        },
      }),
      ctx,
      { id: company.id },
    );
    await createHandler(
      makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
        body: {
          clientId: client2.id,
          role: 'officer',
          startDate: new Date().toISOString(),
        },
      }),
      ctx,
      { id: company.id },
    );

    const req = makeMockRequest('GET', `http://localhost/api/v1/companies/${company.id}/members`);
    const res = await listHandler(req, ctx, { id: company.id });
    expect(res.status).toBe(200);
    const body = (await res.json()) as any;
    expect(Array.isArray(body.data)).toBe(true);
    expect(body.data.length).toBe(2);
    expect(body.data.every((m: { endDate: string | null }) => m.endDate === null)).toBe(true);
  });

  it('includes ended when activeOnly=false', async () => {
    const port = await makePort();
    const company = await makeCompany({ portId: port.id });
    const client1 = await makeClient({ portId: port.id });
    const client2 = await makeClient({ portId: port.id });
    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });

    // Active membership.
    await createHandler(
      makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
        body: {
          clientId: client1.id,
          role: 'director',
          startDate: new Date().toISOString(),
        },
      }),
      ctx,
      { id: company.id },
    );

    // Create then end another membership.
    const createRes = await createHandler(
      makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
        body: {
          clientId: client2.id,
          role: 'officer',
          startDate: new Date().toISOString(),
        },
      }),
      ctx,
      { id: company.id },
    );
    const createdBody = (await createRes.json()) as any;
    const toEndId = createdBody.data.id as string;

    const delRes = await deleteHandler(
      makeMockRequest(
        'DELETE',
        `http://localhost/api/v1/companies/${company.id}/members/${toEndId}`,
      ),
      ctx,
      { id: company.id, mid: toEndId },
    );
    expect(delRes.status).toBe(204);

    // Default - active only.
    const activeOnlyRes = await listHandler(
      makeMockRequest('GET', `http://localhost/api/v1/companies/${company.id}/members`),
      ctx,
      { id: company.id },
    );
    const activeBody = (await activeOnlyRes.json()) as any;
    expect(activeBody.data.length).toBe(1);

    // activeOnly=false.
    const allRes = await listHandler(
      makeMockRequest(
        'GET',
        `http://localhost/api/v1/companies/${company.id}/members?activeOnly=false`,
      ),
      ctx,
      { id: company.id },
    );
    const allBody = (await allRes.json()) as any;
    expect(allBody.data.length).toBe(2);
  });

  it('returns 404 when company does not exist or cross-tenant', async () => {
    const portA = await makePort();
    const portB = await makePort();
    const company = await makeCompany({ portId: portA.id });
    const ctx = makeMockCtx({ portId: portB.id, permissions: makeFullPermissions() });
    const req = makeMockRequest('GET', `http://localhost/api/v1/companies/${company.id}/members`);
    const res = await listHandler(req, ctx, { id: company.id });
    expect(res.status).toBe(404);
  });
});

describe('POST /api/v1/companies/[id]/members', () => {
  it('adds a membership (201)', async () => {
    const port = await makePort();
    const company = await makeCompany({ portId: port.id });
    const client = await makeClient({ portId: port.id });
    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });
    const req = makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
      body: {
        clientId: client.id,
        role: 'director',
        startDate: new Date().toISOString(),
        isPrimary: true,
      },
    });
    const res = await createHandler(req, ctx, { id: company.id });
    expect(res.status).toBe(201);
    const body = (await res.json()) as any;
    expect(body.data.companyId).toBe(company.id);
    expect(body.data.clientId).toBe(client.id);
    expect(body.data.role).toBe('director');
    expect(body.data.isPrimary).toBe(true);
  });

  it('returns 400 when clientId not found or cross-tenant', async () => {
    const portA = await makePort();
    const portB = await makePort();
    const company = await makeCompany({ portId: portA.id });
    const clientOtherPort = await makeClient({ portId: portB.id });
    const ctx = makeMockCtx({ portId: portA.id, permissions: makeFullPermissions() });
    const req = makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
      body: {
        clientId: clientOtherPort.id,
        role: 'director',
        startDate: new Date().toISOString(),
      },
    });
    const res = await createHandler(req, ctx, { id: company.id });
    expect(res.status).toBe(400);
  });

  it('returns 409 when exact duplicate (companyId+clientId+role+startDate)', async () => {
    const port = await makePort();
    const company = await makeCompany({ portId: port.id });
    const client = await makeClient({ portId: port.id });
    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });
    const startDate = new Date().toISOString();
    const body = {
      clientId: client.id,
      role: 'director' as const,
      startDate,
    };

    const first = await createHandler(
      makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
        body,
      }),
      ctx,
      { id: company.id },
    );
    expect(first.status).toBe(201);

    const dupe = await createHandler(
      makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
        body,
      }),
      ctx,
      { id: company.id },
    );
    expect(dupe.status).toBe(409);
  });
});

describe('PATCH /api/v1/companies/[id]/members/[mid]', () => {
  it('updates membership fields', async () => {
    const port = await makePort();
    const company = await makeCompany({ portId: port.id });
    const client = await makeClient({ portId: port.id });
    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });

    const createRes = await createHandler(
      makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
        body: {
          clientId: client.id,
          role: 'director',
          startDate: new Date().toISOString(),
        },
      }),
      ctx,
      { id: company.id },
    );
    const created = ((await createRes.json()) as any).data;

    const patchRes = await patchHandler(
      makeMockRequest(
        'PATCH',
        `http://localhost/api/v1/companies/${company.id}/members/${created.id}`,
        {
          body: {
            role: 'officer',
            notes: 'promoted',
          },
        },
      ),
      ctx,
      { id: company.id, mid: created.id },
    );
    expect(patchRes.status).toBe(200);
    const body = (await patchRes.json()) as any;
    expect(body.data.role).toBe('officer');
    expect(body.data.notes).toBe('promoted');
  });

  it('returns 404 for cross-tenant membership', async () => {
    const portA = await makePort();
    const portB = await makePort();
    const company = await makeCompany({ portId: portA.id });
    const client = await makeClient({ portId: portA.id });
    const ctxA = makeMockCtx({ portId: portA.id, permissions: makeFullPermissions() });

    const createRes = await createHandler(
      makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
        body: {
          clientId: client.id,
          role: 'director',
          startDate: new Date().toISOString(),
        },
      }),
      ctxA,
      { id: company.id },
    );
    const created = ((await createRes.json()) as any).data;

    const ctxB = makeMockCtx({ portId: portB.id, permissions: makeFullPermissions() });
    const patchRes = await patchHandler(
      makeMockRequest(
        'PATCH',
        `http://localhost/api/v1/companies/${company.id}/members/${created.id}`,
        { body: { role: 'officer' } },
      ),
      ctxB,
      { id: company.id, mid: created.id },
    );
    expect(patchRes.status).toBe(404);
  });
});

describe('DELETE /api/v1/companies/[id]/members/[mid]', () => {
  it('sets endDate to now when no body provided (204)', async () => {
    const port = await makePort();
    const company = await makeCompany({ portId: port.id });
    const client = await makeClient({ portId: port.id });
    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });

    const createRes = await createHandler(
      makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
        body: {
          clientId: client.id,
          role: 'director',
          startDate: new Date().toISOString(),
        },
      }),
      ctx,
      { id: company.id },
    );
    const created = ((await createRes.json()) as any).data;

    const before = new Date();
    const delRes = await deleteHandler(
      makeMockRequest(
        'DELETE',
        `http://localhost/api/v1/companies/${company.id}/members/${created.id}`,
      ),
      ctx,
      { id: company.id, mid: created.id },
    );
    expect(delRes.status).toBe(204);

    const [row] = await db
      .select()
      .from(companyMemberships)
      .where(eq(companyMemberships.id, created.id));
    expect(row?.endDate).not.toBeNull();
    expect(row!.endDate!.getTime()).toBeGreaterThanOrEqual(before.getTime() - 1000);
  });

  it('sets endDate from body when provided', async () => {
    const port = await makePort();
    const company = await makeCompany({ portId: port.id });
    const client = await makeClient({ portId: port.id });
    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });

    const createRes = await createHandler(
      makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
        body: {
          clientId: client.id,
          role: 'director',
          startDate: new Date('2025-01-01').toISOString(),
        },
      }),
      ctx,
      { id: company.id },
    );
    const created = ((await createRes.json()) as any).data;

    const explicitEnd = new Date('2026-06-01T00:00:00.000Z');
    const delRes = await deleteHandler(
      makeMockRequest(
        'DELETE',
        `http://localhost/api/v1/companies/${company.id}/members/${created.id}`,
        { body: { endDate: explicitEnd.toISOString() } },
      ),
      ctx,
      { id: company.id, mid: created.id },
    );
    expect(delRes.status).toBe(204);

    const [row] = await db
      .select()
      .from(companyMemberships)
      .where(eq(companyMemberships.id, created.id));
    expect(row?.endDate?.toISOString()).toBe(explicitEnd.toISOString());
  });
});

describe('POST /api/v1/companies/[id]/members/[mid]/set-primary', () => {
  it('sets only one membership as primary per company', async () => {
    const port = await makePort();
    const company = await makeCompany({ portId: port.id });
    const client1 = await makeClient({ portId: port.id });
    const client2 = await makeClient({ portId: port.id });
    const client3 = await makeClient({ portId: port.id });
    const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() });

    // M1: primary from the start.
    const m1Res = await createHandler(
      makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
        body: {
          clientId: client1.id,
          role: 'director',
          startDate: new Date().toISOString(),
          isPrimary: true,
        },
      }),
      ctx,
      { id: company.id },
    );
    const m1 = ((await m1Res.json()) as any).data;

    // M2, M3 - not primary.
    const m2Res = await createHandler(
      makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
        body: {
          clientId: client2.id,
          role: 'officer',
          startDate: new Date().toISOString(),
        },
      }),
      ctx,
      { id: company.id },
    );
    const m2 = ((await m2Res.json()) as any).data;

    const m3Res = await createHandler(
      makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
        body: {
          clientId: client3.id,
          role: 'employee',
          startDate: new Date().toISOString(),
        },
      }),
      ctx,
      { id: company.id },
    );
    const m3 = ((await m3Res.json()) as any).data;

    // Promote M2.
    const setPrimRes = await setPrimaryHandler(
      makeMockRequest(
        'POST',
        `http://localhost/api/v1/companies/${company.id}/members/${m2.id}/set-primary`,
      ),
      ctx,
      { id: company.id, mid: m2.id },
    );
    expect(setPrimRes.status).toBe(200);
    const setPrimBody = (await setPrimRes.json()) as any;
    expect(setPrimBody.data.id).toBe(m2.id);
    expect(setPrimBody.data.isPrimary).toBe(true);

    // Verify DB state: only M2 is primary.
    const rows = await db
      .select()
      .from(companyMemberships)
      .where(eq(companyMemberships.companyId, company.id));

    const primary = rows.filter((r) => r.isPrimary);
    expect(primary.length).toBe(1);
    expect(primary[0]!.id).toBe(m2.id);

    const m1Row = rows.find((r) => r.id === m1.id);
    expect(m1Row?.isPrimary).toBe(false);
    const m3Row = rows.find((r) => r.id === m3.id);
    expect(m3Row?.isPrimary).toBe(false);
  });
});
