const path = require('path');
const { models } = require(path.resolve(process.env.ROOT_PATH, './db/db.js'));
const BaseController = require(path.resolve(process.env.ROOT_PATH, './controllers/BaseController.js'));
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
const CloudflareAPI = require('cloudflare');

class DomainController extends BaseController {
  constructor() {
    super();
    
    this.protected('getAvailableDomains');
    // Admin-only endpoints for managing domains
    this.admin('getAllDomains', 'createDomain', 'updateDomain', 'deleteDomain');
  }

  
  /**
   * @swagger
   * /domains/available:
   *   get:
   *     summary: Get all available domains
   *     description: Retrieves a list of all active domains
   *     tags: [Domains]
   *     security:
   *       - bearerAuth: []
   *     responses:
   *       200:
   *         description: List of available domains
   *         content:
   *           application/json:
   *             schema:
   *               type: array
   *               items:
   *                 type: object
   *                 properties:
   *                   domain:
   *                     type: string
   *                     description: Domain name
   *                   description:
   *                     type: string
   *                     description: Domain description
   *       401:
   *         description: Unauthorized - Authentication required
   *       500:
   *         description: Server error
   */
  async getAvailableDomains(req, res) {
    try {
      const domains = await models.Domain.query()
        .where('active', 1)
        .select(['domain', 'description'])
        .orderBy('domain');
      
      return res.json(domains);
    } catch (error) {
      console.error('Error fetching available domains:', error);
      return res.status(500).json({ error: 'Failed to fetch domains' });
    }
  }

  /**
   * Get all domains (admin only)
   * This returns all domains including inactive ones
   */
  async getAllDomains(req, res) {
    try {
      const domains = await models.Domain.query()
        .select(['id', 'domain', 'description', 'active', 'created', 'modified'])
        .orderBy('domain');
      
      return res.json(domains);
    } catch (error) {
      console.error('Error fetching all domains:', error);
      return res.status(500).json({ error: 'Failed to fetch domains' });
    }
  }

  /**
   * Create a new domain (admin only)
   */
  async createDomain(req, res) {
    try {
      const { domain, description, active } = req.body;
      
      // Validate input
      if (!domain) {
        return res.status(400).json({ error: 'Domain name is required' });
      }
      
      // Check if domain already exists
      const existingDomain = await models.Domain.query().where('domain', domain).first();
      if (existingDomain) {
        return res.status(409).json({ error: 'Domain already exists' });
      }
      
      // Create the domain
      const newDomain = await models.Domain.query().insert({
        domain,
        description: description || domain,
        active: active !== undefined ? active : 1,
        transport: 'smtp',
        backupmx: 0,
        created: new Date().toISOString().slice(0, 19).replace('T', ' '),
        modified: new Date().toISOString().slice(0, 19).replace('T', ' ')
      });
      
      const dkimRecord = await generateDkimKey(domain);
      
      return res.status(201).json({
        domain: newDomain,
        dnsConfiguration: {
          mx: {
            host: '@',
            value: `10 mail.${domain}`
          },
          spf: {
            host: '@',
            type: 'TXT',
            value: `v=spf1 mx a ip4:${process.env.SERVER_IP} -all`
          },
          dkim: dkimRecord,
          dmarc: {
            host: '_dmarc',
            type: 'TXT',
            value: `v=DMARC1; p=reject; rua=mailto:postmaster@${domain}`
          },
          a: {
            host: 'mail',
            value: process.env.SERVER_IP
          }
        }
      });
    } catch (error) {
      console.error('Error creating domain:', error);
      return res.status(500).json({ error: 'Failed to create domain' });
    }
  }

  /**
   * Update a domain (admin only)
   */
  async updateDomain(req, res) {
    try {
      const { id } = req.params;
      const { description, active } = req.body;
      
      // Check if domain exists
      const domain = await models.Domain.query().findById(id);
      if (!domain) {
        return res.status(404).json({ error: 'Domain not found' });
      }
      
      // Update the domain
      const updatedDomain = await models.Domain.query().patchAndFetchById(id, {
        description: description !== undefined ? description : domain.description,
        active: active !== undefined ? active : domain.active,
        modified: new Date().toISOString().slice(0, 19).replace('T', ' ')
      });
      
      return res.json(updatedDomain);
    } catch (error) {
      console.error('Error updating domain:', error);
      return res.status(500).json({ error: 'Failed to update domain' });
    }
  }

  /**
   * Delete a domain (admin only)
   */
  async deleteDomain(req, res) {
    try {
      const { id } = req.params;
      
      // Check if domain exists
      const domain = await models.Domain.query().findById(id);
      if (!domain) {
        return res.status(404).json({ error: 'Domain not found' });
      }
      
      // Check if domain has mailboxes
      const mailboxCount = await models.Mailbox.query()
        .where('domain', domain.domain)
        .resultSize();
      
      if (mailboxCount > 0) {
        return res.status(409).json({ 
          error: 'Cannot delete domain with active mailboxes',
          mailboxCount
        });
      }
      
      // Delete the domain
      await models.Domain.query().deleteById(id);
      
      return res.json({ message: 'Domain deleted successfully' });
    } catch (error) {
      console.error('Error deleting domain:', error);
      return res.status(500).json({ error: 'Failed to delete domain' });
    }
  }
}

async function generateDkimKey(domain) {
  try {
    await execPromise(`docker exec mailserver_opendkim opendkim-genkey -D /etc/opendkim/keys/${domain} -d ${domain} -s mail`);
    await execPromise(`docker exec mailserver_opendkim chown -R opendkim:opendkim /etc/opendkim/keys/${domain}`);
    
    // Read the generated public key
    const { stdout } = await execPromise(`docker exec mailserver_opendkim cat /etc/opendkim/keys/${domain}/mail.txt`);
    
    // Extract the DKIM record value
    const dkimMatch = stdout.match(/p=([^)]+)/);
    const dkimValue = dkimMatch ? dkimMatch[1] : null;
    
    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 = new CloudflareAPI({
    email: process.env.CF_EMAIL,
    key: process.env.CF_API_TOKEN
  });
  
  try {
    // First, get the zone ID for the domain
    const zones = await cf.zones.browse();
    const zone = zones.result.find(z => z.name === domain);
    
    if (!zone) {
      console.error(`Zone not found for domain: ${domain}`);
      return false;
    }
    
    const zoneId = zone.id;
    
    // Add MX record
    await cf.dnsRecords.add(zoneId, {
      type: 'MX',
      name: '@',
      content: `mail.${domain}`,
      priority: 10,
      ttl: 3600
    });
    
    // Add SPF record
    await cf.dnsRecords.add(zoneId, {
      type: 'TXT',
      name: '@',
      content: `v=spf1 mx a ip4:${process.env.SERVER_IP} -all`,
      ttl: 3600
    });
    
    // Add DKIM record
    const dkimRecord = await generateDkimKey(domain);
    if (dkimRecord) {
      await cf.dnsRecords.add(zoneId, {
        type: 'TXT',
        name: 'mail._domainkey',
        content: dkimRecord.value,
        ttl: 3600
      });
    }
    
    // Add DMARC record
    await cf.dnsRecords.add(zoneId, {
      type: 'TXT',
      name: '_dmarc',
      content: `v=DMARC1; p=reject; rua=mailto:postmaster@${domain}`,
      ttl: 3600
    });
    
    // Add A record for mail subdomain
    await cf.dnsRecords.add(zoneId, {
      type: 'A',
      name: 'mail',
      content: process.env.SERVER_IP,
      ttl: 3600
    });
    
    return true;
  } catch (error) {
    console.error('Error configuring DNS:', error);
    return false;
  }
}

module.exports = new DomainController(); 