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

import { db } from '@/lib/db';
import { interests, interestBerths } from '@/lib/db/schema/interests';
import { berthTenancies } from '@/lib/db/schema/tenancies';
import { systemSettings } from '@/lib/db/schema/system';
import { autoCreatePendingTenancies } from '@/lib/services/berth-tenancies.service';
import {
  enableTenanciesModule,
  isTenanciesModuleEnabled,
} from '@/lib/services/tenancies-module.service';
import { makeBerth, makeClient, makePort, makeYacht } from '../helpers/factories';

async function seedInterestWithBundleBerths(
  portId: string,
  bundleCount: number,
): Promise<{ interestId: string; clientId: string; yachtId: string; berthIds: string[] }> {
  const client = await makeClient({ portId });
  const yacht = await makeYacht({ portId, ownerType: 'client', ownerId: client.id });
  const [interest] = await db
    .insert(interests)
    .values({
      portId,
      clientId: client.id,
      yachtId: yacht.id,
      pipelineStage: 'reservation',
      outcome: 'open',
    })
    .returning();

  const berthIds: string[] = [];
  for (let i = 0; i < bundleCount; i++) {
    const b = await makeBerth({ portId });
    berthIds.push(b.id);
    await db.insert(interestBerths).values({
      interestId: interest!.id,
      berthId: b.id,
      isInEoiBundle: true,
      isPrimary: i === 0,
      isSpecificInterest: true,
    });
  }
  return { interestId: interest!.id, clientId: client.id, yachtId: yacht.id, berthIds };
}

async function disableModule(portId: string): Promise<void> {
  await db
    .insert(systemSettings)
    .values({ key: 'tenancies_module_enabled', portId, value: false })
    .onConflictDoUpdate({
      target: [systemSettings.key, systemSettings.portId],
      set: { value: false, updatedAt: new Date() },
    });
}

describe('autoCreatePendingTenancies', () => {
  let portId: string;
  beforeEach(async () => {
    const port = await makePort();
    portId = port.id;
    // Start each test with the module explicitly disabled so the lazy
    // auto-enable path doesn't taint the next test's port.
    await disableModule(portId);
  });

  afterEach(async () => {
    // Make sure no stale advisory locks from a failed test linger.
  });

  it('mints one pending tenancy per in-bundle berth', async () => {
    await enableTenanciesModule(portId);
    const { interestId, berthIds } = await seedInterestWithBundleBerths(portId, 3);

    const result = await autoCreatePendingTenancies(portId, interestId, {
      signedAt: new Date('2026-01-15'),
      sourceDocumentId: 'doc-fixture-123',
      signedFileId: null,
    });

    expect(result).toHaveLength(3);
    expect(result.every((r) => r.status === 'pending')).toBe(true);
    expect(result.every((r) => r.interestId === interestId)).toBe(true);
    expect(result.every((r) => r.contractFileId === null)).toBe(true);
    expect(result.map((r) => r.berthId).sort()).toEqual(berthIds.sort());
  });

  it('skips berths that already have a live tenancy (idempotent under retry)', async () => {
    await enableTenanciesModule(portId);
    const { interestId, berthIds } = await seedInterestWithBundleBerths(portId, 2);

    const first = await autoCreatePendingTenancies(portId, interestId, {
      signedAt: new Date(),
      sourceDocumentId: 'doc-1',
      signedFileId: null,
    });
    expect(first).toHaveLength(2);

    const replay = await autoCreatePendingTenancies(portId, interestId, {
      signedAt: new Date(),
      sourceDocumentId: 'doc-1',
      signedFileId: null,
    });
    expect(replay).toHaveLength(0);

    const allRows = await db
      .select()
      .from(berthTenancies)
      .where(and(eq(berthTenancies.portId, portId), eq(berthTenancies.interestId, interestId)));
    expect(allRows).toHaveLength(2);
    expect(allRows.map((r) => r.berthId).sort()).toEqual(berthIds.sort());
  });

  it('returns empty when the interest has no in-bundle berths', async () => {
    await enableTenanciesModule(portId);
    const client = await makeClient({ portId });
    const yacht = await makeYacht({ portId, ownerType: 'client', ownerId: client.id });
    const [interest] = await db
      .insert(interests)
      .values({
        portId,
        clientId: client.id,
        yachtId: yacht.id,
        pipelineStage: 'reservation',
        outcome: 'open',
      })
      .returning();

    const result = await autoCreatePendingTenancies(portId, interest!.id, {
      signedAt: new Date(),
      sourceDocumentId: 'doc-empty',
      signedFileId: null,
    });
    expect(result).toEqual([]);
  });

  it('does not mint when the interest is missing (deleted before webhook fires)', async () => {
    await enableTenanciesModule(portId);
    const result = await autoCreatePendingTenancies(portId, 'nonexistent-interest', {
      signedAt: new Date(),
      sourceDocumentId: 'doc-x',
      signedFileId: null,
    });
    expect(result).toEqual([]);
  });
});

describe('Tenancies module enable-on-createPending (audit M29: explicit-disable wins)', () => {
  it('does NOT re-enable a module an admin explicitly disabled (M29)', async () => {
    const port = await makePort();
    const portId = port.id;
    await disableModule(portId);
    expect(await isTenanciesModuleEnabled(portId)).toBe(false);

    const client = await makeClient({ portId });
    const yacht = await makeYacht({ portId, ownerType: 'client', ownerId: client.id });
    const berth = await makeBerth({ portId });
    const { createPending } = await import('@/lib/services/berth-tenancies.service');
    await createPending(
      portId,
      {
        berthId: berth.id,
        clientId: client.id,
        yachtId: yacht.id,
        startDate: new Date(),
      },
      { userId: 'system', portId, ipAddress: '0.0.0.0', userAgent: 'test' },
    );

    // Audit M29: a manual createPending (or a Reservation-Agreement webhook)
    // must respect an EXPLICIT `false` and not silently flip the module back
    // on. The lazy auto-surface applies only when the setting is UNSET.
    expect(await isTenanciesModuleEnabled(portId)).toBe(false);
  });
});
