import * as plugins from '../../plugins.js';
import { logger } from '../../logger.js';
import type {
  IDnsProviderClient,
  IConnectionTestResult,
  IProviderRecord,
  IProviderRecordInput,
} from './interfaces.js';
import type { IProviderDomainListing } from '../../../ts_interfaces/data/dns-provider.js';
import type { TDnsRecordType } from '../../../ts_interfaces/data/dns-record.js';

/**
 * Cloudflare implementation of IDnsProviderClient.
 *
 * Wraps `@apiclient.xyz/cloudflare`. Records at Cloudflare are addressed by
 * an internal record id, which we surface as `providerRecordId` so the rest
 * of the system can issue updates and deletes without ambiguity (Cloudflare
 * can have multiple records of the same name+type).
 */
export class CloudflareDnsProvider implements IDnsProviderClient {
  private cfAccount: plugins.cloudflare.CloudflareAccount;

  constructor(apiToken: string) {
    if (!apiToken) {
      throw new Error('CloudflareDnsProvider: apiToken is required');
    }
    this.cfAccount = new plugins.cloudflare.CloudflareAccount(apiToken);
  }

  public async testConnection(): Promise<IConnectionTestResult> {
    try {
      // Listing zones is the lightest-weight call that proves the token works.
      await this.cfAccount.zoneManager.listZones();
      return { ok: true };
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : String(err);
      logger.log('warn', `CloudflareDnsProvider testConnection failed: ${message}`);
      return { ok: false, error: message };
    }
  }

  public async listDomains(): Promise<IProviderDomainListing[]> {
    const zones = await this.cfAccount.zoneManager.listZones();
    return zones.map((zone) => ({
      name: zone.name,
      externalId: zone.id,
      nameservers: zone.name_servers ?? [],
    }));
  }

  public async listRecords(domain: string): Promise<IProviderRecord[]> {
    const records = await this.cfAccount.recordManager.listRecords(domain);
    return records
      .filter((r) => this.isSupportedType(r.type))
      .map((r) => ({
        providerRecordId: r.id,
        name: r.name,
        type: r.type as TDnsRecordType,
        value: r.content,
        ttl: r.ttl,
        proxied: r.proxied,
      }));
  }

  public async createRecord(
    domain: string,
    record: IProviderRecordInput,
  ): Promise<IProviderRecord> {
    const zoneId = await this.cfAccount.zoneManager.getZoneId(domain);
    const apiRecord: any = {
      zone_id: zoneId,
      type: record.type,
      name: record.name,
      content: record.value,
      ttl: record.ttl ?? 1, // 1 = automatic
    };
    if (record.proxied !== undefined) {
      apiRecord.proxied = record.proxied;
    }
    const created = await (this.cfAccount as any).apiAccount.dns.records.create(apiRecord);
    return {
      providerRecordId: created.id,
      name: created.name,
      type: created.type as TDnsRecordType,
      value: created.content,
      ttl: created.ttl,
      proxied: created.proxied,
    };
  }

  public async updateRecord(
    domain: string,
    providerRecordId: string,
    record: IProviderRecordInput,
  ): Promise<IProviderRecord> {
    const zoneId = await this.cfAccount.zoneManager.getZoneId(domain);
    const apiRecord: any = {
      zone_id: zoneId,
      type: record.type,
      name: record.name,
      content: record.value,
      ttl: record.ttl ?? 1,
    };
    if (record.proxied !== undefined) {
      apiRecord.proxied = record.proxied;
    }
    const updated = await (this.cfAccount as any).apiAccount.dns.records.edit(
      providerRecordId,
      apiRecord,
    );
    return {
      providerRecordId: updated.id,
      name: updated.name,
      type: updated.type as TDnsRecordType,
      value: updated.content,
      ttl: updated.ttl,
      proxied: updated.proxied,
    };
  }

  public async deleteRecord(domain: string, providerRecordId: string): Promise<void> {
    const zoneId = await this.cfAccount.zoneManager.getZoneId(domain);
    await (this.cfAccount as any).apiAccount.dns.records.delete(providerRecordId, {
      zone_id: zoneId,
    });
  }

  private isSupportedType(type: string): boolean {
    return ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SOA', 'CAA'].includes(type);
  }
}
