/**
 * lib/services/route53-write-persistence.ts unit tests — SQL contract checks
 * against a mocked postgresClient. No real Postgres connection is made.
 * Follows the mocking discipline in lib/services/pax8-sync-service.test.ts.
 */

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

// Mock postgresClient BEFORE importing the module under test.
const queryMock = vi.fn();
vi.mock('@/lib/services/postgres-client', () => ({
  default: {
    query: (...args: unknown[]) => queryMock(...args),
  },
}));

// Import AFTER the mock is declared so vi.mock hoisting takes effect.
import {
  createPendingAuditLog,
  markAuditCommitted,
  markAuditFailed,
  insertPulseCrudHistory,
  upsertMirrorRecord,
  softDeleteMirrorRecord,
  loadMirrorRecord,
} from './route53-write-persistence';

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

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

beforeEach(() => {
  queryMock.mockReset();
});

describe('createPendingAuditLog', () => {
  it("issues an INSERT into route53_audit_log whose SQL contains 'pending'", async () => {
    queryMock.mockResolvedValueOnce({ rows: [{ id: 'audit-1' }] });
    const result = await createPendingAuditLog({
      operation: 'create',
      zoneId: 'Z123',
      recordKey: 'Z123:www.example.com.:A:',
      recordName: 'www.example.com.',
      recordType: 'A',
      beforeValue: null,
      afterValue: { ttl: 300, resourceRecords: [{ value: '1.2.3.4' }] },
      performedByUserId: 'user-1',
      performedByEmail: 'lorentz@wulfconsulting.com',
    });
    expect(result).toEqual({ id: 'audit-1' });
    expect(calls()).toHaveLength(1);
    expect(calls()[0].sql).toContain('INSERT INTO route53_audit_log');
    expect(calls()[0].sql).toContain("'pending'");
  });
});

describe('markAuditCommitted', () => {
  it("issues an UPDATE setting status = 'committed'", async () => {
    queryMock.mockResolvedValueOnce({ rows: [] });
    await markAuditCommitted('audit-1', 'change-1', 'INSYNC', { ResponseMetadata: {} });
    expect(calls()[0].sql).toContain('UPDATE route53_audit_log');
    expect(calls()[0].sql).toContain("'committed'");
  });
});

describe('markAuditFailed', () => {
  it('passes a sanitized error string — a raw AWS key id never reaches the bound parameters', async () => {
    queryMock.mockResolvedValueOnce({ rows: [] });
    const rawError = new Error('AccessDenied for AKIAIOSFODNN7EXAMPLE');
    await markAuditFailed('audit-1', rawError);

    expect(calls()).toHaveLength(1);
    expect(calls()[0].sql).toContain("'failed'");
    const boundParams = calls()[0].params;
    expect(boundParams).not.toContain(rawError);
    for (const param of boundParams) {
      if (typeof param === 'string') {
        expect(param).not.toContain('AKIAIOSFODNN7EXAMPLE');
      }
    }
  });
});

describe('insertPulseCrudHistory', () => {
  it("binds the literal 'pulse_crud' as the history source", async () => {
    queryMock.mockResolvedValueOnce({ rows: [] });
    await insertPulseCrudHistory({
      zoneId: 'Z123',
      recordKey: 'Z123:www.example.com.:A:',
      recordName: 'www.example.com.',
      recordType: 'A',
      changeAction: 'create',
      beforeValue: null,
      afterValue: { ttl: 300 },
      changedByUserId: 'user-1',
      changedByEmail: 'lorentz@wulfconsulting.com',
      auditLogId: 'audit-1',
    });
    expect(calls()[0].sql).toContain('INSERT INTO route53_record_history');
    expect(calls()[0].sql).toContain("'pulse_crud'");
  });
});

describe('upsertMirrorRecord', () => {
  it('issues an INSERT ... ON CONFLICT upsert and never throws even if the query rejects', async () => {
    queryMock.mockRejectedValueOnce(new Error('connection reset'));
    await expect(
      upsertMirrorRecord({
        recordKey: 'Z123:www.example.com.:A:',
        zoneId: 'Z123',
        name: 'www.example.com.',
        type: 'A',
        setIdentifier: null,
        ttl: 300,
        resourceRecords: [{ value: '1.2.3.4' }],
        aliasTarget: null,
        rawPayload: null,
      })
    ).resolves.toBeUndefined();
  });
});

describe('softDeleteMirrorRecord', () => {
  it('issues an UPDATE and never a DELETE FROM', async () => {
    queryMock.mockResolvedValueOnce({ rows: [] });
    await softDeleteMirrorRecord('Z123:www.example.com.:A:');
    expect(calls()[0].sql).toContain('UPDATE route53_records');
    expect(calls()[0].sql).not.toContain('DELETE FROM');
  });

  it('never throws even if the query rejects (best-effort)', async () => {
    queryMock.mockRejectedValueOnce(new Error('connection reset'));
    await expect(softDeleteMirrorRecord('Z123:www.example.com.:A:')).resolves.toBeUndefined();
  });
});

describe('loadMirrorRecord', () => {
  it('returns a camelCase object when found', async () => {
    queryMock.mockResolvedValueOnce({
      rowCount: 1,
      rows: [
        {
          record_key: 'Z123:www.example.com.:A:',
          zone_id: 'Z123',
          name: 'www.example.com.',
          type: 'A',
          set_identifier: null,
          ttl: 300,
          resource_records: [{ value: '1.2.3.4' }],
          alias_target: null,
        },
      ],
    });
    const result = await loadMirrorRecord('Z123:www.example.com.:A:');
    expect(result).toEqual({
      recordKey: 'Z123:www.example.com.:A:',
      zoneId: 'Z123',
      name: 'www.example.com.',
      type: 'A',
      setIdentifier: null,
      ttl: 300,
      resourceRecords: [{ value: '1.2.3.4' }],
      aliasTarget: null,
    });
  });

  it('returns null when not found', async () => {
    queryMock.mockResolvedValueOnce({ rowCount: 0, rows: [] });
    const result = await loadMirrorRecord('missing-key');
    expect(result).toBeNull();
  });
});
