/**
 * lib/services/route53-record-validation.ts unit tests — D-01 allowlist
 * enforcement and AWS error sanitization (T-24-03). Pure logic, no AWS SDK
 * or Postgres dependency to mock.
 */

import { describe, it, expect } from 'vitest';
import {
  WRITABLE_RECORD_TYPES,
  validateRecordWrite,
  sanitizeAwsError,
} from './route53-record-validation';

describe('WRITABLE_RECORD_TYPES', () => {
  it('is exactly the six D-01 writable types, never NS or SOA', () => {
    expect([...WRITABLE_RECORD_TYPES].sort()).toEqual(
      ['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT'].sort()
    );
    expect(WRITABLE_RECORD_TYPES).not.toContain('NS');
    expect(WRITABLE_RECORD_TYPES).not.toContain('SOA');
  });
});

describe('validateRecordWrite', () => {
  const basePayload = {
    name: 'www.example.com',
    resourceRecords: [{ value: '1.2.3.4' }],
  };

  it('rejects NS with a reason naming it as a zone-delegation record', () => {
    const result = validateRecordWrite({ ...basePayload, type: 'NS' });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.status).toBe(400);
      expect(result.reason).toMatch(/NS/);
      expect(result.reason).toMatch(/delegation/i);
    }
  });

  it('rejects SOA', () => {
    const result = validateRecordWrite({ ...basePayload, type: 'SOA' });
    expect(result.ok).toBe(false);
    if (!result.ok) expect(result.status).toBe(400);
  });

  it('rejects lowercase "ns" case-insensitively', () => {
    const result = validateRecordWrite({ ...basePayload, type: 'ns' });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.status).toBe(400);
      expect(result.reason).toMatch(/delegation/i);
    }
  });

  it.each(['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'SRV'])(
    'accepts well-formed %s payload',
    (type) => {
      const result = validateRecordWrite({ ...basePayload, type });
      expect(result.ok).toBe(true);
      if (result.ok) {
        expect(result.value.type).toBe(type);
        expect(result.value.name).toBe('www.example.com.');
        expect(result.value.ttl).toBe(300);
      }
    }
  );

  it('rejects an unknown type such as CAA (closed allowlist, not a blocklist)', () => {
    const result = validateRecordWrite({ ...basePayload, type: 'CAA' });
    expect(result.ok).toBe(false);
    if (!result.ok) expect(result.status).toBe(400);
  });

  it('rejects an unknown type such as DS', () => {
    const result = validateRecordWrite({ ...basePayload, type: 'DS' });
    expect(result.ok).toBe(false);
    if (!result.ok) expect(result.status).toBe(400);
  });

  it('rejects a missing name', () => {
    const result = validateRecordWrite({ type: 'A', resourceRecords: [{ value: '1.2.3.4' }] });
    expect(result.ok).toBe(false);
    if (!result.ok) expect(result.status).toBe(400);
  });

  it('rejects an empty-string name', () => {
    const result = validateRecordWrite({ ...basePayload, name: '   ', type: 'A' });
    expect(result.ok).toBe(false);
    if (!result.ok) expect(result.status).toBe(400);
  });

  it('rejects a non-integer ttl', () => {
    const result = validateRecordWrite({ ...basePayload, type: 'A', ttl: 3.5 });
    expect(result.ok).toBe(false);
    if (!result.ok) expect(result.status).toBe(400);
  });

  it('rejects a ttl outside 0..2147483647', () => {
    const tooHigh = validateRecordWrite({ ...basePayload, type: 'A', ttl: 2147483648 });
    expect(tooHigh.ok).toBe(false);
    const tooLow = validateRecordWrite({ ...basePayload, type: 'A', ttl: -1 });
    expect(tooLow.ok).toBe(false);
  });

  it('accepts ttl of exactly 0 and exactly 2147483647', () => {
    const min = validateRecordWrite({ ...basePayload, type: 'A', ttl: 0 });
    expect(min.ok).toBe(true);
    const max = validateRecordWrite({ ...basePayload, type: 'A', ttl: 2147483647 });
    expect(max.ok).toBe(true);
  });

  it('rejects an empty resourceRecords array (Route 53 rejects an empty value set)', () => {
    const result = validateRecordWrite({ name: 'www.example.com', type: 'A', resourceRecords: [] });
    expect(result.ok).toBe(false);
    if (!result.ok) expect(result.status).toBe(400);
  });

  it('rejects a resourceRecords entry with an empty-string value', () => {
    const result = validateRecordWrite({
      name: 'www.example.com',
      type: 'A',
      resourceRecords: [{ value: '' }],
    });
    expect(result.ok).toBe(false);
    if (!result.ok) expect(result.status).toBe(400);
  });

  it('caps resourceRecords at 100 entries', () => {
    const tooMany = Array.from({ length: 101 }, (_, i) => ({ value: `10.0.0.${i % 256}` }));
    const result = validateRecordWrite({ name: 'www.example.com', type: 'A', resourceRecords: tooMany });
    expect(result.ok).toBe(false);
    if (!result.ok) expect(result.status).toBe(400);
  });
});

describe('sanitizeAwsError', () => {
  it('strips AWS access key ids, ARNs, and account ids, then truncates to 500 chars', () => {
    const err = new Error(
      'AccessDenied for AKIAIOSFODNN7EXAMPLE on arn:aws:route53:::hostedzone/Z123 account 123456789012'
    );
    const sanitized = sanitizeAwsError(err);
    expect(sanitized).not.toContain('AKIAIOSFODNN7EXAMPLE');
    expect(sanitized).not.toContain('arn:aws:route53:::hostedzone/Z123');
    expect(sanitized).not.toContain('123456789012');
    expect(sanitized.length).toBeLessThanOrEqual(504);
  });

  it('truncates messages longer than 500 characters', () => {
    const longMessage = 'x'.repeat(1000);
    const sanitized = sanitizeAwsError(new Error(longMessage));
    expect(sanitized.length).toBeLessThanOrEqual(504);
    expect(sanitized.endsWith('...')).toBe(true);
  });

  it('never throws on a non-Error input and returns a string', () => {
    expect(() => sanitizeAwsError('a plain string error')).not.toThrow();
    expect(typeof sanitizeAwsError('a plain string error')).toBe('string');
    expect(() => sanitizeAwsError(undefined)).not.toThrow();
    expect(typeof sanitizeAwsError(undefined)).toBe('string');
    expect(() => sanitizeAwsError({ weird: 'object' })).not.toThrow();
    expect(typeof sanitizeAwsError(null)).toBe('string');
  });
});
