const axios = require('axios');
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
require('dotenv').config();
const path = require('path');
const { models } = require(path.resolve(process.env.ROOT_PATH, './db/db.js'));
const net = require('net');
const fs = require('fs').promises;
const { execSync } = require('child_process');

// Create Cloudflare API client
const createCloudflareClient = () => {
  // If using API Token (preferred method)
  if (process.env.CF_API_TOKEN) {
    return axios.create({
      baseURL: 'https://api.cloudflare.com/client/v4',
      headers: {
        'Authorization': `Bearer ${process.env.CF_API_TOKEN}`,
        'Content-Type': 'application/json'
      },
      timeout: 10000
    });
  } 
  // If using API Key (legacy method)
  else if (process.env.CF_API_KEY && process.env.CF_EMAIL) {
    return axios.create({
      baseURL: 'https://api.cloudflare.com/client/v4',
      headers: {
        'X-Auth-Key': process.env.CF_API_KEY,
        'X-Auth-Email': process.env.CF_EMAIL,
        'Content-Type': 'application/json'
      },
      timeout: 10000
    });
  } else {
    throw new Error('No valid Cloudflare authentication method found');
  }
};

async function generateDkimKey(domain) {
  try {
    // Create directory if it doesn't exist
    const keyPath = `/etc/opendkim/keys/${domain}`;
    await fs.mkdir(keyPath, { recursive: true });
    
    // Generate the DKIM key using opendkim-genkey directly
    execSync(`opendkim-genkey -D ${keyPath} -d ${domain} -s mail`);
    
    // Set proper permissions (if needed)
    execSync(`chown -R opendkim:opendkim ${keyPath}`);
    
    // Read the generated public key
    const publicKeyContent = await fs.readFile(`${keyPath}/mail.txt`, 'utf8');
    
    // Extract the DKIM record value
    const dkimMatch = publicKeyContent.match(/p=([^)]+)/);
    const dkimValue = dkimMatch ? dkimMatch[1] : null;
    
    if (!dkimValue) {
      throw new Error('Could not extract DKIM value from key file');
    }
    
    return {
      host: 'mail._domainkey',
      value: `v=DKIM1; k=rsa; p=${dkimValue}`
    };
  } catch (error) {
    console.error('Error generating DKIM key:', error);
    return null;
  }
}

async function configureDNS(domain) {
  const cf = createCloudflareClient();
  
  try {
    // Step 1: Get the zone ID for the domain
    console.log(`Fetching zone for domain: ${domain}`);
    const zonesResponse = await cf.get('/zones', {
      params: {
        name: domain
      }
    });
    
    if (!zonesResponse.data.success) {
      console.error('Cloudflare API request failed:', zonesResponse.data.errors);
      return false;
    }
    
    if (zonesResponse.data.result.length === 0) {
      console.error(`Zone not found for domain: ${domain}`);
      return false;
    }
    
    const zoneId = zonesResponse.data.result[0].id;
    console.log(`Found zone ID: ${zoneId} for domain: ${domain}`);
    
    // Step 2: Get existing DNS records
    console.log(`Fetching existing DNS records for ${domain}...`);
    const recordsResponse = await cf.get(`/zones/${zoneId}/dns_records`);
    
    if (!recordsResponse.data.success) {
      console.error('Failed to fetch DNS records:', recordsResponse.data.errors);
      return false;
    }
    
    // Step 3: Delete existing records (except NS and SOA)
    const existingRecords = recordsResponse.data.result;
    console.log(`Found ${existingRecords.length} DNS records`);
    
    for (const record of existingRecords) {
      if (record.type !== 'NS' && record.type !== 'SOA') {
        console.log(`Deleting record: ${record.type} ${record.name}`);
        try {
          await cf.delete(`/zones/${zoneId}/dns_records/${record.id}`);
        } catch (error) {
          console.error(`Failed to delete record ${record.id}:`, error.message);
        }
      }
    }
    
    // Step 4: Add MX record
    console.log(`Adding MX record for ${domain}`);
    await cf.post(`/zones/${zoneId}/dns_records`, {
      type: 'MX',
      name: '@',
      content: `mail.2weekmail.fyi`,
      priority: 10,
      ttl: 3600
    });
    
    // Step 5: Add SPF record
    console.log(`Adding SPF record for ${domain}`);
    await cf.post(`/zones/${zoneId}/dns_records`, {
      type: 'TXT',
      name: '@',
      content: `"v=spf1 mx a ip4:${process.env.SERVER_IP} -all"`,
      ttl: 3600
    });
    
    // Step 6: Generate and add DKIM record
    console.log(`Generating DKIM key for ${domain}`);
    const dkimRecord = await generateDkimKey(domain);
    if (dkimRecord) {
      console.log(`Adding DKIM record for ${domain}`);
      // Fix the content format - remove any existing quotes and add exactly one set
      const rawValue = dkimRecord.value.replace(/"/g, '');
      const dkimContent = `"${rawValue}"`;
      console.log(`DKIM content: ${dkimContent}`);
      await cf.post(`/zones/${zoneId}/dns_records`, {
        type: 'TXT',
        name: 'mail._domainkey',
        content: dkimContent,
        ttl: 3600
      });
    }
    
    // Step 7: Add DMARC record
    console.log(`Adding DMARC record for ${domain}`);
    await cf.post(`/zones/${zoneId}/dns_records`, {
      type: 'TXT',
      name: '_dmarc',
      content: `"v=DMARC1; p=reject; rua=mailto:postmaster@2weekmail.fyi"`,
      ttl: 3600
    });
    
    // // Step 8: Add A record for mail subdomain
    // console.log(`Adding A record for mail.${domain}`);
    // await cf.post(`/zones/${zoneId}/dns_records`, {
    //   type: 'A',
    //   name: 'mail',
    //   content: process.env.SERVER_IP,
    //   ttl: 3600
    // });
    
    console.log(`DNS configuration completed for ${domain}`);
    return true;
  } catch (error) {
    console.error(`Error configuring DNS for ${domain}:`, error.message);
    if (error.response) {
      console.error('API error details:', error.response.data);
    }
    return false;
  }
}

function checkOpenDKIM() {
    return new Promise((resolve, reject) => {
        const client = new net.Socket();
        
        client.connect(8891, 'opendkim', () => {
            client.destroy();
            resolve(true);
        });

        client.on('error', (err) => {
            client.destroy();
            reject(err);
        });

        // Add timeout
        setTimeout(() => {
            client.destroy();
            reject(new Error('Connection timeout'));
        }, 5000);
    });
}

async function configure2WeekMailDNS() {
  const domain = '2weekmail.fyi';
  const cf = createCloudflareClient();
  
  try {
    // Step 1: Get the zone ID for the domain
    console.log(`Fetching zone for domain: ${domain}`);
    const zonesResponse = await cf.get('/zones', {
      params: {
        name: domain
      }
    });
    
    if (!zonesResponse.data.success) {
      console.error('Cloudflare API request failed:', zonesResponse.data.errors);
      return false;
    }
    
    if (zonesResponse.data.result.length === 0) {
      console.error(`Zone not found for domain: ${domain}`);
      return false;
    }
    
    const zoneId = zonesResponse.data.result[0].id;
    console.log(`Found zone ID: ${zoneId} for domain: ${domain}`);
    
    // Step 2: Get existing DNS records
    console.log(`Fetching existing DNS records for ${domain}...`);
    const recordsResponse = await cf.get(`/zones/${zoneId}/dns_records`);
    
    if (!recordsResponse.data.success) {
      console.error('Failed to fetch DNS records:', recordsResponse.data.errors);
      return false;
    }
    
    const existingRecords = recordsResponse.data.result;
    console.log(`Found ${existingRecords.length} DNS records`);
    
    // Check for required records
    let hasMX = false;
    let hasSPF = false;
    let hasDMARC = false;
    let hasMailA = false;
    let dkimRecordId = null;
    
    for (const record of existingRecords) {
      if (record.type === 'MX' && record.name === domain) {
        hasMX = true;
      } else if (record.type === 'TXT' && record.name === domain && record.content.includes('v=spf1')) {
        hasSPF = true;
      } else if (record.type === 'TXT' && record.name === '_dmarc.' + domain) {
        hasDMARC = true;
      } else if (record.type === 'A' && record.name === 'mail.' + domain) {
        hasMailA = true;
      } else if (record.type === 'TXT' && record.name === 'mail._domainkey.' + domain) {
        dkimRecordId = record.id;
      }
    }
    
    // Add missing records if needed
    if (!hasMX) {
      console.log(`Adding MX record for ${domain}`);
      await cf.post(`/zones/${zoneId}/dns_records`, {
        type: 'MX',
        name: '@',
        content: `mail.${domain}`,
        priority: 10,
        ttl: 3600
      });
    }
    
    if (!hasSPF) {
      console.log(`Adding SPF record for ${domain}`);
      await cf.post(`/zones/${zoneId}/dns_records`, {
        type: 'TXT',
        name: '@',
        content: `"v=spf1 mx a ip4:${process.env.SERVER_IP} -all"`,
        ttl: 3600
      });
    }
    
    if (!hasDMARC) {
      console.log(`Adding DMARC record for ${domain}`);
      await cf.post(`/zones/${zoneId}/dns_records`, {
        type: 'TXT',
        name: '_dmarc',
        content: `"v=DMARC1; p=reject; rua=mailto:postmaster@${domain}"`,
        ttl: 3600
      });
    }
    
    if (!hasMailA) {
      console.log(`Adding A record for mail.${domain}`);
      await cf.post(`/zones/${zoneId}/dns_records`, {
        type: 'A',
        name: 'mail',
        content: process.env.SERVER_IP,
        ttl: 3600
      });
    }
    
    // Always regenerate and update DKIM record
    console.log(`Generating DKIM key for ${domain}`);
    const dkimRecord = await generateDkimKey(domain);
    if (dkimRecord) {
      // Delete existing DKIM record if it exists
      if (dkimRecordId) {
        console.log(`Deleting existing DKIM record for ${domain}`);
        await cf.delete(`/zones/${zoneId}/dns_records/${dkimRecordId}`);
      }
      
      console.log(`Adding DKIM record for ${domain}`);
      const rawValue = dkimRecord.value.replace(/"/g, '');
      const dkimContent = `"${rawValue}"`;
      console.log(`DKIM content: ${dkimContent}`);
      await cf.post(`/zones/${zoneId}/dns_records`, {
        type: 'TXT',
        name: 'mail._domainkey',
        content: dkimContent,
        ttl: 3600
      });
    }
    
    console.log(`DNS configuration completed for ${domain}`);
    return true;
  } catch (error) {
    console.error(`Error configuring DNS for ${domain}:`, error.message);
    if (error.response) {
      console.error('API error details:', error.response.data);
    }
    return false;
  }
}

async function exportAllDomains() {
  try {
    // Check if OpenDKIM is available
    const isOpenDkimAvailable = await checkOpenDKIM();
    if (!isOpenDkimAvailable) {
      console.error('Cannot proceed: OpenDKIM container is not available');
      return;
    }
    
    // First, handle the 2weekmail.fyi domain specifically
    console.log('--------------------------');
    console.log('Configuring DNS for 2weekmail.fyi');
    await configure2WeekMailDNS();
    
    console.log('--------------------------');
    console.log('Starting DNS configuration for all other domains...');
    const domains = await models.Domain.query().where('domain', '!=', '2weekmail.fyi').andWhere('in_cloudflare', 0);
    
    console.log(`Found ${domains.length} domains to configure`);
    let successCount = 0;
    let failCount = 0;
    
    for (const domain of domains) {
      console.log(`--------------------------`);
      console.log(`Configuring DNS for domain: ${domain.domain}`);
      const success = await configureDNS(domain.domain);
      
      if (success) {
        // Update the domain record to mark it as configured in Cloudflare
        await models.Domain.query().where('domain', domain.domain).patch({ in_cloudflare: 1 });
        successCount++;
      } else {
        failCount++;
      }
      
      // Add a small delay between API calls to avoid rate limiting
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
    
    console.log('--------------------------');
    console.log(`DNS configuration completed: ${successCount} succeeded, ${failCount} failed`);
  } catch (error) {
    console.error('Error processing domains:', error);
  }
}

// Verify required environment variables
if (!process.env.CF_API_TOKEN && !(process.env.CF_API_KEY && process.env.CF_EMAIL)) {
  console.error('Error: Either CF_API_TOKEN or both CF_API_KEY and CF_EMAIL environment variables must be set');
  process.exit(1);
}

if (!process.env.SERVER_IP) {
  console.error('Error: SERVER_IP environment variable is not set');
  process.exit(1);
}

// Run the script
exportAllDomains();