'use strict';

const fs = require('fs');
const path = require('path');

const { atomicWriteFileSync } = require('../atomic-write');
const {
  normalizeBindHost,
  parseAllowedOrigins,
  splitOriginList,
} = require('./transport-security');
const { normalizeCloudflareAccessSettings } = require('./cloudflare-access');

const TRANSPORT_ENV_KEYS = [
  'CTM_HOST',
  'CTM_TLS_CERT',
  'CTM_TLS_KEY',
  'CTM_ALLOWED_ORIGINS',
  'CTM_CF_ACCESS_ENABLED',
  'CTM_CF_ACCESS_REQUIRED',
  'CTM_CF_ACCESS_HOSTS',
  'CTM_CF_ACCESS_TEAM_DOMAIN',
  'CTM_CF_ACCESS_AUD',
  'CTM_CF_ACCESS_EMAILS',
  'CTM_CF_ACCESS_JWKS_URL',
];

function cleanEnvValue(value, maxLen = 1000) {
  return String(value || '').replace(/[\r\n]/g, '').trim().slice(0, maxLen);
}

function envLine(key, value) {
  const s = String(value || '');
  if (/^[A-Za-z0-9_./:@,-]+$/.test(s)) return `${key}=${s}`;
  return `${key}=${JSON.stringify(s)}`;
}

function defaultTransportEnvPath() {
  return path.resolve(__dirname, '..', '..', '.env');
}

function writeEnvTransportConfig(values, options = {}) {
  const envPath = options.envPath || defaultTransportEnvPath();
  const transportEnvKeys = options.transportEnvKeys || TRANSPORT_ENV_KEYS;
  const keys = new Set(transportEnvKeys);
  const lines = [];
  try {
    const existing = fs.readFileSync(envPath, 'utf8');
    for (const line of existing.split('\n')) {
      const m = line.match(/^\s*#?\s*([A-Z0-9_]+)\s*=/);
      if (m && keys.has(m[1])) continue;
      lines.push(line);
    }
  } catch {
    lines.push('# Wall-E configuration');
    lines.push('');
  }
  while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop();
  lines.push('');
  for (const key of transportEnvKeys) {
    if (values[key]) lines.push(envLine(key, values[key]));
  }
  atomicWriteFileSync(envPath, lines.join('\n') + '\n', { mode: 0o600 });
}

function strictAllowedOrigins(raw) {
  const pieces = splitOriginList(raw);
  const origins = [];
  const seen = new Set();
  for (const piece of pieces) {
    const parsed = parseAllowedOrigins([piece]);
    if (parsed.length !== 1) {
      return { ok: false, error: 'Allowed origins must be exact http:// or https:// origins with no path, query, username, or password.' };
    }
    if (!seen.has(parsed[0])) {
      seen.add(parsed[0]);
      origins.push(parsed[0]);
    }
  }
  return { ok: true, origins };
}

function transportEnvValuesFromInput(data, env = process.env, options = {}) {
  const hostDecision = normalizeBindHost(data.host || data.ctm_host || '127.0.0.1');
  if (!hostDecision.ok) {
    return { ok: false, status: 400, error: hostDecision.message, code: hostDecision.code };
  }

  const certPath = cleanEnvValue(data.tls_cert || data.tlsCert || '', 1000);
  const keyPath = cleanEnvValue(data.tls_key || data.tlsKey || '', 1000);
  if ((certPath && !keyPath) || (!certPath && keyPath)) {
    return { ok: false, status: 400, error: 'CTM_TLS_CERT and CTM_TLS_KEY must be set together.' };
  }
  const fsImpl = options.fs || fs;
  if (certPath && (!fsImpl.existsSync(certPath) || !fsImpl.existsSync(keyPath))) {
    return { ok: false, status: 400, error: 'TLS certificate and key paths must both exist before restart.' };
  }

  const originsDecision = strictAllowedOrigins(data.allowed_origins || data.allowedOrigins || '');
  if (!originsDecision.ok) {
    return { ok: false, status: 400, error: originsDecision.error };
  }

  const cfInput = data.cloudflare_access || data.cloudflareAccess || null;
  const cfEnv = cfInput
    ? normalizeCloudflareAccessSettings(cfInput, env).env
    : {
      CTM_CF_ACCESS_ENABLED: env.CTM_CF_ACCESS_ENABLED || '',
      CTM_CF_ACCESS_REQUIRED: env.CTM_CF_ACCESS_REQUIRED || '',
      CTM_CF_ACCESS_HOSTS: env.CTM_CF_ACCESS_HOSTS || '',
      CTM_CF_ACCESS_TEAM_DOMAIN: env.CTM_CF_ACCESS_TEAM_DOMAIN || '',
      CTM_CF_ACCESS_AUD: env.CTM_CF_ACCESS_AUD || '',
      CTM_CF_ACCESS_EMAILS: env.CTM_CF_ACCESS_EMAILS || '',
      CTM_CF_ACCESS_JWKS_URL: env.CTM_CF_ACCESS_JWKS_URL || '',
    };
  return {
    ok: true,
    origins: originsDecision.origins,
    envValues: {
      CTM_HOST: hostDecision.host,
      CTM_TLS_CERT: certPath,
      CTM_TLS_KEY: keyPath,
      CTM_ALLOWED_ORIGINS: originsDecision.origins.join(','),
      ...cfEnv,
    },
  };
}

function mergeCloudflareTransportEnv(cfEnv, origin, env = process.env) {
  const origins = [];
  const seen = new Set();
  for (const item of [...parseAllowedOrigins(env.CTM_ALLOWED_ORIGINS || ''), origin]) {
    if (!item || seen.has(item)) continue;
    seen.add(item);
    origins.push(item);
  }
  return {
    CTM_HOST: env.CTM_HOST || '',
    CTM_TLS_CERT: env.CTM_TLS_CERT || '',
    CTM_TLS_KEY: env.CTM_TLS_KEY || '',
    CTM_ALLOWED_ORIGINS: origins.join(','),
    CTM_CF_ACCESS_ENABLED: cfEnv.CTM_CF_ACCESS_ENABLED || '',
    CTM_CF_ACCESS_REQUIRED: cfEnv.CTM_CF_ACCESS_REQUIRED || '',
    CTM_CF_ACCESS_HOSTS: cfEnv.CTM_CF_ACCESS_HOSTS || '',
    CTM_CF_ACCESS_TEAM_DOMAIN: cfEnv.CTM_CF_ACCESS_TEAM_DOMAIN || '',
    CTM_CF_ACCESS_AUD: cfEnv.CTM_CF_ACCESS_AUD || '',
    CTM_CF_ACCESS_EMAILS: cfEnv.CTM_CF_ACCESS_EMAILS || '',
    CTM_CF_ACCESS_JWKS_URL: cfEnv.CTM_CF_ACCESS_JWKS_URL || '',
  };
}

function _isDevTunnelOrigin(origin) {
  try { return new URL(origin).hostname.endsWith('.devtunnels.ms'); } catch { return false; }
}

function mergeAllowedOriginTransportEnv(origin, env = process.env) {
  // A managed Dev Tunnel origin REPLACES any previous devtunnels.ms origin: CTM
  // runs one managed tunnel, and a rotated tunnel's old hostname is dead — it
  // must not stay allowlisted. Non-devtunnel origins (Tailscale, custom) are kept.
  const replacingDevTunnel = !!origin && _isDevTunnelOrigin(origin);
  const origins = [];
  const seen = new Set();
  for (const item of [...parseAllowedOrigins(env.CTM_ALLOWED_ORIGINS || ''), origin]) {
    if (!item || seen.has(item)) continue;
    if (replacingDevTunnel && item !== origin && _isDevTunnelOrigin(item)) continue;
    seen.add(item);
    origins.push(item);
  }
  return {
    CTM_HOST: env.CTM_HOST || '',
    CTM_TLS_CERT: env.CTM_TLS_CERT || '',
    CTM_TLS_KEY: env.CTM_TLS_KEY || '',
    CTM_ALLOWED_ORIGINS: origins.join(','),
    CTM_CF_ACCESS_ENABLED: env.CTM_CF_ACCESS_ENABLED || '',
    CTM_CF_ACCESS_REQUIRED: env.CTM_CF_ACCESS_REQUIRED || '',
    CTM_CF_ACCESS_HOSTS: env.CTM_CF_ACCESS_HOSTS || '',
    CTM_CF_ACCESS_TEAM_DOMAIN: env.CTM_CF_ACCESS_TEAM_DOMAIN || '',
    CTM_CF_ACCESS_AUD: env.CTM_CF_ACCESS_AUD || '',
    CTM_CF_ACCESS_EMAILS: env.CTM_CF_ACCESS_EMAILS || '',
    CTM_CF_ACCESS_JWKS_URL: env.CTM_CF_ACCESS_JWKS_URL || '',
  };
}

module.exports = {
  TRANSPORT_ENV_KEYS,
  cleanEnvValue,
  envLine,
  strictAllowedOrigins,
  transportEnvValuesFromInput,
  writeEnvTransportConfig,
  mergeCloudflareTransportEnv,
  mergeAllowedOriginTransportEnv,
};
