import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';

import { eq } from 'drizzle-orm';

import { db } from '@/lib/db';
import { documents, documentTemplates } from '@/lib/db/schema/documents';
import { clientAddresses, clientContacts } from '@/lib/db/schema/clients';
import {
  interests as interestsTable,
  interestBerths as interestBerthsTable,
} from '@/lib/db/schema/interests';
import { systemSettings } from '@/lib/db/schema/system';
import { ValidationError } from '@/lib/errors';

import { makeBerth, makeClient, makePort, makeYacht } from '../helpers/factories';

// ─── Mocks ────────────────────────────────────────────────────────────────────

vi.mock('@/lib/services/documenso-client', () => ({
  createDocument: vi.fn(),
  sendDocument: vi.fn(),
  generateDocumentFromTemplate: vi.fn(),
}));

vi.mock('@/lib/minio', async () => {
  const actual = await vi.importActual<typeof import('@/lib/minio')>('@/lib/minio');
  return {
    ...actual,
    minioClient: {
      putObject: vi.fn().mockResolvedValue(undefined),
      getObject: vi.fn().mockImplementation(async () => {
        async function* gen() {
          yield Buffer.from('fake-pdf');
        }
        return gen();
      }),
    },
  };
});

vi.mock('@/lib/socket/server', () => ({
  emitToRoom: vi.fn(),
}));

vi.mock('@/lib/pdf/fill-eoi-form', () => ({
  generateEoiPdfFromTemplate: vi
    .fn()
    .mockResolvedValue(new Uint8Array(Buffer.from('fake-eoi-pdf'))),
  loadEoiTemplatePdf: vi.fn(),
  fillEoiFormFields: vi.fn(),
}));

vi.mock('@/lib/audit', () => ({
  createAuditLog: vi.fn().mockResolvedValue(undefined),
}));

// ─── Helpers ──────────────────────────────────────────────────────────────────

async function insertInApptemplate(portId: string) {
  const [row] = await db
    .insert(documentTemplates)
    .values({
      portId,
      name: 'Test EOI',
      templateType: 'eoi',
      bodyHtml: '<p>Hello {{client.fullName}} for berth {{berth.mooringNumber}}</p>',
      createdBy: 'test',
    })
    .returning();
  return row!;
}

async function insertInterest(args: {
  portId: string;
  clientId: string;
  yachtId: string;
  berthId: string;
}) {
  const [row] = await db
    .insert(interestsTable)
    .values({
      portId: args.portId,
      clientId: args.clientId,
      yachtId: args.yachtId,
      pipelineStage: 'open',
    })
    .returning();
  // Plan §3.4: legacy interests.berth_id replaced by the junction.
  await db.insert(interestBerthsTable).values({
    interestId: row!.id,
    berthId: args.berthId,
    isPrimary: true,
    isSpecificInterest: true,
  });
  return row!;
}

// ─── Setup ────────────────────────────────────────────────────────────────────

let setup: {
  portId: string;
  clientId: string;
  yachtId: string;
  berthId: string;
  interestId: string;
  inAppTemplateId: string;
};

let generateAndSign: typeof import('@/lib/services/document-templates').generateAndSign;

beforeAll(async () => {
  ({ generateAndSign } = await import('@/lib/services/document-templates'));

  const port = await makePort();
  const client = await makeClient({
    portId: port.id,
    overrides: { fullName: 'Dual Path Client', nationalityIso: 'US' },
  });
  await db.insert(clientContacts).values({
    clientId: client.id,
    channel: 'email',
    value: 'client@example.com',
    isPrimary: true,
  });
  await db.insert(clientAddresses).values({
    clientId: client.id,
    portId: port.id,
    streetAddress: '1 Wharf Rd',
    city: 'Harbor',
    countryIso: 'US',
    isPrimary: true,
  });

  const yacht = await makeYacht({
    portId: port.id,
    ownerType: 'client',
    ownerId: client.id,
    name: 'Dual Path Yacht',
    overrides: { lengthFt: '45', widthFt: '14', draftFt: '6' },
  });

  const berth = await makeBerth({
    portId: port.id,
    overrides: { mooringNumber: 'DP-1' },
  });

  const interest = await insertInterest({
    portId: port.id,
    clientId: client.id,
    yachtId: yacht.id,
    berthId: berth.id,
  });

  const template = await insertInApptemplate(port.id);

  // Seed the per-port `eoi_signers` legacy blob so getPortEoiSigners returns
  // a developer/approver pair. The env-to-admin migration retired the
  // hardcoded DEFAULT_DEVELOPER_NAME constants, so tests that exercise
  // auto-derived signers must seed the settings themselves.
  await db.insert(systemSettings).values({
    portId: port.id,
    key: 'eoi_signers',
    value: {
      developer: { name: 'David Mizrahi', email: 'dm@portnimara.com' },
      approver: { name: 'Abbie May', email: 'sales@portnimara.com' },
    },
  });

  setup = {
    portId: port.id,
    clientId: client.id,
    yachtId: yacht.id,
    berthId: berth.id,
    interestId: interest.id,
    inAppTemplateId: template.id,
  };
});

beforeEach(() => {
  vi.clearAllMocks();
});

const meta = {
  userId: 'test-user',
  portId: '',
  ipAddress: '127.0.0.1',
  userAgent: 'vitest',
};

// ─── Pathway: inapp ───────────────────────────────────────────────────────────

describe('generateAndSign - inapp pathway', () => {
  it('generates PDF via pdfme, uploads to MinIO, and sends to Documenso', async () => {
    const client = await import('@/lib/services/documenso-client');
    vi.mocked(client.createDocument).mockResolvedValue({
      id: 'documenso-inapp-123',
      status: 'PENDING',
      numericId: null,
      recipients: [],
    });
    vi.mocked(client.sendDocument).mockResolvedValue({
      id: 'documenso-inapp-123',
      status: 'PENDING',
      numericId: null,
      recipients: [],
    });

    const result = await generateAndSign(
      setup.inAppTemplateId,
      setup.portId,
      { clientId: setup.clientId, interestId: setup.interestId },
      [{ name: 'Client', email: 'client@example.com', role: 'signer', signingOrder: 1 }],
      'inapp',
      { ...meta, portId: setup.portId },
    );

    expect(result.file).not.toBeNull();
    expect(client.createDocument).toHaveBeenCalledOnce();
    expect(client.sendDocument).toHaveBeenCalledWith('documenso-inapp-123');

    const [docRow] = await db
      .select()
      .from(documents)
      .where(eq(documents.id, (result.document as { id: string }).id));
    expect(docRow?.documensoId).toBe('documenso-inapp-123');
    expect(docRow?.status).toBe('sent');
    expect(docRow?.fileId).toBeTruthy();
  });

  it('auto-derives signers for EOI templates when none are provided', async () => {
    const client = await import('@/lib/services/documenso-client');
    vi.mocked(client.createDocument).mockResolvedValue({
      id: 'doc-auto-signers',
      status: 'PENDING',
      numericId: null,
      recipients: [],
    });
    vi.mocked(client.sendDocument).mockResolvedValue({
      id: 'doc-auto-signers',
      status: 'PENDING',
      numericId: null,
      recipients: [],
    });

    await generateAndSign(
      setup.inAppTemplateId,
      setup.portId,
      { clientId: setup.clientId, interestId: setup.interestId },
      [],
      'inapp',
      { ...meta, portId: setup.portId },
    );

    expect(client.createDocument).toHaveBeenCalledOnce();
    const recipients = vi.mocked(client.createDocument).mock.calls[0]![2];
    expect(recipients).toHaveLength(3);
    expect(recipients[0]?.name).toBe('Dual Path Client');
    expect(recipients[1]?.name).toBe('David Mizrahi');
    expect(recipients[2]?.role).toBe('approver');
  });

  it('throws ValidationError when non-EOI template has no signers', async () => {
    const [other] = await db
      .insert(documentTemplates)
      .values({
        portId: setup.portId,
        name: 'Plain Letter',
        templateType: 'welcome_letter',
        bodyHtml: '<p>x</p>',
        createdBy: 'test',
      })
      .returning();

    await expect(
      generateAndSign(other!.id, setup.portId, { clientId: setup.clientId }, [], 'inapp', {
        ...meta,
        portId: setup.portId,
      }),
    ).rejects.toThrow(ValidationError);
  });

  it('throws ValidationError when templateId is null', async () => {
    await expect(
      generateAndSign(
        null,
        setup.portId,
        { clientId: setup.clientId, interestId: setup.interestId },
        [{ name: 'X', email: 'x@x.com', role: 'signer', signingOrder: 1 }],
        'inapp',
        { ...meta, portId: setup.portId },
      ),
    ).rejects.toThrow(ValidationError);
  });

  it('uses the EOI source-PDF path for templateType=eoi', async () => {
    const fillModule = await import('@/lib/pdf/fill-eoi-form');
    const client = await import('@/lib/services/documenso-client');
    vi.mocked(client.createDocument).mockResolvedValue({
      id: 'doc-eoi-pdf',
      status: 'PENDING',
      numericId: null,
      recipients: [],
    });
    vi.mocked(client.sendDocument).mockResolvedValue({
      id: 'doc-eoi-pdf',
      status: 'PENDING',
      numericId: null,
      recipients: [],
    });

    await generateAndSign(
      setup.inAppTemplateId,
      setup.portId,
      { clientId: setup.clientId, interestId: setup.interestId },
      [{ name: 'C', email: 'c@x.com', role: 'signer', signingOrder: 1 }],
      'inapp',
      { ...meta, portId: setup.portId },
    );

    expect(fillModule.generateEoiPdfFromTemplate).toHaveBeenCalled();
  });

  it('rejects non-EOI template types on the inapp pathway', async () => {
    // Non-EOI in-app PDF rendering was removed in the PDF stack overhaul
    // (see docs/superpowers/specs/2026-05-12-pdf-stack-overhaul-design.md).
    // The only surviving in-app pathway is EOI via pdf-lib AcroForm fill.
    const [other] = await db
      .insert(documentTemplates)
      .values({
        portId: setup.portId,
        name: 'Welcome Letter',
        templateType: 'welcome_letter',
        bodyHtml: '<p>Welcome {{client.fullName}}</p>',
        createdBy: 'test',
      })
      .returning();

    await expect(
      generateAndSign(
        other!.id,
        setup.portId,
        { clientId: setup.clientId },
        [{ name: 'C', email: 'c@x.com', role: 'signer', signingOrder: 1 }],
        'inapp',
        { ...meta, portId: setup.portId },
      ),
    ).rejects.toThrow(/welcome_letter/);
  });
});

// ─── Pathway: documenso-template ──────────────────────────────────────────────

describe('generateAndSign - documenso-template pathway', () => {
  it('calls Documenso template-generate endpoint and records a documents row', async () => {
    const client = await import('@/lib/services/documenso-client');
    vi.mocked(client.generateDocumentFromTemplate).mockResolvedValue({
      id: 'documenso-template-456',
      status: 'PENDING',
      numericId: null,
      recipients: [],
    });

    const result = await generateAndSign(
      null,
      setup.portId,
      { clientId: setup.clientId, interestId: setup.interestId },
      [],
      'documenso-template',
      { ...meta, portId: setup.portId },
    );

    expect(result.file).toBeNull();
    expect(client.generateDocumentFromTemplate).toHaveBeenCalledOnce();
    const [templateArg, payloadArg] = vi.mocked(client.generateDocumentFromTemplate).mock.calls[0]!;
    expect(typeof templateArg).toBe('number');
    expect(payloadArg).toMatchObject({
      externalId: `loi-${setup.interestId}`,
      formValues: {
        Name: 'Dual Path Client',
        'Yacht Name': 'Dual Path Yacht',
        'Berth Number': 'DP-1',
      },
    });

    const [docRow] = await db
      .select()
      .from(documents)
      .where(eq(documents.id, (result.document as { id: string }).id));
    expect(docRow?.documensoId).toBe('documenso-template-456');
    expect(docRow?.status).toBe('sent');
    expect(docRow?.documentType).toBe('eoi');
    expect(docRow?.interestId).toBe(setup.interestId);
    expect(docRow?.fileId).toBeNull();
  });

  it('throws ValidationError when interestId is missing', async () => {
    await expect(
      generateAndSign(null, setup.portId, { clientId: setup.clientId }, [], 'documenso-template', {
        ...meta,
        portId: setup.portId,
      }),
    ).rejects.toThrow(ValidationError);
  });

  it('does NOT call createDocument / sendDocument / minio for this pathway', async () => {
    const client = await import('@/lib/services/documenso-client');
    vi.mocked(client.generateDocumentFromTemplate).mockResolvedValue({
      id: 'documenso-template-789',
      status: 'PENDING',
      numericId: null,
      recipients: [],
    });

    await generateAndSign(
      null,
      setup.portId,
      { clientId: setup.clientId, interestId: setup.interestId },
      [],
      'documenso-template',
      { ...meta, portId: setup.portId },
    );

    expect(client.createDocument).not.toHaveBeenCalled();
    expect(client.sendDocument).not.toHaveBeenCalled();
  });
});
