/**
 * Cloudflare DNS Provider Adapter
 * Manages DNS records via the Cloudflare API v4.
 */
const BaseDNSProvider = require('./base');

const CF_API_BASE = 'https://api.cloudflare.com/client/v4';

class CloudflareDNSProvider extends BaseDNSProvider {
  constructor(config, ctx) {
    super(config, ctx);
    this.providerId = 'cloudflare';
    this.displayName = 'Cloudflare DNS';

    // Resolve API token: explicit config takes priority, then credential manager
    this.apiToken = config.apiToken
      || (ctx.credentialManager && ctx.credentialManager.get('dns.cloudflare.apiToken'))
      || null;
    this.zoneId = config.zoneId || null;
    this.domain = config.domain || null;
  }

  // ── Helpers ────────────────────────────────────────────────────────────

  /** Build common request headers for Cloudflare API calls */
  _headers() {
    return {
      'Authorization': `Bearer ${this.apiToken}`,
      'Content-Type': 'application/json',
    };
  }

  /** Make an authenticated request to the Cloudflare API */
  async _cfRequest(method, path, body) {
    const url = `${CF_API_BASE}${path}`;
    const opts = {
      method,
      headers: this._headers(),
    };
    if (body !== undefined) {
      opts.body = JSON.stringify(body);
    }
    return this.ctx.fetchT(url, opts);
  }

  /** Map a Cloudflare DNS record to the normalised format expected by routes */
  _mapRecord(rec) {
    return {
      id: rec.id,
      type: rec.type,
      name: rec.name,
      value: rec.content,
      ttl: rec.ttl,
      proxied: rec.proxied || false,
    };
  }

  // ── Capabilities ───────────────────────────────────────────────────────

  supportsCapability(cap) {
    return this.getCapabilities().includes(cap);
  }

  getCapabilities() {
    return ['create-record', 'delete-record', 'resolve', 'list-records', 'credentials', 'zones'];
  }

  // ── Authentication ─────────────────────────────────────────────────────

  /**
   * Validate the API token by calling the Cloudflare verify endpoint.
   * Stores basic zone info on success.
   */
  async authenticate() {
    this.ctx.log('[cloudflare] Authenticating – verifying API token…');

    if (!this.apiToken) {
      return { status: 'error', message: 'No Cloudflare API token provided' };
    }

    const res = await this._cfRequest('GET', '/user/tokens/verify');
    const data = await res.json();

    if (!data.success) {
      const msg = (data.errors && data.errors[0] && data.errors[0].message) || 'Token verification failed';
      this.ctx.log(`[cloudflare] Authentication failed: ${msg}`);
      return { status: 'error', message: msg };
    }

    this.ctx.log(`[cloudflare] Token verified for status "${data.status}"`);

    // Optionally fetch zone info if zoneId is configured
    if (this.zoneId) {
      try {
        const zoneRes = await this._cfRequest('GET', `/zones/${this.zoneId}`);
        const zoneData = await zoneRes.json();
        if (zoneData.success && zoneData.result) {
          this.zoneInfo = zoneData.result;
          this.ctx.log(`[cloudflare] Zone loaded: ${zoneData.result.name} (${zoneData.result.id})`);
        }
      } catch (err) {
        this.ctx.log(`[cloudflare] Could not fetch zone info: ${err.message}`);
      }
    }

    return { status: 'ok', response: { status: data.status } };
  }

  // ── Create Record ──────────────────────────────────────────────────────

  /**
   * Create a DNS record.
   * If overwrite is true, first delete any existing record with the same name+type.
   */
  async createRecord({ domain, zone, type, value, ttl, overwrite }) {
    const targetDomain = domain || this.domain;
    const targetZone = zone || this.zoneId;

    if (!targetZone) {
      return { status: 'error', message: 'No zone ID configured for Cloudflare' };
    }

    if (overwrite) {
      this.ctx.log(`[cloudflare] Overwrite requested – deleting existing ${type} record for ${targetDomain}`);
      try {
        await this.deleteRecord({ domain: targetDomain, type, value });
      } catch (err) {
        this.ctx.log(`[cloudflare] No existing record to overwrite (or delete failed): ${err.message}`);
      }
    }

    const body = {
      type,
      name: targetDomain,
      content: value,
      ttl: ttl || 1, // 1 = automatic TTL in Cloudflare
      proxied: false,
    };

    this.ctx.log(`[cloudflare] Creating ${type} record: ${targetDomain} → ${value}`);
    const res = await this._cfRequest('POST', `/zones/${targetZone}/dns_records`, body);
    const data = await res.json();

    if (!data.success) {
      const msg = (data.errors && data.errors[0] && data.errors[0].message) || 'Record creation failed';
      this.ctx.log(`[cloudflare] Create failed: ${msg}`);
      return { status: 'error', message: msg };
    }

    return { status: 'ok', response: { record: this._mapRecord(data.result) } };
  }

  // ── Delete Record ──────────────────────────────────────────────────────

  /**
   * Delete DNS records matching domain+type.
   * Lists matching records first, then deletes each one.
   */
  async deleteRecord({ domain, type, value }) {
    const targetDomain = domain || this.domain;
    const targetZone = this.zoneId;

    if (!targetZone) {
      return { status: 'error', message: 'No zone ID configured for Cloudflare' };
    }

    // List records matching name + type
    let queryPath = `/zones/${targetZone}/dns_records?name=${encodeURIComponent(targetDomain)}`;
    if (type) {
      queryPath += `&type=${encodeURIComponent(type)}`;
    }

    const listRes = await this._cfRequest('GET', queryPath);
    const listData = await listRes.json();

    if (!listData.success) {
      const msg = (listData.errors && listData.errors[0] && listData.errors[0].message) || 'Failed to list records for deletion';
      this.ctx.log(`[cloudflare] Delete – list failed: ${msg}`);
      return { status: 'error', message: msg };
    }

    const matching = listData.result || [];
    if (matching.length === 0) {
      this.ctx.log(`[cloudflare] No records found for ${targetDomain} (${type || 'any type'})`);
      return { status: 'ok', response: { deleted: 0 } };
    }

    // If a specific value is given, only delete records matching that value
    const toDelete = value
      ? matching.filter((r) => r.content === value)
      : matching;

    let deleted = 0;
    for (const record of toDelete) {
      const delRes = await this._cfRequest('DELETE', `/zones/${targetZone}/dns_records/${record.id}`);
      const delData = await delRes.json();
      if (delData.success) {
        deleted++;
        this.ctx.log(`[cloudflare] Deleted record ${record.id} (${record.type} ${record.name})`);
      } else {
        const msg = (delData.errors && delData.errors[0] && delData.errors[0].message) || 'Delete failed';
        this.ctx.log(`[cloudflare] Failed to delete record ${record.id}: ${msg}`);
      }
    }

    return { status: 'ok', response: { deleted } };
  }

  // ── Resolve Records ───────────────────────────────────────────────────

  /**
   * Resolve/query existing records for a domain.
   * Returns records matching domain (and optionally type).
   */
  async resolveRecords({ domain, zone, type }) {
    const targetDomain = domain || this.domain;
    const targetZone = zone || this.zoneId;

    if (!targetZone) {
      return { status: 'error', message: 'No zone ID configured for Cloudflare' };
    }

    let queryPath = `/zones/${targetZone}/dns_records?name=${encodeURIComponent(targetDomain)}`;
    if (type) {
      queryPath += `&type=${encodeURIComponent(type)}`;
    }

    this.ctx.log(`[cloudflare] Resolving records for ${targetDomain}${type ? ` (${type})` : ''}`);
    const res = await this._cfRequest('GET', queryPath);
    const data = await res.json();

    if (!data.success) {
      const msg = (data.errors && data.errors[0] && data.errors[0].message) || 'Resolve failed';
      this.ctx.log(`[cloudflare] Resolve failed: ${msg}`);
      return { status: 'error', message: msg };
    }

    const records = (data.result || []).map(this._mapRecord);
    return { status: 'ok', response: { records } };
  }

  // ── List Records ───────────────────────────────────────────────────────

  /**
   * List all DNS records in a zone.
   */
  async listRecords({ zone }) {
    const targetZone = zone || this.zoneId;

    if (!targetZone) {
      return { status: 'error', message: 'No zone ID configured for Cloudflare' };
    }

    this.ctx.log(`[cloudflare] Listing all records in zone ${targetZone}`);
    const res = await this._cfRequest('GET', `/zones/${targetZone}/dns_records`);
    const data = await res.json();

    if (!data.success) {
      const msg = (data.errors && data.errors[0] && data.errors[0].message) || 'List failed';
      this.ctx.log(`[cloudflare] List failed: ${msg}`);
      return { status: 'error', message: msg };
    }

    const records = (data.result || []).map(this._mapRecord);
    return { status: 'ok', response: { records } };
  }
}

module.exports = CloudflareDNSProvider;
