/**
 * Caddy context - Caddyfile manipulation and reload
 */
const fsp = require('fs').promises;
const { RETRIES } = require('../utilities/constants');

/**
 * Atomically read-modify-write the Caddyfile and reload Caddy.
 * Uses a mutex to prevent concurrent modifications.
 * Rolls back on reload failure.
 */
let _caddyfileLock = Promise.resolve();

async function modifyCaddyfile(CADDYFILE_PATH, reloadCaddy, modifyFn) {
  let resolve;
  const prev = _caddyfileLock;
  _caddyfileLock = new Promise(r => { resolve = r; });
  await prev;
  
  try {
    const original = await fsp.readFile(CADDYFILE_PATH, 'utf8');
    const modified = await modifyFn(original);
    
    if (modified === null || modified === original) {
      return { success: false, error: 'No changes to apply' };
    }
    
    await fsp.writeFile(CADDYFILE_PATH, modified, 'utf8');
    
    try {
      await reloadCaddy(modified);
      return { success: true };
    } catch (err) {
      // Rollback
      await fsp.writeFile(CADDYFILE_PATH, original, 'utf8');
      return { success: false, error: err.message, rolledBack: true };
    }
  } finally {
    resolve();
  }
}

/**
 * Read the current Caddyfile content
 */
// eslint-disable-next-line require-await -- fsp.readFile already returns a promise
async function readCaddyfile(CADDYFILE_PATH) {
  return await fsp.readFile(CADDYFILE_PATH, 'utf8');
}

/**
 * Reload Caddy via admin API
 */
async function reloadCaddy(CADDY_ADMIN_URL, content, fetchT, log) {
  const maxRetries = RETRIES.CADDY_RELOAD;
  let lastError = null;

  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetchT(`${CADDY_ADMIN_URL}/load`, {
        method: 'POST',
        headers: { 'Content-Type': 'text/caddyfile' },
        body: content
      });

      if (response.ok) {
        log.info('caddy', 'Caddy configuration reloaded successfully');
        await new Promise(resolve => setTimeout(resolve, 1000));
        return;
      }

      lastError = await response.text();
      log.warn('caddy', 'Caddy reload attempt failed', { attempt: i + 1, error: lastError });
    } catch (error) {
      lastError = error.message;
      log.warn('caddy', 'Caddy reload attempt error', { attempt: i + 1, error: lastError });
    }

    if (i < maxRetries - 1) {
      await new Promise(resolve => setTimeout(resolve, 2000));
    }
  }

  throw new Error(`[DC-303] Caddy reload failed after ${maxRetries} attempts: ${lastError}`);
}

/**
 * Verify a site is accessible via HTTPS
 */
async function verifySiteAccessible(domain, fetchT, httpsAgent, log, maxAttempts = 5) {
  const delay = 2000;

  for (let i = 0; i < maxAttempts; i++) {
    try {
      const response = await fetchT(`https://${domain}/`, {
        method: 'HEAD',
        agent: httpsAgent
      }, 5000);

      log.info('caddy', 'Site is accessible', { domain, status: response.status });
      return true;
    } catch (error) {
      log.debug('caddy', 'Site verification attempt', {
        domain,
        attempt: i + 1,
        maxAttempts,
        error: error.message
      });
    }

    if (i < maxAttempts - 1) {
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  log.warn('caddy', 'Could not verify site accessibility', { domain });
  return false;
}

/**
 * Generate Caddy config block for a service
 */
function generateCaddyConfig(subdomain, ip, port, siteConfig, buildDomain, options = {}) {
  const { tailscaleOnly = false, allowedIPs = [], subpathSupport = 'strip' } = options;

  // Subdirectory mode
  if (siteConfig.routingMode === 'subdirectory' && siteConfig.domain) {
    let config = '';

    if (subpathSupport === 'native') {
      config += `\tredir /${subdomain} /${subdomain}/ permanent\n`;
      config += `\thandle /${subdomain}/* {\n`;
    } else {
      config += `\thandle_path /${subdomain}/* {\n`;
    }

    if (tailscaleOnly) {
      config += `\t\t@blocked not remote_ip 100.64.0.0/10`;
      if (allowedIPs.length > 0) config += ` ${allowedIPs.join(' ')}`;
      config += `\n\t\trespond @blocked "Access denied. Tailscale connection required." 403\n`;
    }

    config += `\t\treverse_proxy ${ip}:${port}\n`;
    config += `\t}`;
    return config;
  }

  // Subdomain mode
  let config = `${buildDomain(subdomain)} {\n`;

  if (tailscaleOnly) {
    config += `  @blocked not remote_ip 100.64.0.0/10`;
    if (allowedIPs.length > 0) {
      config += ` ${allowedIPs.join(' ')}`;
    }
    config += `\n  respond @blocked "Access denied. Tailscale connection required." 403\n`;
  }

  config += `  reverse_proxy ${ip}:${port}\n`;
  config += `  tls internal\n`;
  config += `}`;

  return config;
}

function createCaddyContext(CADDYFILE_PATH, CADDY_ADMIN_URL, fetchT, httpsAgent, log, siteConfig, buildDomain) {
  const reload = (content) => reloadCaddy(CADDY_ADMIN_URL, content, fetchT, log);
  const read = () => readCaddyfile(CADDYFILE_PATH);
  const modify = (modifyFn) => modifyCaddyfile(CADDYFILE_PATH, reload, modifyFn);
  const verify = (domain, maxAttempts) => verifySiteAccessible(domain, fetchT, httpsAgent, log, maxAttempts);
  const generate = (subdomain, ip, port, options) => generateCaddyConfig(subdomain, ip, port, siteConfig, buildDomain, options);

  return {
    modify,
    read,
    reload,
    generateConfig: generate,
    verifySite: verify,
    adminUrl: CADDY_ADMIN_URL,
    filePath: CADDYFILE_PATH,
  };
}

module.exports = { createCaddyContext };
