/**
 * lib/services/route53-record-key.ts unit tests.
 *
 * Pure functions, no mocking required — no `pg`, no AWS client construction.
 */

import { describe, it, expect } from 'vitest';
import {
  buildRecordKey,
  normalizeRecordSet,
  recordSetsEqual,
  classifyDrift,
  toHistoryPayload,
  type NormalizedRecordSet,
} from './route53-record-key';
import type { ResourceRecordSet } from '@aws-sdk/client-route-53';

describe('buildRecordKey', () => {
  it('builds a key with an empty setIdentifier segment when none is given', () => {
    expect(
      buildRecordKey({ zoneId: 'Z123', name: 'www.example.com.', type: 'A', setIdentifier: null })
    ).toBe('Z123:www.example.com.:A:');
  });

  it('appends a non-null setIdentifier after the final colon', () => {
    expect(
      buildRecordKey({ zoneId: 'Z123', name: 'www.example.com.', type: 'A', setIdentifier: 'primary' })
    ).toBe('Z123:www.example.com.:A:primary');
  });
});

describe('normalizeRecordSet', () => {
  it('lowercases name, preserves trailing dot, uppercases type, coerces missing TTL to null, sorts resourceRecords', () => {
    const rs: ResourceRecordSet = {
      Name: 'WWW.Example.COM.',
      Type: 'a' as ResourceRecordSet['Type'],
      ResourceRecords: [{ Value: '10.0.0.2' }, { Value: '10.0.0.1' }],
    };
    const normalized = normalizeRecordSet(rs, 'Z123');

    expect(normalized.name).toBe('www.example.com.');
    expect(normalized.type).toBe('A');
    expect(normalized.ttl).toBeNull();
    expect(normalized.resourceRecords).toEqual([{ value: '10.0.0.1' }, { value: '10.0.0.2' }]);
  });

  it('normalizes an alias record with no TTL/ResourceRecords but a populated AliasTarget', () => {
    const rs: ResourceRecordSet = {
      Name: 'alias.example.com.',
      Type: 'A' as ResourceRecordSet['Type'],
      AliasTarget: {
        HostedZoneId: 'Z2FDTNDATAQYW2',
        DNSName: 'd123.cloudfront.net.',
        EvaluateTargetHealth: false,
      },
    };
    const normalized = normalizeRecordSet(rs, 'Z123');

    expect(normalized.ttl).toBeNull();
    expect(normalized.resourceRecords).toEqual([]);
    expect(normalized.aliasTarget).toEqual({
      HostedZoneId: 'Z2FDTNDATAQYW2',
      DNSName: 'd123.cloudfront.net.',
      EvaluateTargetHealth: false,
    });
  });
});

describe('recordSetsEqual', () => {
  const base: NormalizedRecordSet = {
    recordKey: 'Z123:www.example.com.:A:',
    zoneId: 'Z123',
    name: 'www.example.com.',
    type: 'A',
    setIdentifier: null,
    ttl: 300,
    resourceRecords: [{ value: '10.0.0.1' }, { value: '10.0.0.2' }],
    aliasTarget: null,
  };

  it('is true when compared with itself', () => {
    expect(recordSetsEqual(base, base)).toBe(true);
  });

  it('is false when a single resourceRecords value changes', () => {
    const changed: NormalizedRecordSet = {
      ...base,
      resourceRecords: [{ value: '10.0.0.1' }, { value: '10.0.0.3' }],
    };
    expect(recordSetsEqual(base, changed)).toBe(false);
  });

  it('is true when only the order of resourceRecords changes', () => {
    const reordered: NormalizedRecordSet = {
      ...base,
      resourceRecords: [{ value: '10.0.0.2' }, { value: '10.0.0.1' }],
    };
    // Both are pre-sorted by normalizeRecordSet in real use; simulate that
    // order doesn't matter by sorting here too, matching normalization.
    const sorted = [...reordered.resourceRecords].sort((a, b) => a.value.localeCompare(b.value));
    expect(recordSetsEqual(base, { ...reordered, resourceRecords: sorted })).toBe(true);
  });

  it('is false when TTL differs', () => {
    const changed: NormalizedRecordSet = { ...base, ttl: 600 };
    expect(recordSetsEqual(base, changed)).toBe(false);
  });

  it('is true when both are null', () => {
    expect(recordSetsEqual(null, null)).toBe(true);
  });

  it('is false when only one side is null', () => {
    expect(recordSetsEqual(base, null)).toBe(false);
    expect(recordSetsEqual(null, base)).toBe(false);
  });
});

describe('classifyDrift', () => {
  const prev: NormalizedRecordSet = {
    recordKey: 'Z123:www.example.com.:A:',
    zoneId: 'Z123',
    name: 'www.example.com.',
    type: 'A',
    setIdentifier: null,
    ttl: 300,
    resourceRecords: [{ value: '10.0.0.1' }],
    aliasTarget: null,
  };

  it('returns create when prev is null', () => {
    expect(classifyDrift(null, prev)).toBe('create');
  });

  it('returns delete when next is null', () => {
    expect(classifyDrift(prev, null)).toBe('delete');
  });

  it('returns update when normalized sets differ', () => {
    const next: NormalizedRecordSet = { ...prev, ttl: 600 };
    expect(classifyDrift(prev, next)).toBe('update');
  });

  it('returns null when normalized sets are equal (no history row)', () => {
    expect(classifyDrift(prev, { ...prev })).toBeNull();
  });
});

describe('toHistoryPayload', () => {
  it('returns null for a null input', () => {
    expect(toHistoryPayload(null)).toBeNull();
  });

  it('returns the whole-recordset snapshot shape for a non-null input', () => {
    const ns: NormalizedRecordSet = {
      recordKey: 'Z123:www.example.com.:A:',
      zoneId: 'Z123',
      name: 'www.example.com.',
      type: 'A',
      setIdentifier: null,
      ttl: 300,
      resourceRecords: [{ value: '10.0.0.1' }],
      aliasTarget: null,
    };
    expect(toHistoryPayload(ns)).toEqual({
      name: 'www.example.com.',
      type: 'A',
      setIdentifier: null,
      ttl: 300,
      resourceRecords: [{ value: '10.0.0.1' }],
      aliasTarget: null,
    });
  });
});
