/**
 * Express application setup
 * Configures middleware, assembles context, and mounts routes
 */
const express = require('express');
const https = require('https');
const fs = require('fs');

// Configuration
const config = require('./config');
const { assembleContext } = require('./context');
const { createLogger, logError, safeErrorMessage } = require('./utils/logging');
const { fetchT } = require('./utils/http');
const { errorResponse, ok } = require('./utils/responses');
const { asyncHandler } = require('./utils/async-handler');

// Managers and utilities
const StateManager = require('../state-manager');
const { LicenseManager } = require('../license-manager');
const credentialManager = require('../credential-manager');
const authManager = require('../auth-manager');
const dockerSecurity = require('../docker-security');
const auditLogger = require('../audit-logger');
const portLockManager = require('../port-lock-manager');
const resourceMonitor = require('../resource-monitor');
const backupManager = require('../backup-manager');
const healthChecker = require('../health-checker');
const updateManager = require('../update-manager');
const selfUpdater = require('../self-updater');
const configureMiddleware = require('../middleware');
const { validateStartupConfig, syncHealthCheckerServices } = require('../startup-validator');
const { CSRF_HEADER_NAME } = require('../csrf-protection');
const { resolveServiceUrl } = require('../url-resolver');
const metrics = require('../metrics');
const { validateURL } = require('../input-validator');

// Optional modules
let dockerMaintenance, logDigest;
try { dockerMaintenance = require('../docker-maintenance'); } catch (_) { /* optional module */ }
try { logDigest = require('../log-digest'); } catch (_) { /* optional module */ }

// Templates
const { APP_TEMPLATES, TEMPLATE_CATEGORIES, DIFFICULTY_LEVELS } = require('../app-templates');
const { RECIPE_TEMPLATES, RECIPE_CATEGORIES } = require('../recipe-templates');

// Route modules
const healthRoutes = require('../routes/health');
const monitoringRoutes = require('../routes/monitoring');
const updatesRoutes = require('../routes/updates');
const authRoutes = require('../routes/auth');
const configRoutes = require('../routes/config');
const dnsRoutes = require('../routes/dns');
const notificationRoutes = require('../routes/notifications');
const containerRoutes = require('../routes/containers');
const serviceRoutes = require('../routes/services');
const tailscaleRoutes = require('../routes/tailscale');
const sitesRoutes = require('../routes/sites');
const credentialsRoutes = require('../routes/credentials');
const arrRoutes = require('../routes/arr');
const appsRoutes = require('../routes/apps');
const logsRoutes = require('../routes/logs');
const backupsRoutes = require('../routes/backups');
const caRoutes = require('../routes/ca');
const browseRoutes = require('../routes/browse');
const errorLogsRoutes = require('../routes/errorlogs');
const licenseRoutes = require('../routes/license');
const recipesRoutes = require('../routes/recipes');
const themesRoutes = require('../routes/themes');
const dockerResourcesRoutes = require('../routes/docker-resources');
const eventsRoutes = require('../routes/events');

// Constants
const { APP } = require('../constants');

/**
 * Create and configure the Express application
 */
async function createApp() {
  const app = express();

  // Initialize logging
  const log = createLogger(config.LOG_LEVEL);

  // Load site configuration
  config.loadSiteConfig(config.CONFIG_FILE, log);

  // Create state managers
  const servicesStateManager = new StateManager(config.SERVICES_FILE);
  const configStateManager = new StateManager(config.CONFIG_FILE);

  // Initialize license manager
  const licenseManager = new LicenseManager(credentialManager, config.CONFIG_FILE, console);
  licenseManager.loadSecret(config.LICENSE_SECRET_FILE);

  // HTTPS agent for internal CA
  const CA_CERT_PATH = process.env.CA_CERT_PATH || '/app/pki/root.crt';
  let httpsAgent;
  try {
    const caCert = fs.readFileSync(CA_CERT_PATH);
    httpsAgent = new https.Agent({ ca: [...require('tls').rootCertificates, caCert] });
    log.info('server', 'HTTPS agent configured with CA certificate', { path: CA_CERT_PATH });
  } catch {
    httpsAgent = new https.Agent();
    log.warn('server', 'CA cert not found — HTTPS calls may fail', { path: CA_CERT_PATH });
  }

  // TOTP configuration
  const totpConfig = {
    enabled: false,
    sessionDuration: 'never',
    isSetUp: false
  };

  // Load TOTP config from file
  try {
    if (fs.existsSync(config.TOTP_CONFIG_FILE)) {
      const loaded = JSON.parse(fs.readFileSync(config.TOTP_CONFIG_FILE, 'utf8'));
      delete loaded.secret; // secret belongs only in credential-manager
      Object.assign(totpConfig, loaded);
      log.info('config', 'TOTP config loaded', { enabled: totpConfig.enabled });
    }
  } catch (e) {
    log.warn('config', 'Could not load TOTP config', { error: e.message });
  }

  // Tailscale configuration  
  const tailscaleConfig = {
    enabled: false,
    requireAuth: false,
    allowedTailnet: null,
    devices: [],
    oauthConfigured: false,
    tailnet: null,
    syncInterval: 300,
    lastSync: null
  };

  // Helper functions needed by middleware
  function isValidContainerId(id) {
    const CONTAINER_ID_RE = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/;
    return typeof id === 'string' && CONTAINER_ID_RE.test(id);
  }

  function isTailscaleIP(ip) {
    if (!ip) return false;
    const parts = ip.split('.');
    if (parts.length !== 4) return false;
    const first = parseInt(parts[0]);
    const second = parseInt(parts[1]);
    return first === 100 && second >= 64 && second <= 127;
  }

  async function getTailscaleStatus() {
    // Stub for now - will be populated by context
    return null;
  }

  // Configure middleware
  const middlewareResult = configureMiddleware(app, {
    siteConfig: config.siteConfig,
    totpConfig,
    tailscaleConfig,
    metrics,
    auditLogger,
    authManager,
    log,
    cryptoUtils: require('../crypto-utils'),
    isValidContainerId,
    isTailscaleIP,
    getTailscaleStatus,
    RATE_LIMITS: require('../constants').RATE_LIMITS,
    LIMITS: require('../constants').LIMITS,
    APP: require('../constants').APP,
    CACHE_CONFIGS: require('../cache-config').CACHE_CONFIGS,
    createCache: require('../cache-config').createCache,
  });

  const { strictLimiter } = middlewareResult;

  // Helper functions
  async function getServiceById(serviceId) {
    const services = await servicesStateManager.read();
    return services.find(s => s.id === serviceId) || null;
  }

  async function readConfig() {
    const { readJsonFile } = require('../fs-helpers');
    return readJsonFile(config.CONFIG_FILE, {});
  }

  async function saveConfig(updates) {
    return await configStateManager.update(cfg => Object.assign(cfg, updates));
  }

  async function addServiceToConfig(service) {
    await servicesStateManager.update(services => {
      const existingIndex = services.findIndex(s => s.id === service.id);
      if (existingIndex >= 0) {
        services[existingIndex] = { ...services[existingIndex], ...service };
      } else {
        services.push(service);
      }
      return services;
    });
    log.info('deploy', 'Service added to config', { serviceId: service.id });
  }

  async function saveTotpConfig() {
    try {
      const { writeJsonFile } = require('../fs-helpers');
      await writeJsonFile(config.TOTP_CONFIG_FILE, totpConfig);
    } catch (e) {
      log.error('config', 'Could not save TOTP config', { error: e.message });
    }
  }

  async function loadNotificationConfig() {
    // Stub - will be implemented
  }

  async function resyncHealthChecker() {
    return syncHealthCheckerServices({
      log,
      SERVICES_FILE: config.SERVICES_FILE,
      servicesStateManager,
      healthChecker,
      buildServiceUrl: config.buildServiceUrl,
      siteConfig: config.siteConfig,
      APP
    });
  }

  // Create bound logError function
  const boundLogError = (context, error, additionalInfo) =>
    logError(config.ERROR_LOG_FILE, config.MAX_ERROR_LOG_SIZE, context, error, additionalInfo, log);

  // Create bound asyncHandler
  const boundAsyncHandler = (fn, context) => asyncHandler(boundLogError, fn, context);

  // Assemble context
  const ctx = assembleContext({
    // Config
    siteConfig: config.siteConfig,
    buildDomain: config.buildDomain,
    buildServiceUrl: config.buildServiceUrl,
    SERVICES_FILE: config.SERVICES_FILE,
    CONFIG_FILE: config.CONFIG_FILE,
    TOTP_CONFIG_FILE: config.TOTP_CONFIG_FILE,
    TAILSCALE_CONFIG_FILE: config.TAILSCALE_CONFIG_FILE,
    NOTIFICATIONS_FILE: config.NOTIFICATIONS_FILE,
    ERROR_LOG_FILE: config.ERROR_LOG_FILE,
    DNS_CREDENTIALS_FILE: config.DNS_CREDENTIALS_FILE,
    CADDYFILE_PATH: config.CADDYFILE_PATH,
    CADDY_ADMIN_URL: config.CADDY_ADMIN_URL,

    // State managers
    servicesStateManager,
    configStateManager,

    // Managers
    credentialManager,
    authManager,
    licenseManager,
    healthChecker,
    updateManager,
    backupManager,
    resourceMonitor,
    auditLogger,
    portLockManager,
    selfUpdater,
    dockerMaintenance,
    logDigest,
    dockerSecurity,

    // Templates
    APP_TEMPLATES,
    TEMPLATE_CATEGORIES,
    DIFFICULTY_LEVELS,
    RECIPE_TEMPLATES,
    RECIPE_CATEGORIES,

    // Helpers
    asyncHandler: boundAsyncHandler,
    errorResponse,
    ok,
    fetchT,
    httpsAgent,
    log,
    logError: boundLogError,
    safeErrorMessage,
    getServiceById,
    readConfig,
    saveConfig,
    addServiceToConfig,
    validateURL,
    strictLimiter,
    totpConfig,
    saveTotpConfig,
    loadSiteConfig: () => config.loadSiteConfig(config.CONFIG_FILE, log),
    loadNotificationConfig,
    resyncHealthChecker,

    // Middleware result
    middlewareResult,

    // App
    app,
  });

  // Build versioned API router
  const apiRouter = express.Router();
  
  // Mount route modules
  apiRouter.use(authRoutes(ctx));
  apiRouter.use(configRoutes(ctx));
  apiRouter.use('/dns', dnsRoutes({
    dns: ctx.dns,
    siteConfig: ctx.siteConfig,
    asyncHandler: ctx.asyncHandler,
    log: ctx.log,
    safeErrorMessage: ctx.safeErrorMessage,
    fetchT: ctx.fetchT,
    credentialManager: ctx.credentialManager
  }));
  apiRouter.use('/notifications', notificationRoutes({
    notification: ctx.notification,
    asyncHandler: ctx.asyncHandler
  }));
  apiRouter.use('/containers', containerRoutes({
    docker: ctx.docker,
    log: ctx.log,
    asyncHandler: ctx.asyncHandler
  }));
  apiRouter.use(serviceRoutes({
    servicesStateManager: ctx.servicesStateManager,
    credentialManager: ctx.credentialManager,
    siteConfig: ctx.siteConfig,
    buildServiceUrl: ctx.buildServiceUrl,
    buildDomain: ctx.buildDomain,
    fetchT: ctx.fetchT,
    asyncHandler: ctx.asyncHandler,
    SERVICES_FILE: ctx.SERVICES_FILE,
    log: ctx.log,
    safeErrorMessage: ctx.safeErrorMessage,
    resyncHealthChecker: ctx.resyncHealthChecker,
    caddy: ctx.caddy,
    dns: ctx.dns
  }));
  apiRouter.use(healthRoutes({
    fetchT: ctx.fetchT,
    SERVICES_FILE: ctx.SERVICES_FILE,
    servicesStateManager: ctx.servicesStateManager,
    siteConfig: ctx.siteConfig,
    buildServiceUrl: ctx.buildServiceUrl,
    asyncHandler: ctx.asyncHandler,
    logError: ctx.logError,
    healthChecker: ctx.healthChecker
  }));
  apiRouter.use(monitoringRoutes({
    resourceMonitor: ctx.resourceMonitor,
    docker: ctx.docker,
    asyncHandler: ctx.asyncHandler,
    log: ctx.log
  }));
  apiRouter.use(updatesRoutes({
    updateManager: ctx.updateManager,
    selfUpdater: ctx.selfUpdater,
    asyncHandler: ctx.asyncHandler,
    logError: ctx.logError
  }));
  apiRouter.use('/tailscale', tailscaleRoutes({
    tailscale: ctx.tailscale,
    caddy: ctx.caddy,
    servicesStateManager: ctx.servicesStateManager,
    credentialManager: ctx.credentialManager,
    buildDomain: ctx.buildDomain,
    asyncHandler: ctx.asyncHandler,
    SERVICES_FILE: ctx.SERVICES_FILE,
    log: ctx.log
  }));
  apiRouter.use(sitesRoutes({
    asyncHandler: ctx.asyncHandler,
    caddy: ctx.caddy,
    dns: ctx.dns,
    fetchT: ctx.fetchT,
    buildDomain: ctx.buildDomain,
    addServiceToConfig: ctx.addServiceToConfig,
    siteConfig: ctx.siteConfig,
    log: ctx.log
  }));
  apiRouter.use(credentialsRoutes({
    credentialManager: ctx.credentialManager,
    asyncHandler: ctx.asyncHandler
  }));
  apiRouter.use(arrRoutes(ctx));
  apiRouter.use(appsRoutes(ctx));
  apiRouter.use(logsRoutes({
    asyncHandler: ctx.asyncHandler,
    docker: ctx.docker,
    logDigest: ctx.logDigest,
    dockerMaintenance: ctx.dockerMaintenance
  }));
  apiRouter.use(backupsRoutes({
    backupManager: ctx.backupManager,
    asyncHandler: ctx.asyncHandler
  }));
  apiRouter.use('/ca', caRoutes(ctx));
  apiRouter.use(browseRoutes({
    asyncHandler: ctx.asyncHandler,
    validateSecurePath: ctx.validateSecurePath,
    auditLogger: ctx.auditLogger,
    docker: ctx.docker
  }));
  apiRouter.use(errorLogsRoutes({
    ERROR_LOG_FILE: ctx.ERROR_LOG_FILE,
    auditLogger: ctx.auditLogger,
    asyncHandler: ctx.asyncHandler
  }));
  apiRouter.use('/license', licenseRoutes({
    licenseManager: ctx.licenseManager,
    asyncHandler: ctx.asyncHandler
  }));
  apiRouter.use('/recipes', recipesRoutes(ctx));
  apiRouter.use(themesRoutes({ asyncHandler: ctx.asyncHandler, log: ctx.log }));
  apiRouter.use('/docker', dockerResourcesRoutes({
    docker: ctx.docker,
    asyncHandler: ctx.asyncHandler
  }));
  apiRouter.use('/events', eventsRoutes({
    resourceMonitor: ctx.resourceMonitor,
    healthChecker: ctx.healthChecker,
    updateManager: ctx.updateManager,
    logError: ctx.logError
  }));

  // Inline API routes
  apiRouter.get('/health', (req, res) => {
    res.json({ status: 'ok', timestamp: new Date().toISOString() });
  });
  
  apiRouter.get('/csrf-token', (req, res) => {
    res.json({ success: true, token: req.csrfToken, headerName: CSRF_HEADER_NAME });
  });
  
  apiRouter.get('/metrics', (req, res) => {
    res.json({ success: true, metrics: metrics.getSummary() });
  });

  // Mount at /api/v1 (canonical) and /api (legacy)
  app.use('/api/v1', apiRouter);
  app.use('/api', apiRouter);

  // Root-level health check
  app.get('/health', (req, res) => {
    res.json({ status: 'ok', timestamp: new Date().toISOString() });
  });

  // Lightweight probe endpoint
  app.get('/probe/:id', boundAsyncHandler(async (req, res) => {
    const id = req.params.id;
    const { exists } = require('../fs-helpers');

    let service = null;
    if (id !== 'internet' && await exists(config.SERVICES_FILE)) {
      const data = await servicesStateManager.read();
      const services = Array.isArray(data) ? data : data.services || [];
      service = services.find(s => s.id === id);
    }

    const url = resolveServiceUrl(id, service, config.siteConfig, config.buildServiceUrl);
    const parsed = new URL(url);
    const isHttps = parsed.protocol === 'https:';
    const lib = isHttps ? https : require('http');

    const options = {
      hostname: parsed.hostname,
      port: parsed.port || (isHttps ? 443 : 80),
      path: parsed.pathname + parsed.search,
      method: 'HEAD',
      timeout: 5000,
      agent: isHttps ? httpsAgent : undefined,
      headers: { 'User-Agent': APP.USER_AGENTS.PROBE },
    };

    const makeRequest = (method) => new Promise((resolve, reject) => {
      const reqOpts = { ...options, method };
      const probeReq = lib.request(reqOpts, (response) => {
        response.resume();
        resolve(response.statusCode);
      });
      probeReq.on('error', reject);
      probeReq.on('timeout', () => { probeReq.destroy(); reject(new Error('Timeout')); });
      probeReq.end();
    });

    let statusCode;
    try {
      statusCode = await makeRequest('HEAD');
      if (statusCode === 501 || statusCode === 405) {
        statusCode = await makeRequest('GET');
      }
    } catch {
      // Direct probe failed — try Pylon relay if configured
      const pylonConfig = config.siteConfig?.pylon;
      if (pylonConfig?.url) {
        try {
          const pylonUrl = `${pylonConfig.url}/probe?url=${encodeURIComponent(url)}`;
          const headers = { 'User-Agent': APP.USER_AGENTS.PROBE };
          if (pylonConfig.key) headers['x-pylon-key'] = pylonConfig.key;
          const controller = new AbortController();
          const pylonTimeout = setTimeout(() => controller.abort(), 8000);
          const pylonRes = await fetchT(pylonUrl, { method: 'GET', signal: controller.signal, headers });
          clearTimeout(pylonTimeout);
          if (pylonRes.ok) {
            const data = await pylonRes.json();
            statusCode = data.statusCode || 502;
          }
        } catch {
          // Pylon also failed — fall through to domain fallback
        }
      }

      // Domain-based fallback (last resort)
      if (!statusCode) {
        const fallbackUrl = `https://${config.buildDomain(id)}`;
        const fp = new URL(fallbackUrl);
        statusCode = await new Promise((resolve, reject) => {
          const fReq = https.request({
            hostname: fp.hostname,
            port: 443,
            path: '/',
            method: 'GET',
            timeout: 5000,
            agent: httpsAgent,
            headers: { 'User-Agent': APP.USER_AGENTS.PROBE }
          }, (fRes) => {
            fRes.resume();
            resolve(fRes.statusCode);
          });
          fReq.on('error', reject);
          fReq.on('timeout', () => { fReq.destroy(); reject(new Error('Timeout')); });
          fReq.end();
        }).catch(() => 502);
      }
    }

    res.status(statusCode).send();
  }, 'probe'));

  // Network IPs endpoint
  app.get('/api/network/ips', (req, res) => {
    try {
      const os = require('os');
      const envLan = process.env.HOST_LAN_IP;
      const envTailscale = process.env.HOST_TAILSCALE_IP;

      const result = {
        localhost: '127.0.0.1',
        lan: envLan || null,
        tailscale: envTailscale || null,
        all: []
      };

      if (!envLan || !envTailscale) {
        const interfaces = os.networkInterfaces();
        for (const [name, addrs] of Object.entries(interfaces)) {
          for (const addr of addrs) {
            if (addr.internal || addr.family !== 'IPv4') continue;
            const ip = addr.address;
            result.all.push({ name, ip });

            if (!result.tailscale && ip.startsWith('100.')) {
              result.tailscale = ip;
            } else if (!result.lan && (ip.startsWith('192.168.') || ip.startsWith('10.') || ip.match(/^172\.(1[6-9]|2[0-9]|3[0-1])\./))) {
              result.lan = ip;
            }
          }
        }
      }

      res.json(result);
    } catch (error) {
      errorResponse(res, 500, safeErrorMessage(error));
    }
  });

  // API Documentation
  app.get('/api/docs', (req, res) => {
    res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline' https://unpkg.com; style-src 'self' 'unsafe-inline' https://unpkg.com; img-src 'self' data: https:; connect-src 'self'; font-src 'self' data: https://unpkg.com;");
    res.send(`<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8"/>
  <title>DashCaddy API Documentation</title>
  <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css"/>
  <style>body{margin:0} .swagger-ui .topbar{display:none}</style>
</head>
<body>
  <div id="swagger-ui"></div>
  <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
  <script>SwaggerUIBundle({url:'/api/docs/spec',dom_id:'#swagger-ui',deepLinking:true})</script>
</body>
</html>`);
  });

  app.get('/api/docs/spec', boundAsyncHandler(async (req, res) => {
    const path = require('path');
    const { exists } = require('../fs-helpers');
    const fsp = require('fs').promises;
    
    const specPath = path.join(__dirname, '../openapi.yaml');
    if (await exists(specPath)) {
      const yaml = await fsp.readFile(specPath, 'utf8');
      res.type('text/yaml').send(yaml);
    } else {
      errorResponse(res, 404, 'OpenAPI spec not found');
    }
  }, 'api-docs-spec'));

  // Error handlers (MUST be last)
  const { notFoundHandler, errorMiddleware } = require('../error-handler');
  app.use('/api', notFoundHandler);
  app.use(errorMiddleware);

  return { app, log, config: config.siteConfig, licenseManager };
}

module.exports = { createApp };
