/**
 * DNS context - Technitium DNS operations and token management
 * 
 * DEPRECATED: This module is kept for backward compatibility.
 * New code should use src/context/provider-dns.js which supports multiple providers.
 * 
 * This module now delegates to the provider system internally.
 */
const { TIMEOUTS, SESSION_TTL, CADDY } = require('../utilities/constants');
const { createCache, CACHE_CONFIGS } = require('../utilities/cache-config');
const { createProviderDnsContext } = require('./provider-dns');

// DNS token management
let dnsToken = process.env.DNS_ADMIN_TOKEN || '';
let dnsTokenExpiry = null;

// Per-server token cache
const dnsServerTokens = createCache(CACHE_CONFIGS.dnsTokens);

/**
 * Build full Technitium DNS API URL
 */
function buildDnsUrl(server, apiPath, params) {
  const protocol = server.match(/^\d+\.\d+\.\d+\.\d+$/) ? 'http' : 'https';
  const port = protocol === 'http' ? `:${CADDY.DEFAULT_DNS_PORT}` : '';
  const qs = params instanceof URLSearchParams ? params.toString() : new URLSearchParams(params).toString();
  return `${protocol}://${server}${port}${apiPath}?${qs}`;
}

/**
 * Call a Technitium DNS API endpoint
 */
async function callDns(server, apiPath, params, fetchT, httpsAgent) {
  const url = buildDnsUrl(server, apiPath, params);
  const response = await fetchT(url, {
    method: 'GET',
    headers: { 'Accept': 'application/json' },
    agent: httpsAgent
  }, TIMEOUTS.HTTP_LONG);
  return response.json();
}

/**
 * Refresh DNS token via login
 */
async function refreshDnsToken(username, password, server, fetchT, log) {
  try {
    const params = new URLSearchParams({
      user: username,
      pass: password,
      includeInfo: 'false'
    });

    const response = await fetchT(
      `http://${server}:5380/api/user/login?${params.toString()}`,
      {
        method: 'POST',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/x-www-form-urlencoded'
        }
      },
      10000
    );

    const result = await response.json();

    if (result.status === 'ok' && result.token) {
      dnsToken = result.token;
      dnsTokenExpiry = new Date(Date.now() + SESSION_TTL.DNS_TOKEN).toISOString();
      log.info('dns', 'DNS token refreshed', { expires: dnsTokenExpiry });
      return { success: true, token: dnsToken };
    }

    return { success: false, error: result.errorMessage || 'Login failed' };
  } catch (error) {
    log.error('dns', 'DNS token refresh error', { error: error.message });
    return { success: false, error: error.message };
  }
}

/**
 * Try to refresh the DNS token using per-server (dns.<id>.<role>) credentials.
 * Returns the refresh result on success, or null if no per-server credentials match.
 */
async function refreshWithPerServerCredentials(dnsId, serverIp, credentialManager, fetchT, log) {
  for (const role of ['admin', 'readonly']) {
    try {
      const username = await credentialManager.retrieve(`dns.${dnsId}.${role}.username`);
      const password = await credentialManager.retrieve(`dns.${dnsId}.${role}.password`);
      if (username && password) {
        return await refreshDnsToken(username, password, serverIp, fetchT, log);
      }
    } catch (err) {
      log.error('dns', `Per-server ${role} credential error`, { dnsId, error: err.message });
    }
  }
  return null;
}

/**
 * Ensure we have a valid DNS token (auto-refresh if needed)
 */
async function tryCredentialPair(dnsId, role, primaryIp, siteConfig, credentialManager, fetchT, log) {
  try {
    const username = await credentialManager.retrieve(`dns.${dnsId}.${role}.username`);
    const password = await credentialManager.retrieve(`dns.${dnsId}.${role}.password`);
    if (username && password) {
      return await refreshDnsToken(username, password, primaryIp, fetchT, log);
    }
    return null;
  } catch (err) {
    log.error('dns', `Per-server ${role} credential error`, { dnsId, error: err.message });
    return null;
  }
}

async function ensureValidDnsToken(siteConfig, credentialManager, fetchT, log) {
  // Check if token is valid and not expired
  if (dnsToken && dnsTokenExpiry && new Date() < new Date(dnsTokenExpiry)) {
    return { success: true, token: dnsToken };
  }

  const primaryIp = siteConfig.dnsServerIp;
  if (primaryIp) {
    const dnsId = dnsIpToDnsId(primaryIp, siteConfig);
    if (dnsId) {
      for (const role of ['admin', 'readonly']) {
        const result = await tryCredentialPair(dnsId, role, primaryIp, siteConfig, credentialManager, fetchT, log);
        if (result) return result;
      }
    }
  }

  // Fall back to global credentials
  try {
    const username = await credentialManager.retrieve('dns.username');
    const password = await credentialManager.retrieve('dns.password');
    const server = await credentialManager.retrieve('dns.server');
    if (username && password) {
      return await refreshDnsToken(username, password, server || primaryIp, fetchT, log);
    }
  } catch (err) {
    log.error('dns', 'Credential manager error', { error: err.message });
  }

  return {
    success: false,
    error: 'No DNS credentials configured. Please set up credentials via /api/dns/credentials'
  };
}

/**
 * Map DNS server IP to its ID
 */
function dnsIpToDnsId(serverIp, siteConfig) {
  for (const [dnsId, info] of Object.entries(siteConfig.dnsServers || {})) {
    if (info.ip === serverIp) return dnsId;
  }
  return null;
}

/**
 * Get a valid token for a specific DNS server
 */
async function getTokenForServer(targetServer, siteConfig, credentialManager, fetchT, log, role = 'readonly') {
  const cacheKey = `${targetServer}:${role}`;
  const cached = dnsServerTokens.get(cacheKey);
  
  if (cached && cached.token && cached.expiry && new Date() < new Date(cached.expiry)) {
    return { success: true, token: cached.token };
  }

  const serverPort = siteConfig.dnsServerPort || '5380';

  async function authenticateToServer(username, password) {
    const params = new URLSearchParams({
      user: username,
      pass: password,
      includeInfo: 'false'
    });

    const response = await fetchT(
      `http://${targetServer}:${serverPort}/api/user/login?${params.toString()}`,
      {
        method: 'POST',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/x-www-form-urlencoded'
        }
      }
    );

    const result = await response.json();

    if (result.status === 'ok' && result.token) {
      dnsServerTokens.set(cacheKey, {
        token: result.token,
        expiry: new Date(Date.now() + SESSION_TTL.DNS_TOKEN).toISOString()
      });
      log.info('dns', 'DNS token obtained for server', { server: targetServer, role });
      return { success: true, token: result.token };
    }

    return { success: false, error: result.errorMessage || 'Login failed' };
  }

  const dnsId = dnsIpToDnsId(targetServer, siteConfig);

  if (dnsId) {
    try {
      const username = await credentialManager.retrieve(`dns.${dnsId}.${role}.username`);
      const password = await credentialManager.retrieve(`dns.${dnsId}.${role}.password`);
      if (username && password) {
        return await authenticateToServer(username, password);
      }
    } catch (err) {
      log.error('dns', `Per-server ${role} credential error`, { dnsId, server: targetServer, error: err.message });
    }

    const fallbackRole = role === 'readonly' ? 'admin' : 'readonly';
    try {
      const username = await credentialManager.retrieve(`dns.${dnsId}.${fallbackRole}.username`);
      const password = await credentialManager.retrieve(`dns.${dnsId}.${fallbackRole}.password`);
      if (username && password) {
        return await authenticateToServer(username, password);
      }
    } catch (err) {
      // ignore
    }
  }

  try {
    const username = await credentialManager.retrieve('dns.username');
    const password = await credentialManager.retrieve('dns.password');
    if (username && password) {
      return await authenticateToServer(username, password);
    }
  } catch (err) {
    log.error('dns', 'Credential manager error', { server: targetServer, error: err.message });
  }

  return { success: false, error: 'No DNS credentials configured' };
}

/**
 * Require a DNS token (throw if unavailable)
 */
async function requireDnsToken(providedToken, siteConfig, credentialManager, fetchT, log) {
  if (providedToken) return providedToken;
  const result = await ensureValidDnsToken(siteConfig, credentialManager, fetchT, log);
  if (result.success) return result.token;
  const err = new Error('No valid DNS token available. ' + result.error);
  err.statusCode = 401;
  throw err;
}

/**
 * Create DNS record
 */
async function createDnsRecord(subdomain, ip, siteConfig, buildDomain, credentialManager, fetchT, httpsAgent, log) {
  const tokenResult = await ensureValidDnsToken(siteConfig, credentialManager, fetchT, log);
  if (!tokenResult.success) {
    throw new Error(`DNS token not available: ${tokenResult.error}`);
  }

  const domain = buildDomain(subdomain);
  const zone = siteConfig.tld.replace(/^\./, '');

  const dnsParams = {
    token: dnsToken,
    domain,
    zone,
    type: 'A',
    ipAddress: ip,
    ttl: '300',
    overwrite: 'true'
  };

  const callDnsApi = () => callDns(siteConfig.dnsServerIp, '/api/zones/records/add', dnsParams, fetchT, httpsAgent);

  try {
    log.info('dns', 'Creating DNS record', { domain, ip });
    const result = await callDnsApi();

    if (result.status === 'ok') {
      log.info('dns', 'DNS record created', { domain, ip });
      return { success: true };
    }

    if (result.errorMessage && result.errorMessage.toLowerCase().includes('token')) {
      log.info('dns', 'Token appears expired, attempting auto-refresh');
      const refreshResult = await ensureValidDnsToken(siteConfig, credentialManager, fetchT, log);
      if (!refreshResult.success) throw new Error(`Token refresh failed: ${refreshResult.error}`);

      const retryResult = await callDnsApi();
      if (retryResult.status === 'ok') {
        log.info('dns', 'DNS record created after token refresh', { domain, ip });
        return { success: true };
      }
      throw new Error(retryResult.errorMessage || 'Unknown error after token refresh');
    }

    throw new Error(result.errorMessage || 'Unknown error');
  } catch (error) {
    throw new Error(`Failed to create DNS record for ${domain}: ${error.message}`);
  }
}

function invalidateTokenForServer(serverIp) {
  dnsServerTokens.delete(`${serverIp}:readonly`);
  dnsServerTokens.delete(`${serverIp}:admin`);
}

function createDnsContext(siteConfig, buildDomain, credentialManager, fetchT, httpsAgent, log, DNS_CREDENTIALS_FILE) {
  // Create the new provider-aware context
  const providerCtx = createProviderDnsContext(siteConfig, buildDomain, credentialManager, fetchT, httpsAgent, log, DNS_CREDENTIALS_FILE);

  // Legacy Technitium-specific wrappers (kept for backward compat)
  const ensureToken = () => ensureValidDnsToken(siteConfig, credentialManager, fetchT, log);
  const require = (providedToken) => requireDnsToken(providedToken, siteConfig, credentialManager, fetchT, log);
  const getForServer = (server, role) => getTokenForServer(server, siteConfig, credentialManager, fetchT, log, role);
  const refresh = (username, password, server) => refreshDnsToken(username, password, server, fetchT, log);
  const create = (subdomain, ip) => createDnsRecord(subdomain, ip, siteConfig, buildDomain, credentialManager, fetchT, httpsAgent, log);
  const call = (server, apiPath, params) => callDns(server, apiPath, params, fetchT, httpsAgent);

  return {
    // Legacy Technitium-specific interface (unchanged)
    call,
    buildUrl: buildDnsUrl,
    requireToken: require,
    ensureToken,
    createRecord: create,
    getToken: () => dnsToken,
    setToken: (t) => { dnsToken = t; },
    getTokenExpiry: () => dnsTokenExpiry,
    setTokenExpiry: (e) => { dnsTokenExpiry = e; },
    getTokenForServer: getForServer,
    invalidateTokenForServer,
    refresh,
    credentialsFile: DNS_CREDENTIALS_FILE,

    // Provider-aware methods (new)
    getProviderId: providerCtx.getProviderId,
    getActiveProvider: providerCtx.getActiveProvider,
    getAvailableProviders: providerCtx.getAvailableProviders,
    supportsCapability: providerCtx.supportsCapability,

    // Universal DNS helpers (delegated to provider context)
    universalCreateRecord: providerCtx.universalCreateRecord,
    universalDeleteRecord: providerCtx.universalDeleteRecord,
    universalResolveRecord: providerCtx.universalResolveRecord,
  };
}

module.exports = { createDnsContext };
