/**
 * DNS Manager for Onebox
 *
 * Manages DNS records via Cloudflare API
 */

import Cloudflare from 'cloudflare';
import * as plugins from '../plugins.js';
import { logger } from '../logging.js';
import { getErrorMessage } from '../utils/error.js';
import { OneboxDatabase } from '../database/index.js';

export class OneboxDnsManager {
  private oneboxRef: any;
  private database: OneboxDatabase;
  private cloudflareClient: Cloudflare | null = null;
  private zones: Map<string, string> = new Map(); // zone name -> zone ID
  private serverIP: string | null = null;

  constructor(oneboxRef: any) {
    this.oneboxRef = oneboxRef;
    this.database = oneboxRef.database;
  }

  /**
   * Initialize DNS manager with Cloudflare credentials
   */
  async init(): Promise<void> {
    try {
      // Get Cloudflare credentials from settings
      const apiKey = await this.database.getSecretSetting('cloudflareToken');
      const serverIP = await this.database.getSetting('serverIP');

      if (!apiKey) {
        logger.warn('Cloudflare credentials not configured. DNS management will be disabled.');
        logger.info('Configure with: onebox config set cloudflareToken <key>');
        return;
      }

      this.serverIP = serverIP;

      // Initialize Cloudflare client
      // The official Cloudflare SDK expects the API token
      logger.info(`Initializing Cloudflare client with token: ${apiKey.substring(0, 10)}...`);
      this.cloudflareClient = new Cloudflare({
        apiToken: apiKey,
      });
      logger.info('Cloudflare client initialized successfully');

      // Load all available zones
      logger.info('Loading Cloudflare zones...');
      const zoneList: Array<{ name: string; id: string }> = [];
      for await (const zone of this.cloudflareClient.zones.list()) {
        if (!zone.name || !zone.id) continue;
        const zoneInfo = { name: zone.name, id: zone.id };
        zoneList.push(zoneInfo);
        this.zones.set(zoneInfo.name, zoneInfo.id);
      }

      if (zoneList.length === 0) {
        logger.warn('No Cloudflare zones found for this account');
        return;
      }

      logger.info(`Loaded ${zoneList.length} Cloudflare zone(s):`);
      for (const zone of zoneList) {
        logger.info(`  - ${zone.name} (ID: ${zone.id})`);
      }
      logger.success('DNS manager initialized with multi-zone support');
    } catch (error) {
      logger.error(`Failed to initialize DNS manager: ${getErrorMessage(error)}`);
      const errMsg = getErrorMessage(error);
      if (errMsg && errMsg.includes('Authorization header')) {
        logger.error('The provided API key appears to be invalid.');
        logger.error('Make sure you are using a Cloudflare API TOKEN (not the global API key).');
        logger.info('Create an API Token at: https://dash.cloudflare.com/profile/api-tokens');
        logger.info('The token needs "Zone:Read" and "DNS:Edit" permissions.');
      }
      throw error;
    }
  }

  /**
   * Check if DNS manager is configured
   */
  isConfigured(): boolean {
    return this.cloudflareClient !== null && this.zones.size > 0;
  }

  /**
   * Find the zone ID for a given domain
   * Example: "api.task.vc" -> finds "task.vc" zone
   */
  private findZoneForDomain(domain: string): string | null {
    // Try exact match first
    if (this.zones.has(domain)) {
      return this.zones.get(domain)!;
    }

    // Try finding parent zone (e.g., "api.task.vc" -> "task.vc")
    const parts = domain.split('.');
    for (let i = 1; i < parts.length; i++) {
      const candidate = parts.slice(i).join('.');
      if (this.zones.has(candidate)) {
        return this.zones.get(candidate)!;
      }
    }

    return null;
  }

  /**
   * Add a DNS record for a domain
   */
  async addDNSRecord(domain: string, ip?: string): Promise<void> {
    try {
      if (!this.isConfigured()) {
        throw new Error('DNS manager not configured');
      }

      logger.info(`Adding DNS record for ${domain}`);

      const targetIP = ip || this.serverIP;
      if (!targetIP) {
        throw new Error('Server IP not configured. Set with: onebox config set serverIP <ip>');
      }

      // Find the zone for this domain
      const zoneID = this.findZoneForDomain(domain);
      if (!zoneID) {
        throw new Error(
          `No Cloudflare zone found for domain ${domain}. Available zones: ${Array.from(this.zones.keys()).join(', ')}`
        );
      }

      // Check if record already exists
      const existing = await this.getDNSRecord(domain);
      if (existing) {
        logger.info(`DNS record already exists for ${domain}`);
        return;
      }

      // Create A record
      const response = await this.cloudflareClient!.dns.records.create({
        zone_id: zoneID,
        type: 'A',
        name: domain,
        content: targetIP,
        ttl: 1, // Auto
        proxied: false, // Don't proxy through Cloudflare for direct SSL
      });

      // Store in database
      await this.database.createDnsRecord({
        domain,
        type: 'A',
        value: targetIP,
        cloudflareID: response.id,
        zoneID,
        createdAt: Date.now(),
        updatedAt: Date.now(),
      });

      logger.success(`DNS record created for ${domain} → ${targetIP}`);
    } catch (error) {
      logger.error(`Failed to add DNS record for ${domain}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Remove a DNS record
   */
  async removeDNSRecord(domain: string): Promise<void> {
    try {
      if (!this.isConfigured()) {
        throw new Error('DNS manager not configured');
      }

      logger.info(`Removing DNS record for ${domain}`);

      // Get record from database
      const record = await this.database.getDnsRecordByDomain(domain);

      if (!record) {
        logger.warn(`DNS record not found for ${domain}`);
        return;
      }

      const cloudflareID = record.cloudflareID;
      const zoneID = record.zoneID;

      // Delete from Cloudflare
      if (cloudflareID && zoneID) {
        await this.cloudflareClient!.dns.records.delete(cloudflareID, {
          zone_id: zoneID,
        });
      }

      // Delete from database
      await this.database.deleteDnsRecord(domain);

      logger.success(`DNS record removed for ${domain}`);
    } catch (error) {
      logger.error(`Failed to remove DNS record for ${domain}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Get DNS record for a domain
   */
  async getDNSRecord(domain: string): Promise<any> {
    try {
      if (!this.isConfigured()) {
        return null;
      }

      // Get from database first
      return await this.database.getDnsRecordByDomain(domain);
    } catch (error) {
      logger.error(`Failed to get DNS record for ${domain}: ${getErrorMessage(error)}`);
      return null;
    }
  }

  /**
   * List all DNS records
   */
  async listDNSRecords(): Promise<any[]> {
    try {
      return await this.database.getAllDnsRecords();
    } catch (error) {
      logger.error(`Failed to list DNS records: ${getErrorMessage(error)}`);
      return [];
    }
  }

  /**
   * Sync DNS records from Cloudflare
   */
  async syncFromCloudflare(): Promise<void> {
    try {
      if (!this.isConfigured()) {
        throw new Error('DNS manager not configured');
      }

      logger.info('Syncing DNS records from all Cloudflare zones...');

      // Sync records from all zones
      for (const [zoneName, zoneID] of this.zones.entries()) {
        logger.info(`Syncing zone: ${zoneName}`);

        const records: Array<{ type: string; name: string; content?: unknown; id: string }> = [];
        for await (const record of this.cloudflareClient!.dns.records.list({
          zone_id: zoneID,
        })) {
          if (!record.name || !record.id) continue;
          records.push(record as { type: string; name: string; content?: unknown; id: string });
        }

        // Only sync A records
        const aRecords = records.filter((r) => r.type === 'A');

        for (const record of aRecords) {
          // Check if exists in database
          const existing = await this.getDNSRecord(record.name);

          if (!existing) {
            // Add to database
            await this.database.createDnsRecord({
              domain: record.name,
              type: record.type as 'A',
              value: String(record.content || ''),
              cloudflareID: record.id,
              zoneID,
              createdAt: Date.now(),
              updatedAt: Date.now(),
            });

            logger.info(`Synced DNS record: ${record.name}`);
          }
        }
      }

      logger.success('DNS records synced from Cloudflare');
    } catch (error) {
      logger.error(`Failed to sync DNS records: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Check if domain DNS is properly configured
   */
  async checkDNS(domain: string): Promise<boolean> {
    try {
      logger.info(`Checking DNS for ${domain}...`);

      // Use dig or nslookup to check DNS resolution
      const { code, stdout } = await plugins.runCommand('dig', {
        args: ['+short', domain],
        stdout: 'piped',
        stderr: 'piped',
      });

      if (code !== 0) {
        logger.warn(`DNS check failed for ${domain}`);
        return false;
      }

      const ip = new TextDecoder().decode(stdout).trim();

      if (ip === this.serverIP) {
        logger.success(`DNS correctly points to ${ip}`);
        return true;
      } else {
        logger.warn(`DNS points to ${ip}, expected ${this.serverIP}`);
        return false;
      }
    } catch (error) {
      logger.error(`Failed to check DNS for ${domain}: ${getErrorMessage(error)}`);
      return false;
    }
  }
}
