import * as plugins from '../../plugins.js';
import type { IEmailDomainConfig } from './interfaces.js';
import { logger } from '../../logger.js';
import type { DcRouter } from '../../classes.dcrouter.js';
import type { StorageManager } from '../../storage/index.js';

/**
 * DNS validation result
 */
export interface IDnsValidationResult {
  valid: boolean;
  errors: string[];
  warnings: string[];
  requiredChanges: string[];
}

/**
 * DNS records found for a domain
 */
interface IDnsRecords {
  mx?: string[];
  spf?: string;
  dkim?: string;
  dmarc?: string;
  ns?: string[];
}

/**
 * Manages DNS configuration for email domains
 * Handles both validation and creation of DNS records
 */
export class DnsManager {
  private dcRouter: DcRouter;
  private storageManager: StorageManager;
  
  constructor(dcRouter: DcRouter) {
    this.dcRouter = dcRouter;
    this.storageManager = dcRouter.storageManager;
  }
  
  /**
   * Validate all domain configurations
   */
  async validateAllDomains(domainConfigs: IEmailDomainConfig[]): Promise<Map<string, IDnsValidationResult>> {
    const results = new Map<string, IDnsValidationResult>();
    
    for (const config of domainConfigs) {
      const result = await this.validateDomain(config);
      results.set(config.domain, result);
    }
    
    return results;
  }
  
  /**
   * Validate a single domain configuration
   */
  async validateDomain(config: IEmailDomainConfig): Promise<IDnsValidationResult> {
    switch (config.dnsMode) {
      case 'forward':
        return this.validateForwardMode(config);
      case 'internal-dns':
        return this.validateInternalDnsMode(config);
      case 'external-dns':
        return this.validateExternalDnsMode(config);
      default:
        return {
          valid: false,
          errors: [`Unknown DNS mode: ${config.dnsMode}`],
          warnings: [],
          requiredChanges: []
        };
    }
  }
  
  /**
   * Validate forward mode configuration
   */
  private async validateForwardMode(config: IEmailDomainConfig): Promise<IDnsValidationResult> {
    const result: IDnsValidationResult = {
      valid: true,
      errors: [],
      warnings: [],
      requiredChanges: []
    };
    
    // Forward mode doesn't require DNS validation by default
    if (!config.dns?.forward?.skipDnsValidation) {
      logger.log('info', `DNS validation skipped for forward mode domain: ${config.domain}`);
    }
    
    // DKIM keys are still generated for consistency
    result.warnings.push(
      `Domain "${config.domain}" uses forward mode. DKIM keys will be generated but signing only happens if email is processed.`
    );
    
    return result;
  }
  
  /**
   * Validate internal DNS mode configuration
   */
  private async validateInternalDnsMode(config: IEmailDomainConfig): Promise<IDnsValidationResult> {
    const result: IDnsValidationResult = {
      valid: true,
      errors: [],
      warnings: [],
      requiredChanges: []
    };
    
    // Check if DNS configuration is set up
    const dnsNsDomains = this.dcRouter.options?.dnsNsDomains;
    const dnsScopes = this.dcRouter.options?.dnsScopes;
    
    if (!dnsNsDomains || dnsNsDomains.length === 0) {
      result.valid = false;
      result.errors.push(
        `Domain "${config.domain}" is configured to use internal DNS, but dnsNsDomains is not set in DcRouter configuration.`
      );
      console.error(
        `āŒ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` +
        '   but dnsNsDomains is not set in DcRouter configuration.\n' +
        '   Please configure dnsNsDomains to enable the DNS server.\n' +
        '   Example: dnsNsDomains: ["ns1.myservice.com", "ns2.myservice.com"]'
      );
      return result;
    }
    
    if (!dnsScopes || dnsScopes.length === 0) {
      result.valid = false;
      result.errors.push(
        `Domain "${config.domain}" is configured to use internal DNS, but dnsScopes is not set in DcRouter configuration.`
      );
      console.error(
        `āŒ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` +
        '   but dnsScopes is not set in DcRouter configuration.\n' +
        '   Please configure dnsScopes to define authoritative domains.\n' +
        '   Example: dnsScopes: ["myservice.com", "mail.myservice.com"]'
      );
      return result;
    }
    
    // Check if the email domain is in dnsScopes
    if (!dnsScopes.includes(config.domain)) {
      result.valid = false;
      result.errors.push(
        `Domain "${config.domain}" is configured to use internal DNS, but is not included in dnsScopes.`
      );
      console.error(
        `āŒ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` +
        `   but is not included in dnsScopes: [${dnsScopes.join(', ')}].\n` +
        '   Please add this domain to dnsScopes to enable internal DNS.\n' +
        `   Example: dnsScopes: [..., "${config.domain}"]`
      );
      return result;
    }
    
    const primaryNameserver = dnsNsDomains[0];
    
    // Check NS delegation
    try {
      const nsRecords = await this.resolveNs(config.domain);
      const delegatedNameservers = dnsNsDomains.filter(ns => nsRecords.includes(ns));
      const isDelegated = delegatedNameservers.length > 0;
      
      if (!isDelegated) {
        result.warnings.push(
          `NS delegation not found for ${config.domain}. Please add NS records at your registrar.`
        );
        dnsNsDomains.forEach(ns => {
          result.requiredChanges.push(
            `Add NS record: ${config.domain}. NS ${ns}.`
          );
        });
        
        console.log(
          `šŸ“‹ DNS Delegation Required for ${config.domain}:\n` +
          '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
          'Please add these NS records at your domain registrar:\n' +
          dnsNsDomains.map(ns => `  ${config.domain}. NS ${ns}.`).join('\n') + '\n' +
          '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
          'This delegation is required for internal DNS mode to work.'
        );
      } else {
        console.log(
          `āœ… NS delegation verified: ${config.domain} -> [${delegatedNameservers.join(', ')}]`
        );
      }
    } catch (error) {
      result.warnings.push(
        `Could not verify NS delegation for ${config.domain}: ${error.message}`
      );
    }
    
    return result;
  }
  
  /**
   * Validate external DNS mode configuration
   */
  private async validateExternalDnsMode(config: IEmailDomainConfig): Promise<IDnsValidationResult> {
    const result: IDnsValidationResult = {
      valid: true,
      errors: [],
      warnings: [],
      requiredChanges: []
    };
    
    try {
      // Get current DNS records
      const records = await this.checkDnsRecords(config);
      const requiredRecords = config.dns?.external?.requiredRecords || ['MX', 'SPF', 'DKIM', 'DMARC'];
      
      // Check MX record
      if (requiredRecords.includes('MX') && !records.mx?.length) {
        result.requiredChanges.push(
          `Add MX record: ${this.getBaseDomain(config.domain)} -> ${config.domain} (priority 10)`
        );
      }
      
      // Check SPF record
      if (requiredRecords.includes('SPF') && !records.spf) {
        result.requiredChanges.push(
          `Add TXT record: ${this.getBaseDomain(config.domain)} -> "v=spf1 a mx ~all"`
        );
      }
      
      // Check DKIM record
      if (requiredRecords.includes('DKIM') && !records.dkim) {
        const selector = config.dkim?.selector || 'default';
        const dkimPublicKey = await this.storageManager.get(`/email/dkim/${config.domain}/public.key`);
        
        if (dkimPublicKey) {
          const publicKeyBase64 = dkimPublicKey
            .replace(/-----BEGIN PUBLIC KEY-----/g, '')
            .replace(/-----END PUBLIC KEY-----/g, '')
            .replace(/\s/g, '');
            
          result.requiredChanges.push(
            `Add TXT record: ${selector}._domainkey.${config.domain} -> "v=DKIM1; k=rsa; p=${publicKeyBase64}"`
          );
        } else {
          result.warnings.push(
            `DKIM public key not found for ${config.domain}. It will be generated on first use.`
          );
        }
      }
      
      // Check DMARC record
      if (requiredRecords.includes('DMARC') && !records.dmarc) {
        result.requiredChanges.push(
          `Add TXT record: _dmarc.${this.getBaseDomain(config.domain)} -> "v=DMARC1; p=none; rua=mailto:dmarc@${config.domain}"`
        );
      }
      
      // Show setup instructions if needed
      if (result.requiredChanges.length > 0) {
        console.log(
          `šŸ“‹ DNS Configuration Required for ${config.domain}:\n` +
          '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
          result.requiredChanges.map((change, i) => `${i + 1}. ${change}`).join('\n') +
          '\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'
        );
      }
      
    } catch (error) {
      result.errors.push(`DNS validation failed: ${error.message}`);
      result.valid = false;
    }
    
    return result;
  }
  
  /**
   * Check DNS records for a domain
   */
  private async checkDnsRecords(config: IEmailDomainConfig): Promise<IDnsRecords> {
    const records: IDnsRecords = {};
    const baseDomain = this.getBaseDomain(config.domain);
    const selector = config.dkim?.selector || 'default';
    
    // Use custom DNS servers if specified
    const resolver = new plugins.dns.promises.Resolver();
    if (config.dns?.external?.servers?.length) {
      resolver.setServers(config.dns.external.servers);
    }
    
    // Check MX records
    try {
      const mxRecords = await resolver.resolveMx(baseDomain);
      records.mx = mxRecords.map(mx => mx.exchange);
    } catch (error) {
      logger.log('debug', `No MX records found for ${baseDomain}`);
    }
    
    // Check SPF record
    try {
      const txtRecords = await resolver.resolveTxt(baseDomain);
      const spfRecord = txtRecords.find(records => 
        records.some(record => record.startsWith('v=spf1'))
      );
      if (spfRecord) {
        records.spf = spfRecord.join('');
      }
    } catch (error) {
      logger.log('debug', `No SPF record found for ${baseDomain}`);
    }
    
    // Check DKIM record
    try {
      const dkimRecords = await resolver.resolveTxt(`${selector}._domainkey.${config.domain}`);
      const dkimRecord = dkimRecords.find(records => 
        records.some(record => record.includes('v=DKIM1'))
      );
      if (dkimRecord) {
        records.dkim = dkimRecord.join('');
      }
    } catch (error) {
      logger.log('debug', `No DKIM record found for ${selector}._domainkey.${config.domain}`);
    }
    
    // Check DMARC record
    try {
      const dmarcRecords = await resolver.resolveTxt(`_dmarc.${baseDomain}`);
      const dmarcRecord = dmarcRecords.find(records => 
        records.some(record => record.startsWith('v=DMARC1'))
      );
      if (dmarcRecord) {
        records.dmarc = dmarcRecord.join('');
      }
    } catch (error) {
      logger.log('debug', `No DMARC record found for _dmarc.${baseDomain}`);
    }
    
    return records;
  }
  
  /**
   * Resolve NS records for a domain
   */
  private async resolveNs(domain: string): Promise<string[]> {
    try {
      const resolver = new plugins.dns.promises.Resolver();
      const nsRecords = await resolver.resolveNs(domain);
      return nsRecords;
    } catch (error) {
      logger.log('warn', `Failed to resolve NS records for ${domain}: ${error.message}`);
      return [];
    }
  }
  
  /**
   * Get base domain from email domain (e.g., mail.example.com -> example.com)
   */
  private getBaseDomain(domain: string): string {
    const parts = domain.split('.');
    if (parts.length <= 2) {
      return domain;
    }
    
    // For subdomains like mail.example.com, return example.com
    // But preserve domain structure for longer TLDs like .co.uk
    if (parts[parts.length - 2].length <= 3 && parts[parts.length - 1].length === 2) {
      // Likely a country code TLD like .co.uk
      return parts.slice(-3).join('.');
    }
    
    return parts.slice(-2).join('.');
  }
  
  /**
   * Ensure all DNS records are created for configured domains
   * This is the main entry point for DNS record management
   */
  async ensureDnsRecords(domainConfigs: IEmailDomainConfig[], dkimCreator?: any): Promise<void> {
    logger.log('info', `Ensuring DNS records for ${domainConfigs.length} domains`);
    
    // First, validate all domains
    const validationResults = await this.validateAllDomains(domainConfigs);
    
    // Then create records for internal-dns domains
    const internalDnsDomains = domainConfigs.filter(config => config.dnsMode === 'internal-dns');
    if (internalDnsDomains.length > 0) {
      await this.createInternalDnsRecords(internalDnsDomains);
      
      // Create DKIM records if DKIMCreator is provided
      if (dkimCreator) {
        await this.createDkimRecords(domainConfigs, dkimCreator);
      }
    }
    
    // Log validation results for external-dns domains
    for (const [domain, result] of validationResults) {
      const config = domainConfigs.find(c => c.domain === domain);
      if (config?.dnsMode === 'external-dns' && result.requiredChanges.length > 0) {
        logger.log('warn', `External DNS configuration required for ${domain}`);
      }
    }
  }
  
  /**
   * Create DNS records for internal-dns mode domains
   */
  private async createInternalDnsRecords(domainConfigs: IEmailDomainConfig[]): Promise<void> {
    // Check if DNS server is available
    if (!this.dcRouter.dnsServer) {
      logger.log('warn', 'DNS server not available, skipping internal DNS record creation');
      return;
    }
    
    logger.log('info', `Creating DNS records for ${domainConfigs.length} internal-dns domains`);
    
    for (const domainConfig of domainConfigs) {
      const domain = domainConfig.domain;
      const ttl = domainConfig.dns?.internal?.ttl || 3600;
      const mxPriority = domainConfig.dns?.internal?.mxPriority || 10;
      
      try {
        // 1. Register MX record - points to the email domain itself
        this.dcRouter.dnsServer.registerHandler(
          domain,
          ['MX'],
          () => ({
            name: domain,
            type: 'MX',
            class: 'IN',
            ttl: ttl,
            data: {
              priority: mxPriority,
              exchange: domain
            }
          })
        );
        logger.log('info', `MX record registered for ${domain} -> ${domain} (priority ${mxPriority})`);
        
        // Store MX record in StorageManager
        await this.storageManager.set(
          `/email/dns/${domain}/mx`,
          JSON.stringify({
            type: 'MX',
            priority: mxPriority,
            exchange: domain,
            ttl: ttl
          })
        );
        
        // 2. Register SPF record - allows the domain to send emails
        const spfRecord = `v=spf1 a mx ~all`;
        this.dcRouter.dnsServer.registerHandler(
          domain,
          ['TXT'],
          () => ({
            name: domain,
            type: 'TXT',
            class: 'IN',
            ttl: ttl,
            data: spfRecord
          })
        );
        logger.log('info', `SPF record registered for ${domain}: "${spfRecord}"`);
        
        // Store SPF record in StorageManager
        await this.storageManager.set(
          `/email/dns/${domain}/spf`,
          JSON.stringify({
            type: 'TXT',
            data: spfRecord,
            ttl: ttl
          })
        );
        
        // 3. Register DMARC record - policy for handling email authentication
        const dmarcRecord = `v=DMARC1; p=none; rua=mailto:dmarc@${domain}`;
        this.dcRouter.dnsServer.registerHandler(
          `_dmarc.${domain}`,
          ['TXT'],
          () => ({
            name: `_dmarc.${domain}`,
            type: 'TXT',
            class: 'IN',
            ttl: ttl,
            data: dmarcRecord
          })
        );
        logger.log('info', `DMARC record registered for _dmarc.${domain}: "${dmarcRecord}"`);
        
        // Store DMARC record in StorageManager
        await this.storageManager.set(
          `/email/dns/${domain}/dmarc`,
          JSON.stringify({
            type: 'TXT',
            name: `_dmarc.${domain}`,
            data: dmarcRecord,
            ttl: ttl
          })
        );
        
        // Log summary of DNS records created
        logger.log('info', `āœ… DNS records created for ${domain}:
  - MX: ${domain} (priority ${mxPriority})
  - SPF: ${spfRecord}
  - DMARC: ${dmarcRecord}
  - DKIM: Will be created when keys are generated`);
        
      } catch (error) {
        logger.log('error', `Failed to create DNS records for ${domain}: ${error.message}`);
      }
    }
  }
  
  /**
   * Create DKIM DNS records for all domains
   */
  private async createDkimRecords(domainConfigs: IEmailDomainConfig[], dkimCreator: any): Promise<void> {
    for (const domainConfig of domainConfigs) {
      const domain = domainConfig.domain;
      const selector = domainConfig.dkim?.selector || 'default';
      
      try {
        // Get DKIM DNS record from DKIMCreator
        const dnsRecord = await dkimCreator.getDNSRecordForDomain(domain);
        
        // For internal-dns domains, register the DNS handler
        if (domainConfig.dnsMode === 'internal-dns' && this.dcRouter.dnsServer) {
          const ttl = domainConfig.dns?.internal?.ttl || 3600;
          
          this.dcRouter.dnsServer.registerHandler(
            `${selector}._domainkey.${domain}`,
            ['TXT'],
            () => ({
              name: `${selector}._domainkey.${domain}`,
              type: 'TXT',
              class: 'IN',
              ttl: ttl,
              data: dnsRecord.value
            })
          );
          
          logger.log('info', `DKIM DNS record registered for ${selector}._domainkey.${domain}`);
          
          // Store DKIM record in StorageManager
          await this.storageManager.set(
            `/email/dns/${domain}/dkim`,
            JSON.stringify({
              type: 'TXT',
              name: `${selector}._domainkey.${domain}`,
              data: dnsRecord.value,
              ttl: ttl
            })
          );
        }
        
        // For external-dns domains, just log what should be configured
        if (domainConfig.dnsMode === 'external-dns') {
          logger.log('info', `DKIM record for external DNS: ${dnsRecord.name} -> "${dnsRecord.value}"`);
        }
        
      } catch (error) {
        logger.log('warn', `Could not create DKIM DNS record for ${domain}: ${error.message}`);
      }
    }
  }
}