/**
 * DNS Propagation Checker
 * Verifies DNS record propagation by querying multiple resolvers.
 * Runs as background jobs with configurable timeout and interval.
 *
 * @module dns-propagation
 */

const dns = require('dns').promises;
const EventEmitter = require('events');

/** Default verification options */
const DEFAULT_OPTIONS = {
  timeout: 300000,    // 5 minutes
  interval: 10000,    // 10 seconds
  resolvers: ['1.1.1.1', '8.8.8.8', '9.9.9.9']
};

/** Maximum age for stored verification results (1 hour) */
const MAX_RESULT_AGE_MS = 3600000;

class DNSPropagationChecker extends EventEmitter {
  /**
   * Create a DNSPropagationChecker instance.
   * @param {Object} ctx - Shared application context
   * @param {Object} ctx.notification - NotificationManager instance
   * @param {Object} ctx.log - Logger instance
   */
  constructor(ctx) {
    super();
    this.ctx = ctx;
    this.log = ctx.log || console;

    /** @type {Map<string, Object>} domain → verification status */
    this.verifications = new Map();
  }

  /**
   * Verify that a DNS record has propagated by querying multiple resolvers.
   * Retries every `interval` ms until `timeout` is reached.
   *
   * @param {string} domain - The domain to check (e.g., 'test.sami')
   * @param {string} expectedIp - The expected IP address
   * @param {Object} [options={}] - Verification options
   * @param {number} [options.timeout=300000] - Maximum time to wait (ms)
   * @param {number} [options.interval=10000] - Time between retries (ms)
   * @param {string[]} [options.resolvers] - DNS resolvers to query
   * @returns {Promise<Object>} Verification result
   */
  async verifyRecord(domain, expectedIp, options = {}) {
    const startTime = Date.now();
    const {
      timeout = DEFAULT_OPTIONS.timeout,
      interval = DEFAULT_OPTIONS.interval,
      resolvers = DEFAULT_OPTIONS.resolvers
    } = options;

    const allResults = [];
    let propagated = false;

    while (Date.now() - startTime < timeout) {
      const roundResults = [];

      for (const resolver of resolvers) {
        const checkStart = Date.now();
        try {
          // Use dns.resolve4 with a custom resolver
          const resolverInstance = new dns.Resolver();
          resolverInstance.setServers([resolver]);
          resolverInstance.setTimeout(5000);

          const addresses = await resolverInstance.resolve4(domain);
          const matched = addresses.includes(expectedIp);

          const result = {
            resolver,
            ips: addresses,
            matched,
            checkedAt: new Date().toISOString(),
            responseTime: Date.now() - checkStart
          };

          roundResults.push(result);

          if (matched) {
            propagated = true;
          }
        } catch (err) {
          roundResults.push({
            resolver,
            ips: [],
            matched: false,
            checkedAt: new Date().toISOString(),
            error: err.code || err.message,
            responseTime: Date.now() - checkStart
          });
        }
      }

      allResults.push(...roundResults);

      // Emit progress event
      this.emit('propagation-check', {
        domain,
        expectedIp,
        roundResults,
        elapsed: Date.now() - startTime,
        propagated
      });

      if (propagated) {
        break;
      }

      // Wait before next attempt
      await new Promise(resolve => setTimeout(resolve, interval));
    }

    const totalTime = Date.now() - startTime;

    return {
      domain,
      expectedIp,
      propagated,
      results: allResults,
      totalTime,
      checkedAt: new Date().toISOString()
    };
  }

  /**
   * Start a background DNS propagation verification.
   * Does not block — returns immediately with the job reference.
   *
   * @param {string} domain - The domain to verify
   * @param {string} expectedIp - The expected IP address
   * @param {Object} [options={}] - Verification options
   * @returns {Object} Job status object
   */
  startVerification(domain, expectedIp, options = {}) {
    // If there's already a running verification for this domain, return it
    const existing = this.verifications.get(domain);
    if (existing && existing.status === 'running') {
      return existing;
    }

    const job = {
      domain,
      expectedIp,
      status: 'running',
      startedAt: new Date().toISOString(),
      progress: [],
      result: null
    };

    this.verifications.set(domain, job);

    // Run verification in background (non-blocking)
    this.verifyRecord(domain, expectedIp, options)
      .then(result => {
        job.status = 'completed';
        job.result = result;
        job.completedAt = new Date().toISOString();

        if (result.propagated) {
          this.emit('propagation-complete', result);

          if (this.ctx.notification) {
            this.ctx.notification.send('dns-propagation', {
              text: `✅ DNS record for ${domain} propagated successfully to ${expectedIp}`,
              domain,
              expectedIp,
              totalTime: result.totalTime
            }, 'success').catch(err => {
              this.log.error('dns-propagation', 'Failed to send propagation notification', {
                error: err.message
              });
            });
          }
        } else {
          this.emit('propagation-timeout', result);

          if (this.ctx.notification) {
            this.ctx.notification.send('dns-propagation', {
              text: `⏱️ DNS propagation timeout for ${domain} — expected ${expectedIp} not found after ${Math.round(result.totalTime / 1000)}s`,
              domain,
              expectedIp,
              totalTime: result.totalTime
            }, 'warning').catch(err => {
              this.log.error('dns-propagation', 'Failed to send timeout notification', {
                error: err.message
              });
            });
          }
        }
      })
      .catch(err => {
        job.status = 'error';
        job.error = err.message;
        job.completedAt = new Date().toISOString();

        this.log.error('dns-propagation', `Verification failed for ${domain}`, {
          error: err.message
        });
      });

    return job;
  }

  /**
   * Get the current verification status for a domain.
   *
   * @param {string} domain - The domain to look up
   * @returns {Object|null} Verification status or null if not found
   */
  getVerificationStatus(domain) {
    const job = this.verifications.get(domain);
    if (!job) return null;
    return {
      domain: job.domain,
      expectedIp: job.expectedIp,
      status: job.status,
      startedAt: job.startedAt,
      completedAt: job.completedAt || null,
      result: job.result || null,
      error: job.error || null
    };
  }

  /**
   * Get all recent verifications.
   *
   * @returns {Object[]} Array of verification statuses
   */
  getAllVerifications() {
    const results = [];
    for (const [domain, job] of this.verifications.entries()) {
      results.push({
        domain,
        expectedIp: job.expectedIp,
        status: job.status,
        startedAt: job.startedAt,
        completedAt: job.completedAt || null,
        propagated: job.result?.propagated || null,
        totalTime: job.result?.totalTime || null,
        error: job.error || null
      });
    }
    return results;
  }

  /**
   * Remove verifications older than 1 hour.
   */
  cleanup() {
    const now = Date.now();
    for (const [domain, job] of this.verifications.entries()) {
      const completedAt = job.completedAt ? new Date(job.completedAt).getTime() : null;
      const startedAt = new Date(job.startedAt).getTime();

      // Clean up completed/error jobs older than 1 hour
      // Also clean up stale running jobs that started over 2 hours ago
      const age = completedAt ? (now - completedAt) : (now - startedAt);
      const maxAge = job.status === 'running' ? MAX_RESULT_AGE_MS * 2 : MAX_RESULT_AGE_MS;

      if (age > maxAge) {
        this.verifications.delete(domain);
      }
    }
  }
}

module.exports = DNSPropagationChecker;
