/**
 * Pure, dependency-free helpers for Route 53 record-key derivation,
 * recordset normalization, and drift classification.
 *
 * No `pg` import, no AWS SDK client construction — only types are imported
 * from `@aws-sdk/client-route-53`. This keeps the module unit-testable
 * without mocking anything (see 24-RESEARCH.md Anti-Patterns: no per-value
 * diffing; Route 53 models an update as a whole-recordset replace).
 */

import type { ResourceRecordSet } from '@aws-sdk/client-route-53';

export interface NormalizedRecordSet {
  recordKey: string;
  zoneId: string;
  name: string;
  type: string;
  setIdentifier: string | null;
  ttl: number | null;
  resourceRecords: Array<{ value: string }>;
  aliasTarget: Record<string, unknown> | null;
}

/**
 * Build the `route53_records.record_key` primary key / `recordId` URL
 * segment: `${zoneId}:${name}:${type}:${setIdentifier ?? ''}`.
 */
export function buildRecordKey(input: {
  zoneId: string;
  name: string;
  type: string;
  setIdentifier?: string | null;
}): string {
  return `${input.zoneId}:${input.name}:${input.type}:${input.setIdentifier ?? ''}`;
}

/**
 * Normalize a raw AWS `ResourceRecordSet` into a comparable, stable shape.
 *
 * - `name` is lowercased with its trailing dot preserved
 * - `type` is uppercased
 * - `ttl` is `rs.TTL ?? null` (alias records have no TTL)
 * - `resourceRecords` is sorted by value so AWS's unordered list doesn't
 *   register as drift on every sync
 * - `aliasTarget` is `rs.AliasTarget ?? null`
 */
export function normalizeRecordSet(rs: ResourceRecordSet, zoneId: string): NormalizedRecordSet {
  const name = (rs.Name ?? '').toLowerCase();
  const type = (rs.Type ?? '').toUpperCase();
  const setIdentifier = rs.SetIdentifier ?? null;
  const ttl = rs.TTL ?? null;
  const resourceRecords = (rs.ResourceRecords ?? [])
    .map((r) => ({ value: r.Value ?? '' }))
    .sort((a, b) => a.value.localeCompare(b.value));
  const aliasTarget = (rs.AliasTarget as unknown as Record<string, unknown> | undefined) ?? null;

  return {
    recordKey: buildRecordKey({ zoneId, name, type, setIdentifier }),
    zoneId,
    name,
    type,
    setIdentifier,
    ttl,
    resourceRecords,
    aliasTarget,
  };
}

/**
 * Compare two normalized recordsets for material equality: TTL, the
 * serialized (already-sorted) resourceRecords array, and the serialized
 * aliasTarget. Both null returns true; exactly one null returns false.
 */
export function recordSetsEqual(a: NormalizedRecordSet | null, b: NormalizedRecordSet | null): boolean {
  if (a === null && b === null) return true;
  if (a === null || b === null) return false;

  if (a.ttl !== b.ttl) return false;
  if (JSON.stringify(a.resourceRecords) !== JSON.stringify(b.resourceRecords)) return false;
  if (JSON.stringify(a.aliasTarget) !== JSON.stringify(b.aliasTarget)) return false;

  return true;
}

/**
 * Classify the drift between a previous (mirror) and next (AWS-live)
 * normalized recordset, mapping to `route53_record_history.change_action`.
 * Returns `null` when there is no material difference, so the sync does not
 * write a no-op history row.
 */
export function classifyDrift(
  prev: NormalizedRecordSet | null,
  next: NormalizedRecordSet | null
): 'create' | 'update' | 'delete' | null {
  if (prev === null && next === null) return null;
  if (prev === null) return 'create';
  if (next === null) return 'delete';
  return recordSetsEqual(prev, next) ? null : 'update';
}

/**
 * Build the JSONB payload stored in `before_value`/`after_value`: the whole
 * recordset snapshot. Route 53 has no partial-value primitive, so the unit
 * of change recorded in history is always the whole recordset, never a
 * per-field delta.
 */
export function toHistoryPayload(ns: NormalizedRecordSet | null): unknown {
  if (ns === null) return null;
  return {
    name: ns.name,
    type: ns.type,
    setIdentifier: ns.setIdentifier,
    ttl: ns.ttl,
    resourceRecords: ns.resourceRecords,
    aliasTarget: ns.aliasTarget,
  };
}
