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

import Cloudflare from 'npm:cloudflare@5.2.0';
import { logger } from '../logging.ts';
import { getErrorMessage } from '../utils/error.ts';
import { OneboxDatabase } from './database.ts';

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 = 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 = [];
      for await (const zone of this.cloudflareClient.zones.list()) {
        zoneList.push(zone);
        this.zones.set(zone.name, zone.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.query(
        'INSERT INTO dns_records (domain, type, value, cloudflare_id, zone_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)',
        [domain, 'A', targetIP, response.id, zoneID, Date.now(), 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 rows = this.database.query('SELECT cloudflare_id, zone_id FROM dns_records WHERE domain = ?', [
        domain,
      ]);

      if (rows.length === 0) {
        logger.warn(`DNS record not found for ${domain}`);
        return;
      }

      const cloudflareID = String((rows[0] as any).cloudflare_id || rows[0][0]);
      const zoneID = String((rows[0] as any).zone_id || rows[0][1]);

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

      // Delete from database
      this.database.query('DELETE FROM dns_records WHERE domain = ?', [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
      const rows = this.database.query('SELECT * FROM dns_records WHERE domain = ?', [domain]);

      if (rows.length > 0) {
        const row = rows[0] as any;
        return {
          domain: String(row.domain || row[1]),
          type: String(row.type || row[2]),
          value: String(row.value || row[3]),
          cloudflareID: row.cloudflare_id ? String(row.cloudflare_id) : (row[4] ? String(row[4]) : null),
          zoneID: row.zone_id ? String(row.zone_id) : (row[5] ? String(row[5]) : null),
        };
      }

      return null;
    } catch (error) {
      logger.error(`Failed to get DNS record for ${domain}: ${getErrorMessage(error)}`);
      return null;
    }
  }

  /**
   * List all DNS records
   */
  listDNSRecords(): any[] {
    try {
      const rows = this.database.query('SELECT * FROM dns_records ORDER BY created_at DESC');

      return rows.map((row: any) => ({
        id: Number(row.id || row[0]),
        domain: String(row.domain || row[1]),
        type: String(row.type || row[2]),
        value: String(row.value || row[3]),
        cloudflareID: row.cloudflare_id ? String(row.cloudflare_id) : (row[4] ? String(row[4]) : null),
        zoneID: row.zone_id ? String(row.zone_id) : (row[5] ? String(row[5]) : null),
        createdAt: Number(row.created_at || row[6]),
        updatedAt: Number(row.updated_at || row[7]),
      }));
    } 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 = [];
        for await (const record of this.cloudflareClient!.dns.records.list({
          zone_id: zoneID,
        })) {
          records.push(record);
        }

        // 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.query(
              'INSERT INTO dns_records (domain, type, value, cloudflare_id, zone_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)',
              [record.name, record.type, record.content, record.id, zoneID, Date.now(), 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 command = new Deno.Command('dig', {
        args: ['+short', domain],
        stdout: 'piped',
        stderr: 'piped',
      });

      const { code, stdout } = await command.output();

      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;
    }
  }
}
