/**
 * Google Cloud DNS driver.
 *
 * Auth uses the shared vibn-workspace-provisioner SA (which we granted
 * roles/dns.admin). Zones are created as PUBLIC managed zones in the
 * configured GCP project. Visibility is global (anycast) — see
 * `AI_CAPABILITIES_ROADMAP.md#P5.1` for the residency note.
 *
 * Every read/write goes through https://dns.googleapis.com/dns/v1.
 */

import { getGcpAccessToken, GCP_PROJECT_ID } from '@/lib/gcp-auth';
import type { DnsProvider, DnsRecord, DnsZone } from './provider';

const API = `https://dns.googleapis.com/dns/v1/projects/${GCP_PROJECT_ID}`;

function zoneName(apex: string): string {
  // Cloud DNS managed-zone names must match [a-z0-9-]+ and start with a letter.
  const slug = apex.toLowerCase().replace(/[^a-z0-9-]/g, '-');
  return `vibn-${slug}`;
}

async function authedFetch(
  method: 'GET' | 'POST' | 'DELETE' | 'PATCH',
  path: string,
  body?: unknown,
): Promise<Response> {
  const token = await getGcpAccessToken();
  const headers: Record<string, string> = {
    Authorization: `Bearer ${token}`,
    Accept: 'application/json',
  };
  if (body) headers['Content-Type'] = 'application/json';
  return fetch(`${API}${path}`, {
    method,
    headers,
    body: body ? JSON.stringify(body) : undefined,
  });
}

async function parseOrThrow<T>(res: Response, context: string): Promise<T> {
  const text = await res.text();
  if (!res.ok) {
    throw new Error(`[cloud-dns ${context} ${res.status}] ${text.slice(0, 500)}`);
  }
  return text ? JSON.parse(text) : ({} as T);
}

interface CloudDnsManagedZone {
  name: string;
  dnsName: string;
  nameServers?: string[];
  creationTime?: string;
  visibility?: string;
}

interface CloudDnsRrSet {
  name: string;
  type: string;
  ttl?: number;
  rrdatas: string[];
}

interface CloudDnsChange {
  additions?: CloudDnsRrSet[];
  deletions?: CloudDnsRrSet[];
  kind: 'dns#change';
}

export const cloudDnsProvider: DnsProvider = {
  id: 'cloud_dns',

  async createZone(apex) {
    const name = zoneName(apex);
    const dnsName = apex.endsWith('.') ? apex : `${apex}.`;
    // Idempotent: if the zone already exists, return it unchanged.
    const existing = await cloudDnsProvider.getZone(apex);
    if (existing) return existing;

    const res = await authedFetch('POST', `/managedZones`, {
      name,
      dnsName,
      description: `Vibn-managed zone for ${apex}`,
      visibility: 'public',
    });
    const zone = await parseOrThrow<CloudDnsManagedZone>(res, 'createZone');
    return {
      apex,
      zoneId: zone.name,
      nameservers: zone.nameServers ?? [],
      createdAt: zone.creationTime,
    };
  },

  async getZone(apex) {
    const name = zoneName(apex);
    const res = await authedFetch('GET', `/managedZones/${name}`);
    if (res.status === 404) return null;
    const zone = await parseOrThrow<CloudDnsManagedZone>(res, 'getZone');
    return {
      apex,
      zoneId: zone.name,
      nameservers: zone.nameServers ?? [],
      createdAt: zone.creationTime,
    };
  },

  /**
   * Replaces the record set for each (name,type) pair in `records`. Other
   * record sets on the zone are untouched.
   */
  async setRecords(apex, records) {
    const name = zoneName(apex);
    const dnsName = apex.endsWith('.') ? apex : `${apex}.`;
    // Fetch existing rrsets for these (name,type) pairs so we can delete
    // them as part of the atomic change.
    const existingRes = await authedFetch('GET', `/managedZones/${name}/rrsets`);
    const existingJson = await parseOrThrow<{ rrsets?: CloudDnsRrSet[] }>(existingRes, 'listRrsets');
    const existing = existingJson.rrsets ?? [];

    const rel = (nm: string) => (nm === '@' ? dnsName : `${nm}.${dnsName}`);

    const additions: CloudDnsRrSet[] = records.map(r => ({
      name: rel(r.name),
      type: r.type,
      ttl: r.ttl ?? 300,
      rrdatas: r.rrdatas,
    }));

    const wantPairs = new Set(additions.map(a => `${a.name}|${a.type}`));
    const deletions = existing.filter(e => wantPairs.has(`${e.name}|${e.type}`));

    const change: CloudDnsChange = { kind: 'dns#change', additions, deletions };
    const res = await authedFetch('POST', `/managedZones/${name}/changes`, change);
    await parseOrThrow(res, 'setRecords');
  },

  async deleteZone(apex) {
    const name = zoneName(apex);
    const res = await authedFetch('DELETE', `/managedZones/${name}`);
    if (res.status === 404) return;
    await parseOrThrow(res, 'deleteZone');
  },
};
