/**
 * pax8-company-match-resolver.ts unit tests.
 *
 * The resolver takes `tx` as a parameter (it runs inside
 * postgresClient.transaction() at the call site), so no module mock is
 * needed here — a hand-rolled mock tx `{ query: vi.fn() }` is scripted per
 * test to return the sequenced results, and assertions check the SQL
 * strings + bound params of each `query` call plus the returned
 * ResolveResult, matching the mocking discipline of
 * pax8-company-matcher.test.ts.
 */

import { describe, it, expect, vi } from 'vitest';
import { resolvePax8CompanyMatch } from './pax8-company-match-resolver';

interface MockCall {
  sql: string;
  params: unknown[];
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type QueryMock = ReturnType<typeof vi.fn<(...args: any[]) => Promise<{ rows: unknown[]; rowCount: number }>>>;

function makeTx(queryMock: QueryMock) {
  return {
    query: <T = unknown>(sql: string, params?: unknown[]) =>
      queryMock(sql, params) as Promise<{ rows: T[]; rowCount: number }>,
  };
}

function calls(queryMock: QueryMock): MockCall[] {
  return queryMock.mock.calls.map(([sql, params]) => ({
    sql: String(sql),
    params: (params as unknown[]) ?? [],
  }));
}

describe('resolvePax8CompanyMatch', () => {
  it('writes both pax8_companies and pax8_company_match_review on success', async () => {
    const queryMock = vi.fn();
    // 1. review SELECT ... FOR UPDATE
    queryMock.mockResolvedValueOnce({
      rows: [{ pax8_company_id: 'pax8-uuid-1', resolved_at: null }],
      rowCount: 1,
    });
    // 2. company existence/active check
    queryMock.mockResolvedValueOnce({ rows: [{ '?column?': 1 }], rowCount: 1 });
    // 3. UPDATE pax8_companies
    queryMock.mockResolvedValueOnce({ rows: [], rowCount: 1 });
    // 4. UPDATE pax8_company_match_review
    queryMock.mockResolvedValueOnce({ rows: [], rowCount: 1 });

    const tx = makeTx(queryMock);
    const result = await resolvePax8CompanyMatch(tx, {
      reviewId: 'review-uuid-1',
      companyId: 42,
      note: 'looks right',
      userId: 'user-1',
    });

    expect(result).toEqual({ ok: true, resolvedToCompanyId: 42 });

    const c = calls(queryMock);
    expect(c).toHaveLength(4);
    expect(c[2].sql).toMatch(/UPDATE\s+pax8_companies/i);
    expect(c[2].sql).toMatch(/match_method\s*=\s*'manual'/i);
    expect(c[2].params).toEqual([ 'pax8-uuid-1', 42 ]);
    expect(c[3].sql).toMatch(/UPDATE\s+pax8_company_match_review/i);
    expect(c[3].sql).toMatch(/resolved_at\s*=\s*NOW\(\)/i);
    expect(c[3].params).toEqual(['review-uuid-1', 'user-1', 42, 'looks right']);
  });

  it('returns not_found and issues no updates when the review row is missing', async () => {
    const queryMock = vi.fn();
    queryMock.mockResolvedValueOnce({ rows: [], rowCount: 0 });

    const tx = makeTx(queryMock);
    const result = await resolvePax8CompanyMatch(tx, {
      reviewId: 'missing-review',
      companyId: 42,
      note: null,
      userId: 'user-1',
    });

    expect(result).toEqual({
      ok: false,
      code: 'not_found',
      message: expect.any(String),
    });
    expect(queryMock).toHaveBeenCalledTimes(1);
  });

  it('returns already_resolved and issues no updates when resolved_at is set', async () => {
    const queryMock = vi.fn();
    queryMock.mockResolvedValueOnce({
      rows: [{ pax8_company_id: 'pax8-uuid-1', resolved_at: '2026-01-01T00:00:00.000Z' }],
      rowCount: 1,
    });

    const tx = makeTx(queryMock);
    const result = await resolvePax8CompanyMatch(tx, {
      reviewId: 'review-uuid-1',
      companyId: 42,
      note: null,
      userId: 'user-1',
    });

    expect(result).toEqual({
      ok: false,
      code: 'already_resolved',
      message: expect.any(String),
    });
    expect(queryMock).toHaveBeenCalledTimes(1);
  });

  it('returns company_not_found and issues no pax8_companies write when target does not exist/is inactive', async () => {
    const queryMock = vi.fn();
    queryMock.mockResolvedValueOnce({
      rows: [{ pax8_company_id: 'pax8-uuid-1', resolved_at: null }],
      rowCount: 1,
    });
    queryMock.mockResolvedValueOnce({ rows: [], rowCount: 0 });

    const tx = makeTx(queryMock);
    const result = await resolvePax8CompanyMatch(tx, {
      reviewId: 'review-uuid-1',
      companyId: 999,
      note: null,
      userId: 'user-1',
    });

    expect(result).toEqual({
      ok: false,
      code: 'company_not_found',
      message: expect.any(String),
    });
    expect(queryMock).toHaveBeenCalledTimes(2);
  });

  it('resolves successfully with a companyId NOT in candidate_company_ids (manual-search / zero-candidate case)', async () => {
    const queryMock = vi.fn();
    queryMock.mockResolvedValueOnce({
      rows: [{ pax8_company_id: 'pax8-uuid-1', resolved_at: null }],
      rowCount: 1,
    });
    queryMock.mockResolvedValueOnce({ rows: [{ '?column?': 1 }], rowCount: 1 });
    queryMock.mockResolvedValueOnce({ rows: [], rowCount: 1 });
    queryMock.mockResolvedValueOnce({ rows: [], rowCount: 1 });

    const tx = makeTx(queryMock);
    // companyId 777 was never a member of candidate_company_ids for this
    // review — resolver must not check membership at all.
    const result = await resolvePax8CompanyMatch(tx, {
      reviewId: 'review-uuid-1',
      companyId: 777,
      note: null,
      userId: 'user-1',
    });

    expect(result).toEqual({ ok: true, resolvedToCompanyId: 777 });
  });
});
