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

/**
 * Signing-automation orchestrator validation tests. Heavy integration
 * paths (actual Documenso round-trip, real signer dispatch) are
 * exercised by realapi Playwright specs. These tests pin the input-
 * validation contract: refuses to enable on completed / cancelled /
 * already-automated documents, refuses when documensoId is null,
 * refuses with fewer than two signers.
 */

// vi.hoisted lets us declare mock helpers that the hoisted vi.mock
// factory can reference safely — without hoisted, the factory sees
// undefined for any top-level const.
const mocks = vi.hoisted(() => ({
  docFindFirst: vi.fn(),
  signersFindFirst: vi.fn(),
  updateCall: vi.fn(() => ({ set: vi.fn(() => ({ where: vi.fn() })) })),
  selectCall: vi.fn(() => ({
    from: vi.fn(() => ({
      where: vi.fn(() => ({ orderBy: vi.fn(() => Promise.resolve([])) })),
    })),
  })),
}));

vi.mock('@/lib/db', () => ({
  db: {
    query: {
      documents: { findFirst: mocks.docFindFirst },
      documentSigners: { findFirst: mocks.signersFindFirst },
      ports: { findFirst: vi.fn().mockResolvedValue({ id: 'port-1', name: 'Test Port' }) },
    },
    update: mocks.updateCall,
    select: mocks.selectCall,
  },
}));

vi.mock('@/lib/services/port-config', () => ({
  getPortDocumensoConfig: vi
    .fn()
    .mockResolvedValue({ signingOrder: 'SEQUENTIAL', developerName: 'Dev' }),
}));

vi.mock('@/lib/services/documenso-client', () => ({
  getDocument: vi
    .fn()
    .mockResolvedValue({
      id: 'env-1',
      status: 'PENDING',
      recipients: [],
      signingOrder: 'SEQUENTIAL',
    }),
  distributeEnvelopeV2: vi
    .fn()
    .mockResolvedValue({
      id: 'env-1',
      status: 'PENDING',
      recipients: [],
      signingOrder: 'SEQUENTIAL',
    }),
}));

vi.mock('@/lib/services/document-signing-emails.service', () => ({
  sendSigningInvitation: vi.fn().mockResolvedValue(undefined),
}));

vi.mock('@/lib/services/documenso-signers', () => ({
  DOC_TYPE_LABEL: { eoi: 'EOI', contract: 'Contract', reservation_agreement: 'Reservation' },
}));

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

import {
  enableSigningAutomation,
  disableSigningAutomation,
} from '@/lib/services/signing-automation.service';

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

describe('enableSigningAutomation', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('throws NotFound when document does not exist', async () => {
    mocks.docFindFirst.mockResolvedValue(null);
    await expect(enableSigningAutomation('doc-1', 'port-1', meta)).rejects.toThrow(/document/i);
  });

  it('throws Conflict when document has no Documenso envelope', async () => {
    mocks.docFindFirst.mockResolvedValue({
      id: 'doc-1',
      documensoId: null,
      status: 'draft',
      automationMode: 'manual',
      documentType: 'eoi',
      invitationMessage: null,
    });
    await expect(enableSigningAutomation('doc-1', 'port-1', meta)).rejects.toThrow(
      /no Documenso envelope/i,
    );
  });

  it('throws Conflict when document is completed', async () => {
    mocks.docFindFirst.mockResolvedValue({
      id: 'doc-1',
      documensoId: 'env-1',
      status: 'completed',
      automationMode: 'manual',
      documentType: 'eoi',
      invitationMessage: null,
    });
    await expect(enableSigningAutomation('doc-1', 'port-1', meta)).rejects.toThrow(/completed/);
  });

  it('throws Conflict when automation is already enabled', async () => {
    mocks.docFindFirst.mockResolvedValue({
      id: 'doc-1',
      documensoId: 'env-1',
      status: 'sent',
      automationMode: 'sequential_auto',
      documentType: 'eoi',
      invitationMessage: null,
    });
    await expect(enableSigningAutomation('doc-1', 'port-1', meta)).rejects.toThrow(
      /already enabled/i,
    );
  });

  it('throws Conflict when fewer than two signers exist', async () => {
    mocks.docFindFirst.mockResolvedValue({
      id: 'doc-1',
      documensoId: 'env-1',
      status: 'sent',
      automationMode: 'manual',
      documentType: 'eoi',
      invitationMessage: null,
    });
    // mockSelect.from.where.orderBy resolves to [] -> ensureSigningUrls
    // returns 0 signers -> service throws "at least two".
    await expect(enableSigningAutomation('doc-1', 'port-1', meta)).rejects.toThrow(
      /at least two signers/i,
    );
  });
});

describe('disableSigningAutomation', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('throws NotFound when document does not exist', async () => {
    mocks.docFindFirst.mockResolvedValue(null);
    await expect(disableSigningAutomation('doc-1', 'port-1', meta)).rejects.toThrow(/document/i);
  });

  it('is a no-op when document is already manual (idempotent)', async () => {
    mocks.docFindFirst.mockResolvedValue({ id: 'doc-1', automationMode: 'manual' });
    await expect(disableSigningAutomation('doc-1', 'port-1', meta)).resolves.toBeUndefined();
    // No update should fire when already manual.
    expect(mocks.updateCall).not.toHaveBeenCalled();
  });
});
