import { SecurityResults } from './types';
import dns from 'dns';
import { promisify } from 'util';
import { exec } from 'child_process';

// Convert DNS callback functions to Promise-based
const lookup = promisify(dns.lookup);
const resolve = promisify(dns.resolve);
const resolveMx = promisify(dns.resolveMx);
const resolveTxt = promisify(dns.resolveTxt);
const resolveNs = promisify(dns.resolveNs);
const resolveCaa = promisify(dns.resolveCaa);
const resolveNaptr = promisify(dns.resolveNaptr);
const promiseExec = promisify(exec);

interface DNSRecords {
  a?: string[];
  aaaa?: string[];
  mx?: dns.MxRecord[];
  txt?: string[][];
  ns?: string[];
  caa?: dns.CaaRecord[];
  naptr?: dns.NaptrRecord[];
}

export class DNSChecker {
  async check(domain: string, results: SecurityResults): Promise<void> {
    try {
      // Initialize DNS findings in results
      results.findings.dns = {
        records: {},
        issues: [],
        status: 'checking'
      };

      // Get DNS records
      const dnsRecords = await this.getAllDNSRecords(domain);
      results.findings.dns.records = dnsRecords;
      
      // Check for DNS-related security issues
      await this.checkDNSSecurity(domain, dnsRecords, results);
      
      // Update final status
      if (results.findings.dns.issues.length === 0) {
        results.findings.dns.status = 'secure';
      } else {
        results.findings.dns.status = 'issues-found';
      }
    } catch (error) {
      this.addDNSIssue(
        results,
        'error',
        'medium',
        `DNS check failed: ${error.message}`,
        'Verify DNS configuration and domain availability'
      );
      results.findings.dns.status = 'error';
    }
  }

  private async getAllDNSRecords(domain: string): Promise<DNSRecords> {
    const records: DNSRecords = {};
    const recordTypes = ['A', 'AAAA', 'MX', 'TXT', 'NS', 'CAA', 'NAPTR'];
    
    // Use Promise.allSettled to fetch all records in parallel and handle errors gracefully
    const recordPromises = recordTypes.map(async (type) => {
      try {
        switch (type) {
          case 'A':
            const aRecords = await resolve(domain, 'A');
            if (aRecords?.length > 0) records.a = aRecords;
            break;
          case 'AAAA':
            const aaaaRecords = await resolve(domain, 'AAAA');
            if (aaaaRecords?.length > 0) records.aaaa = aaaaRecords;
            break;
          case 'MX':
            const mxRecords = await resolveMx(domain);
            if (mxRecords?.length > 0) records.mx = mxRecords;
            break;
          case 'TXT':
            const txtRecords = await resolveTxt(domain);
            if (txtRecords?.length > 0) records.txt = txtRecords;
            break;
          case 'NS':
            const nsRecords = await resolveNs(domain);
            if (nsRecords?.length > 0) records.ns = nsRecords;
            break;
          case 'CAA':
            const caaRecords = await resolveCaa(domain);
            if (caaRecords?.length > 0) records.caa = caaRecords;
            break;
          case 'NAPTR':
            const naptrRecords = await resolveNaptr(domain);
            if (naptrRecords?.length > 0) records.naptr = naptrRecords;
            break;
        }
      } catch (e) {
        // Silently ignore errors for individual record types
      }
    });

    await Promise.allSettled(recordPromises);
    return records;
  }

  private async checkDNSSecurity(domain: string, records: DNSRecords, results: SecurityResults): Promise<void> {
    // Check for  DNSSEC implementation
    await this.checkRealDNSSEC(domain, results);
    
    // Check for SPF, DKIM, and DMARC (email security)
    await this.checkEmailSecurity(domain, records, results);
    
    // Check for NS configuration issues
    await this.checkNSConfiguration(records, results);
  }

  private async checkRealDNSSEC(domain: string, results: SecurityResults): Promise<void> {
    try {
      // Use dig command to check DNSSEC properly
      const { stdout } = await promiseExec(`dig +dnssec +short ${domain} SOA`);
      
      // Check for RRSIG records which indicate DNSSEC is enabled
      const hasRRSIG = stdout.includes('RRSIG');
      
      if (!hasRRSIG) {
        // Validate with another approach - check for DS records in parent zone
        const parentCheckResult = await promiseExec(`dig +short ${domain} DS`);
        const hasDS = parentCheckResult.stdout.trim().length > 0;
        
        if (!hasDS) {
          this.addDNSIssue(
            results,
            'missing_security_feature',
            'high',
            'DNSSEC not implemented',
            'Implement DNSSEC to protect against DNS spoofing and cache poisoning attacks',
            'DNSSEC cryptographically signs DNS records, providing authentication to prevent attackers from redirecting traffic by manipulating DNS responses'
          );
        }
      }
    } catch (error) {
      // Fallback to a simpler check if dig command fails
      try {
        const txtRecords = await resolveTxt(domain);
        const hasDNSSECTxt = txtRecords.some(record => 
          record.some(txt => txt.toLowerCase().includes('dnssec'))
        );
        
        if (!hasDNSSECTxt) {
          this.addDNSIssue(
            results,
            'missing_security_feature',
            'high',
            'DNSSEC not detected',
            'Implement DNSSEC to protect against DNS spoofing attacks',
            'Without DNSSEC, your domain is vulnerable to cache poisoning attacks'
          );
        }
      } catch (e) {
        // Unable to check DNSSEC
      }
    }
  }

  private async checkEmailSecurity(domain: string, records: DNSRecords, results: SecurityResults): Promise<void> {
    // Check SPF
    this.checkSPF(records, results);
    
    // Check DMARC with improved validation
    await this.checkDMARC(domain, results);
    
    // Check DKIM with common and custom selectors
    await this.checkDKIM(domain, records, results);
    
    // Check BIMI record (optional but emerging standard)
    await this.checkBIMI(domain, results);
  }

  private checkSPF(records: DNSRecords, results: SecurityResults): void {
    if (!records.txt) {
      this.addDNSIssue(
        results,
        'missing_security_feature',
        'high',
        'SPF record not found',
        'Add an SPF record to prevent email spoofing',
        'SPF records specify which mail servers are authorized to send email on behalf of your domain'
      );
      return;
    }

    const spfRecords = records.txt.filter(record => 
      record.some(txt => txt.toLowerCase().startsWith('v=spf1'))
    );

    if (spfRecords.length === 0) {
      this.addDNSIssue(
        results,
        'missing_security_feature',
        'high',
        'SPF record not found',
        'Add an SPF record to prevent email spoofing',
        'SPF records specify which mail servers are authorized to send email on behalf of your domain'
      );
      return;
    }

    if (spfRecords.length > 1) {
      this.addDNSIssue(
        results,
        'misconfiguration',
        'high',
        'Multiple SPF records found',
        'Consolidate multiple SPF records into a single record',
        'Multiple SPF records can cause inconsistent email delivery and security issues'
      );
      return;
    }

    // Check SPF record syntax and policy
    const spfRecord = spfRecords[0].join('');
    
    // Check for missing ~all or -all
    if (!spfRecord.includes(' ~all') && !spfRecord.includes(' -all')) {
      this.addDNSIssue(
        results,
        'misconfiguration',
        'medium',
        'SPF record missing strict policy',
        'Add "~all" (soft fail) or "-all" (hard fail) to your SPF record',
        'Without a strict policy, unauthorized senders may still be able to spoof your domain'
      );
    }
    
    // Check for SPF record length (DNS lookup limit)
    const lookupCount = (spfRecord.match(/include:/g) || []).length + 
                       (spfRecord.match(/a:/g) || []).length + 
                       (spfRecord.match(/mx:/g) || []).length + 
                       (spfRecord.match(/ptr:/g) || []).length + 
                       (spfRecord.match(/exists:/g) || []).length;
                       
    if (lookupCount > 10) {
      this.addDNSIssue(
        results,
        'misconfiguration',
        'medium',
        'SPF record exceeds lookup limit',
        'Simplify your SPF record to use fewer than 10 DNS lookups',
        'SPF has a maximum of 10 DNS lookups per record; exceeding this can cause some mail servers to ignore your SPF policy'
      );
    }
    
    // Check for deprecated or insecure mechanisms
    if (spfRecord.includes('ptr:')) {
      this.addDNSIssue(
        results,
        'misconfiguration',
        'low',
        'SPF record uses deprecated PTR mechanism',
        'Remove PTR mechanisms from your SPF record',
        'The PTR mechanism is deprecated, inefficient, and can lead to unexpected results'
      );
    }
  }

  private async checkDKIM(domain: string, records: DNSRecords, results: SecurityResults): Promise<void> {
    try {
      // Check common DKIM selectors plus email service providers' known selectors
      const selectors = [
        'default', 'dkim', 'mail', 'email', 'selector1', 'selector2', 'k1', 
        'google', 'mxvault', 'everlytickey1', 'everlytickey2', 'mandrill', 
        'sendgrid', 'zoho', 'key1', 'key2', 'pm', 'mailjet'
      ];
      
      let foundSelectors = [];
      
      // Try to detect email providers from MX records to check specific selectors
      if (records.mx) {
        const mxDomains = records.mx.map(mx => mx.exchange.toLowerCase());
        
        if (mxDomains.some(mx => mx.includes('google') || mx.includes('gmail'))) {
          selectors.push('google');
        }
        if (mxDomains.some(mx => mx.includes('outlook') || mx.includes('hotmail') || mx.includes('microsoft'))) {
          selectors.push('selector1', 'selector2');
        }
        if (mxDomains.some(mx => mx.includes('amazon') || mx.includes('aws'))) {
          selectors.push('amazonses');
        }
      }
      
      // Check for DKIM records with all potential selectors
      for (const selector of selectors) {
        try {
          const dkimDomain = `${selector}._domainkey.${domain}`;
          const dkimRecords = await resolveTxt(dkimDomain);
          
          if (dkimRecords && dkimRecords.length > 0) {
            const validDkimRecord = dkimRecords.some(record => 
              record.some(txt => txt.toLowerCase().includes('v=dkim1'))
            );
            
            if (validDkimRecord) {
              foundSelectors.push(selector);
            }
          }
        } catch (e) {
          // Continue checking other selectors
        }
      }

      if (foundSelectors.length === 0) {
        this.addDNSIssue(
          results,
          'missing_security_feature',
          'high',
          'DKIM record not found',
          'Set up DKIM to authenticate emails sent from your domain',
          'DKIM adds a digital signature to emails to verify they were sent from an authorized server'
        );
      } else {
        // Store found selectors in results for reporting
        results.findings.dns.dkimSelectors = foundSelectors;
      }
    } catch (error) {
      // Continue if there's an error checking DKIM
    }
  }

  private async checkDMARC(domain: string, results: SecurityResults): Promise<void> {
    try {
      const dmarcDomain = `_dmarc.${domain}`;
      let dmarcRecords;
      
      try {
        dmarcRecords = await resolveTxt(dmarcDomain);
      } catch (e) {
        this.addDNSIssue(
          results,
          'missing_security_feature',
          'high',
          'DMARC record not found',
          'Implement DMARC to enhance email security',
          'DMARC helps prevent email spoofing by providing clear instructions on how to handle emails that fail SPF or DKIM checks'
        );
        return;
      }
      
      if (!dmarcRecords || dmarcRecords.length === 0) {
        this.addDNSIssue(
          results,
          'missing_security_feature',
          'high',
          'DMARC record not found',
          'Implement DMARC to enhance email security',
          'DMARC helps prevent email spoofing by providing clear instructions on how to handle emails that fail SPF or DKIM checks'
        );
        return;
      }
      
      const dmarcRecord = dmarcRecords.find(record => 
        record.some(txt => txt.toLowerCase().startsWith('v=dmarc1'))
      );
      
      if (!dmarcRecord) {
        this.addDNSIssue(
          results,
          'missing_security_feature',
          'high',
          'DMARC record not found',
          'Implement DMARC to enhance email security',
          'DMARC helps prevent email spoofing by providing clear instructions on how to handle emails that fail SPF or DKIM checks'
        );
        return;
      }
      
      // Check DMARC policy
      const dmarcTxt = dmarcRecord.join('');
      const policyMatch = dmarcTxt.match(/p=([a-z]+)/i);
      
      if (policyMatch) {
        const policy = policyMatch[1].toLowerCase();
        
        if (policy === 'none') {
          this.addDNSIssue(
            results,
            'misconfiguration',
            'medium',
            'DMARC policy set to "none"',
            'Consider strengthening your DMARC policy to "quarantine" or "reject"',
            'A policy of "none" means no action is taken against email that fails DMARC, reducing its effectiveness'
          );
        }
      }
      
      // Check if reporting is configured
      if (!dmarcTxt.includes('rua=') && !dmarcTxt.includes('ruf=')) {
        this.addDNSIssue(
          results,
          'misconfiguration',
          'low',
          'DMARC reporting not configured',
          'Add reporting addresses to your DMARC record (rua= and/or ruf=)',
          'Without reporting addresses, you won\'t receive feedback about emails that fail DMARC checks'
        );
      }
    } catch (error) {
      // Continue if there's an error checking DMARC
    }
  }
  
  private async checkBIMI(domain: string, results: SecurityResults): Promise<void> {
    try {
      const bimiDomain = `default._bimi.${domain}`;
      let bimiExists = false;
      
      try {
        const bimiRecords = await resolveTxt(bimiDomain);
        bimiExists = bimiRecords && bimiRecords.length > 0 && 
                    bimiRecords.some(record => 
                      record.some(txt => txt.toLowerCase().startsWith('v=bimi1'))
                    );
      } catch (e) {
        bimiExists = false;
      }
      
      // BIMI is not critical but good to have
      if (!bimiExists) {
        this.addDNSIssue(
          results,
          'enhancement',
          'info',
          'BIMI record not found',
          'Consider implementing BIMI to display your logo in supported email clients',
          'Brand Indicators for Message Identification (BIMI) allows your logo to appear next to authenticated emails in supporting mail clients'
        );
      }
    } catch (error) {
      // Ignore BIMI check errors
    }
  }

  private async checkNSConfiguration(records: DNSRecords, results: SecurityResults): Promise<void> {
    // Check NS resilience and configuration
    if (!records.ns || records.ns.length === 0) {
      this.addDNSIssue(
        results,
        'misconfiguration',
        'critical',
        'No NS records found',
        'Configure authoritative DNS servers for your domain',
        'Without NS records, your domain will not resolve properly'
      );
      return;
    }
    
    if (records.ns.length < 2) {
      this.addDNSIssue(
        results,
        'misconfiguration',
        'high',
        'Insufficient DNS redundancy',
        'Configure at least two nameservers for your domain',
        'Having only one nameserver creates a single point of failure for your domain'
      );
      return;
    }
    
    // Check for nameserver diversity (different providers)
    const nsProviders = new Map<string, number>();
    
    records.ns.forEach(ns => {
      // Extract the base domain to identify the provider
      const match = ns.match(/\.([^.]+\.[a-z]+)$/i);
      if (match) {
        const provider = match[1].toLowerCase();
        nsProviders.set(provider, (nsProviders.get(provider) || 0) + 1);
      }
    });
    
    if (nsProviders.size < 2) {
      this.addDNSIssue(
        results,
        'misconfiguration',
        'medium',
        'All nameservers are with the same provider',
        'Use multiple DNS providers for increased reliability',
        'Using multiple DNS providers improves resilience against provider-specific outages'
      );
    }
    
    // Check for geographic diversity when possible
    // This would require additional lookups to determine geographic locations,
    // which is beyond the scope of this improvement
  }

  private addDNSIssue(
    results: SecurityResults,
    type: string,
    severity: 'critical' | 'high' | 'medium' | 'low' | 'info',
    title: string,
    recommendation: string,
    details?: string
  ): void {
    // Add issue to DNS findings
    if (!results.findings.dns.issues) {
      results.findings.dns.issues = [];
    }
    
    results.findings.dns.issues.push({ type, severity, title, recommendation, details });
    
    // Add issue to main issues list
    results.issues.push({ 
      category: 'dns',
      type, 
      severity, 
      title, 
      recommendation, 
      details 
    });
    
    // Update summary counts
    switch (severity) {
      case 'critical': results.summary.criticalIssues++; break;
      case 'high': results.summary.highIssues++; break;
      case 'medium': results.summary.mediumIssues++; break;
      case 'low': results.summary.lowIssues++; break;
      case 'info': results.summary.infos++; break;
    }
  }
}