import { Router, type Request, type Response } from 'express';
import path from 'path';
import { promises as fsPromises } from 'fs';
import { authMiddleware } from '../middleware/auth';
import { requireAdmin } from '../middleware/tierGates';
import { requirePermission } from '../middleware/permissions';
import { templateService } from '../services/TemplateService';
import { FileSystemService } from '../services/FileSystemService';
import { ComposeService } from '../services/ComposeService';
import { DatabaseService } from '../services/DatabaseService';
import { ErrorParser } from '../utils/ErrorParser';
import { isValidStackName, isPathWithinBase } from '../utils/validation';
import { isDebugEnabled } from '../utils/debug';
import { getErrorMessage } from '../utils/errors';
import { runPolicyGate, triggerPostDeployScan } from '../helpers/policyGate';
import { invalidateNodeCaches } from '../helpers/cacheInvalidation';
import { getRegistryDeliveryContext, getRegistryDeliveryLockContext } from '../helpers/registryDeliveryContext';
import { materializePreparedSourceToStack } from '../helpers/registryDeliveryMaterialize';
import { StackOpLockService } from '../services/StackOpLockService';
import { getTerminalWs, DEPLOY_SESSION_HEADER } from '../websocket/generic';

function tryAcquireRegistryDeliveryLock(
  req: Request,
  res: Response,
  stackName: string,
): boolean {
  if (!getRegistryDeliveryContext()) return true;
  const lockContext = getRegistryDeliveryLockContext();
  if (!lockContext) {
    res.status(400).json({ error: 'Registry delivery lock context missing' });
    return false;
  }
  const user = req.user?.username ?? 'system';
  const result = StackOpLockService.getInstance().tryAcquire(
    req.nodeId,
    stackName,
    'deploy',
    user,
    lockContext,
  );
  if (!result.acquired) {
    res.status(409).json({
      error: `${stackName} is already busy`,
      code: 'stack_op_in_progress',
      inProgress: {
        action: result.existing.action,
        startedAt: result.existing.startedAt,
        user: result.existing.user,
      },
    });
    return false;
  }
  return true;
}

export const templatesRouter = Router();

templatesRouter.get('/', authMiddleware, async (req: Request, res: Response) => {
  if (!requirePermission(req, res, 'stack:read')) return;
  try {
    const templates = await templateService.getTemplates();

    const imageRefs = templates.map(t => t.image).filter((i): i is string => !!i);
    const scanSummary = DatabaseService.getInstance().getLatestScanSummaryByImageRefs(req.nodeId, imageRefs);

    const topCandidates = templates
      .map((t, i) => ({ t, i }))
      .filter(({ t }) => (t.stars ?? 0) > 0)
      .sort((a, b) => (b.t.stars ?? 0) - (a.t.stars ?? 0))
      .slice(0, 5);
    const weekIndex = Math.floor(Date.now() / (7 * 86_400_000));
    const featuredIndex = topCandidates.length > 0
      ? topCandidates[weekIndex % topCandidates.length].i
      : -1;

    const enriched = templates.map((t, i) => {
      const summary = t.image ? scanSummary.get(t.image) : undefined;
      const scan_status: 'clean' | 'vulnerable' | 'unscanned' = summary
        ? (summary.total === 0 ? 'clean' : 'vulnerable')
        : 'unscanned';
      return {
        ...t,
        scan_status,
        scan_cve_count: summary?.total ?? 0,
        scan_critical_count: summary?.critical ?? 0,
        scan_high_count: summary?.high ?? 0,
        featured: i === featuredIndex,
      };
    });

    res.json(enriched);
  } catch (error) {
    console.error('[Templates] Failed to fetch:', error);
    res.status(500).json({ error: getErrorMessage(error, 'Failed to fetch templates') });
  }
});

templatesRouter.post('/refresh-cache', authMiddleware, (req: Request, res: Response) => {
  if (!requireAdmin(req, res)) return;
  templateService.clearCache();
  console.log('[Templates] Cache cleared by', req.user?.username || 'unknown');
  res.json({ success: true });
});

templatesRouter.post('/deploy', authMiddleware, async (req: Request, res: Response) => {
  if (!requirePermission(req, res, 'stack:create')) return;
  if (!requirePermission(req, res, 'stack:deploy')) return;
  let needsDeliveryLock = false;
  let lockedStackName: string | null = null;
  try {
    const { stackName, template, envVars, skip_scan } = req.body;

    if (!stackName || !template) {
      return res.status(400).json({ error: 'stackName and template are required' });
    }

    if (!isValidStackName(stackName)) {
      return res.status(400).json({ error: 'Stack name can only contain alphanumeric characters, hyphens, and underscores' });
    }

    const fsService = FileSystemService.getInstance(req.nodeId);
    const baseDir = fsService.getBaseDir();
    const stackPath = path.join(baseDir, stackName);
    if (!isPathWithinBase(stackPath, baseDir)) {
      return res.status(400).json({ error: 'Invalid stack path' });
    }

    needsDeliveryLock = Boolean(getRegistryDeliveryContext());
    if (needsDeliveryLock && !tryAcquireRegistryDeliveryLock(req, res, stackName)) {
      return;
    }
    lockedStackName = stackName;

    try {
      await fsPromises.access(stackPath);

      if (await fsService.hasComposeFile(stackPath)) {
        return res.status(409).json({
          error: `A stack directory named '${stackName}' already exists. Please choose a different Stack Name.`,
          rolledBack: false
        });
      }

      console.log(`[Templates] Cleaned up orphaned stack directory: ${stackName}`);
      await fsService.deleteStack(stackName);
    } catch {
      // Directory does not exist; proceed with deploy
    }

    const debug = isDebugEnabled();
    const deployStartedAt = Date.now();
    console.log(`[Templates] Deploy started: ${stackName}`);
    if (debug) console.debug('[Templates:debug] Deploy payload', {
      stackName,
      // title/source are registry/client-supplied; bound the title so a long
      // value cannot bloat the log line. No env values or YAML are logged.
      templateTitle: String(template.title ?? '').slice(0, 80),
      source: String(template.source ?? '').slice(0, 40),
      portCount: Array.isArray(template.ports) ? template.ports.length : 0,
      volumeCount: Array.isArray(template.volumes) ? template.volumes.length : 0,
      envVarCount: envVars ? Object.keys(envVars).length : 0,
    });

    await fsService.createStack(stackName);

    const deliveryPrepId = getRegistryDeliveryContext()?.envelope.prepId;
    if (deliveryPrepId) {
      await materializePreparedSourceToStack(deliveryPrepId, req.nodeId, stackName);
    } else {
      const composeYaml = templateService.generateComposeFromTemplate(template, stackName);
      await fsService.saveStackContent(stackName, composeYaml);

      if (envVars && Object.keys(envVars).length > 0) {
        const envString = templateService.generateEnvString(envVars);
        await fsService.saveEnvContent(stackName, envString);
      }
    }

    try {
      if (!(await runPolicyGate(req, res, stackName, req.nodeId))) {
        try {
          await fsService.deleteStack(stackName);
        } catch (cleanupErr) {
          console.error(`[Templates] Cleanup after policy block failed for ${stackName}:`, cleanupErr);
        }
        return;
      }
      const atomic = true;
      await ComposeService.getInstance(req.nodeId).deployStack(
        stackName,
        getTerminalWs(req.get(DEPLOY_SESSION_HEADER)),
        atomic,
        req.deployContext ?? { source: 'template', actor: req.user?.username ?? null },
      );
      invalidateNodeCaches(req.nodeId);
      console.log(`[Templates] Deploy completed: ${stackName}`);
      if (debug) console.debug(`[Templates:debug] Deploy timing: ${stackName} took ${Date.now() - deployStartedAt}ms`);
      res.json({ success: true, message: 'Template deployed successfully' });
      if (!skip_scan) {
        triggerPostDeployScan(stackName, req.nodeId).catch(err =>
          console.error(`[Security] Post-deploy scan failed for ${stackName}:`, err),
        );
      }
    } catch (deployError: unknown) {
      const rawError = getErrorMessage(deployError, String(deployError));
      console.error(`[Templates] Deploy failed: ${stackName} -`, rawError);
      const parsed = ErrorParser.parse(rawError);

      const shouldRollback = parsed.rule ? parsed.rule.canSilentlyRollback : true;
      let rolledBack = false;

      if (shouldRollback) {
        let dockerDownCompleted = true;
        let fileDeleteCompleted = true;
        try {
          await ComposeService.getInstance(req.nodeId).downStack(stackName, { removeVolumes: true });
        } catch (downErr) {
          dockerDownCompleted = false;
          console.error("[Templates] Rollback Stage 1 (Docker down) failed:", downErr);
        }

        try {
          await fsService.deleteStack(stackName);
        } catch (fsErr) {
          fileDeleteCompleted = false;
          console.error("[Templates] Rollback Stage 2 (File deletion) failed:", fsErr);
        }
        rolledBack = dockerDownCompleted && fileDeleteCompleted;
      }

      invalidateNodeCaches(req.nodeId);
      res.status(500).json({
        error: parsed.message,
        rolledBack,
        ruleId: parsed.rule?.id || 'UNKNOWN'
      });
    }
  } catch (error: unknown) {
    const message = getErrorMessage(error, 'Failed to deploy template');
    console.error('[Templates] Deploy error:', message);
    res.status(500).json({ error: message });
  } finally {
    if (needsDeliveryLock && lockedStackName) {
      StackOpLockService.getInstance().release(req.nodeId, lockedStackName);
    }
  }
});
