import { Router, Request, Response } from 'express';
import { AuditAction } from '@prisma/client';
import rateLimit from 'express-rate-limit';
import { prisma } from '../../lib/prisma';
import { authenticate, requireRole } from '../../middleware/auth';
import { validate } from '../../middleware/validate';
import { createInstanceSchema, updateInstanceSchema, registerInstanceSchema, reconfigureInstanceSchema, configureTunnelSchema, importInstancesSchema, startUpgradeSchema, startImageUpgradeSchema, startReleaseUpgradeSchema, setupRemoteTunnelSchema } from './instances.schemas';
import * as instancesService from './instances.service';
import * as healthService from '../../services/health.service';
import * as backupService from '../../services/backup.service';
import * as restoreService from '../../services/restore.service';
import * as upgradeService from '../../services/upgrade.service';
import * as tunnelService from '../../services/tunnel.service';
import { discoverInstances } from '../../services/discovery.service';

const secretsLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 10,
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: { message: 'Too many secrets requests, please try again later', code: 'RATE_LIMITED' } },
});

const router = Router();

// All instance routes require authentication
router.use(authenticate);

// ─── Discovery Endpoints ────────────────────────────────────────────

router.post(
  '/discover',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (_req: Request, res: Response) => {
    const result = await discoverInstances();
    res.json({ data: result });
  }
);

router.post(
  '/import',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  validate(importInstancesSchema),
  async (req: Request, res: Response) => {
    const { instances } = req.body as { instances: Array<Record<string, unknown>> };
    const results: Array<{ slug: string; success: boolean; instanceId?: string; error?: string }> = [];

    for (const inst of instances) {
      try {
        const registered = await instancesService.registerInstance(
          inst as Parameters<typeof instancesService.registerInstance>[0],
          req.user!.id,
          req.ip
        );
        results.push({ slug: inst.slug as string, success: true, instanceId: registered?.id });
      } catch (err) {
        results.push({ slug: inst.slug as string, success: false, error: (err as Error).message });
      }
    }

    const succeeded = results.filter((r) => r.success).length;
    const failed = results.filter((r) => !r.success).length;
    res.json({
      data: {
        results,
        summary: { total: results.length, succeeded, failed },
      },
    });
  }
);

// ─── CRUD Endpoints ──────────────────────────────────────────────────

router.get('/', requireRole('SUPER_ADMIN', 'OPERATOR'), async (_req: Request, res: Response) => {
  const instances = await instancesService.listInstances();
  res.json({ data: instances });
});

// Register an existing (externally-managed) instance for monitoring
router.post(
  '/register',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  validate(registerInstanceSchema),
  async (req: Request, res: Response) => {
    const instance = await instancesService.registerInstance(req.body, req.user!.id, req.ip);
    res.status(201).json({ data: instance });
  }
);

router.get('/:id', requireRole('SUPER_ADMIN', 'OPERATOR'), async (req: Request, res: Response) => {
  const instance = await instancesService.getInstance(req.params.id as string);
  res.json({ data: instance });
});

router.post(
  '/',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  validate(createInstanceSchema),
  async (req: Request, res: Response) => {
    const instance = await instancesService.createInstance(req.body, req.user!.id, req.ip);
    res.status(201).json({ data: instance });
  }
);

router.put(
  '/:id',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  validate(updateInstanceSchema),
  async (req: Request, res: Response) => {
    const instance = await instancesService.updateInstance(
      req.params.id as string,
      req.body,
      req.user!.id,
      req.ip
    );
    res.json({ data: instance });
  }
);

router.delete(
  '/:id',
  requireRole('SUPER_ADMIN'),
  async (req: Request, res: Response) => {
    const result = await instancesService.deleteInstance(req.params.id as string, req.user!.id, req.ip);
    res.json(result);
  }
);

// Get decrypted secrets (SUPER_ADMIN only, rate limited)
router.get(
  '/:id/secrets',
  secretsLimiter,
  requireRole('SUPER_ADMIN'),
  async (req: Request, res: Response) => {
    const secrets = await instancesService.getInstanceSecrets(req.params.id as string);

    // Audit log: someone viewed secrets
    await prisma.auditLog.create({
      data: {
        userId: req.user!.id,
        instanceId: req.params.id as string,
        action: AuditAction.SECRETS_VIEWED,
        details: { instanceId: req.params.id as string },
        ipAddress: req.ip,
      },
    });

    res.set('Cache-Control', 'no-store');
    res.json({ data: secrets });
  }
);

// ─── Reconfiguration ────────────────────────────────────────────────

router.post(
  '/:id/reconfigure',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  validate(reconfigureInstanceSchema),
  async (req: Request, res: Response) => {
    const result = await instancesService.reconfigureInstance(
      req.params.id as string,
      req.body,
      req.user!.id,
      req.ip
    );
    res.json({ data: result });
  }
);

// ─── Tunnel Management ──────────────────────────────────────────────

router.post(
  '/:id/tunnel',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  validate(configureTunnelSchema),
  async (req: Request, res: Response) => {
    const result = await instancesService.configureTunnel(
      req.params.id as string,
      req.body,
      req.user!.id,
      req.ip
    );
    res.json({ data: result });
  }
);

router.delete(
  '/:id/tunnel',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    // Branch: remote instances use the CCP's Pangolin API to teardown;
    // local instances use the existing manual removal logic.
    const instance = await prisma.instance.findUnique({ where: { id: req.params.id as string } });
    if (instance?.isRemote && instance.pangolinSiteId) {
      const result = await tunnelService.teardownTunnel(
        req.params.id as string,
        req.user!.id,
        req.ip
      );
      res.json({ data: result });
      return;
    }
    const result = await instancesService.removeTunnel(
      req.params.id as string,
      req.user!.id,
      req.ip
    );
    res.json({ data: result });
  }
);

// Remote tunnel setup via CCP's Pangolin API credentials
router.post(
  '/:id/tunnel/setup',
  requireRole('SUPER_ADMIN'),
  validate(setupRemoteTunnelSchema),
  async (req: Request, res: Response) => {
    const { subdomainPrefix } = req.body || {};
    const result = await tunnelService.setupTunnel(
      req.params.id as string,
      { subdomainPrefix },
      req.user!.id,
      req.ip
    );
    res.status(201).json({ data: result });
  }
);

// Get tunnel status (resource matrix) — works for both local and remote
router.get(
  '/:id/tunnel/status',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const status = await tunnelService.getTunnelStatus(req.params.id as string);
    res.json({ data: status });
  }
);

// Re-sync resources (idempotent — creates missing, leaves existing)
router.post(
  '/:id/tunnel/sync',
  requireRole('SUPER_ADMIN'),
  async (req: Request, res: Response) => {
    const result = await tunnelService.syncResources(
      req.params.id as string,
      req.user!.id,
      req.ip
    );
    res.json({ data: result });
  }
);

// Adopt a tunnel that was set up outside CCP (e.g. by config.sh --pangolin-site new)
router.post(
  '/:id/tunnel/import',
  requireRole('SUPER_ADMIN'),
  async (req: Request, res: Response) => {
    const result = await tunnelService.importTunnel(
      req.params.id as string,
      req.user!.id,
      req.ip
    );
    res.json({ data: result });
  }
);

// ─── Lifecycle Endpoints ─────────────────────────────────────────────

router.post(
  '/:id/provision',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const result = await instancesService.provisionInstance(req.params.id as string, req.user!.id, req.ip);
    res.json(result);
  }
);

router.post(
  '/:id/start',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const result = await instancesService.startInstance(req.params.id as string, req.user!.id, req.ip);
    res.json(result);
  }
);

router.post(
  '/:id/stop',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const result = await instancesService.stopInstance(req.params.id as string, req.user!.id, req.ip);
    res.json(result);
  }
);

router.post(
  '/:id/restart',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const service = req.query.service as string | undefined;
    const result = await instancesService.restartInstance(
      req.params.id as string,
      req.user!.id,
      req.ip,
      service
    );
    res.json(result);
  }
);

// ─── Services & Logs ─────────────────────────────────────────────────

router.get(
  '/:id/services',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const services = await instancesService.getInstanceServices(req.params.id as string);
    res.json({ data: services });
  }
);

router.get(
  '/:id/logs',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const { service, tail, since } = req.query;
    const tailNum = tail ? Math.min(Math.max(parseInt(tail as string, 10) || 200, 1), 2000) : 200;
    const logs = await instancesService.getInstanceLogs(
      req.params.id as string,
      service as string | undefined,
      tailNum,
      since as string | undefined
    );
    res.json({ data: logs });
  }
);

// ─── Upgrades ──────────────────────────────────────────────────────

router.post(
  '/:id/check-update',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const status = await upgradeService.checkForUpdates(req.params.id as string);
    res.json({ data: status });
  }
);

router.post(
  '/:id/upgrade',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  validate(startUpgradeSchema),
  async (req: Request, res: Response) => {
    const { skipBackup, useRegistry, branch } = req.body || {};
    const upgrade = await upgradeService.startUpgrade(
      req.params.id as string,
      req.user!.id,
      req.ip,
      { skipBackup, useRegistry, branch }
    );
    res.status(201).json({ data: upgrade });
  }
);

// Image-only upgrade (Approach B). Faster + safer than full upgrade for
// releases that don't change orchestration/templates. See upgrade.service.ts
// startImageUpgrade for full rationale.
router.post(
  '/:id/upgrade-images',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  validate(startImageUpgradeSchema),
  async (req: Request, res: Response) => {
    const { imageTag } = req.body || {};
    const upgrade = await upgradeService.startImageUpgrade(
      req.params.id as string,
      req.user!.id,
      req.ip,
      { imageTag }
    );
    res.status(201).json({ data: upgrade });
  }
);

// Release upgrade (Approach C). Re-renders templates via CCP and applies
// them to the tenant, then composePull + composeUp. Used when a release
// changes orchestration in addition to image versions.
router.post(
  '/:id/upgrade-release',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  validate(startReleaseUpgradeSchema),
  async (req: Request, res: Response) => {
    const { imageTag } = req.body || {};
    const upgrade = await upgradeService.startReleaseUpgrade(
      req.params.id as string,
      req.user!.id,
      req.ip,
      { imageTag }
    );
    res.status(201).json({ data: upgrade });
  }
);

// Approach C pre-flight: preview what re-render would change before applying.
// READ-ONLY — tenant disk is not touched.
router.post(
  '/:id/upgrade-release/preview',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  validate(startReleaseUpgradeSchema),
  async (req: Request, res: Response) => {
    const { imageTag } = req.body || {};
    const preview = await upgradeService.previewReleaseUpgrade(
      req.params.id as string,
      { imageTag }
    );
    res.json({ data: preview });
  }
);

router.get(
  '/:id/upgrade-status',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const status = await upgradeService.getUpgradeStatus(req.params.id as string);
    res.json({ data: status });
  }
);

router.get(
  '/:id/upgrade-history',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const page = Math.max(1, parseInt(req.query.page as string, 10) || 1);
    const limit = Math.min(100, Math.max(1, parseInt(req.query.limit as string, 10) || 20));
    const result = await upgradeService.getUpgradeHistory(req.params.id as string, page, limit);
    res.json(result);
  }
);

// ─── Health Checks ──────────────────────────────────────────────────

router.post(
  '/:id/health-check',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const check = await healthService.checkInstanceHealth(req.params.id as string);
    res.json({ data: check });
  }
);

router.get(
  '/:id/health-history',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const page = Math.max(1, parseInt(req.query.page as string, 10) || 1);
    const limit = Math.min(100, Math.max(1, parseInt(req.query.limit as string, 10) || 20));
    const result = await healthService.getHealthHistory(req.params.id as string, page, limit);
    res.json(result);
  }
);

// ─── Backups ────────────────────────────────────────────────────────

router.post(
  '/:id/backup',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const backup = await backupService.createBackup(req.params.id as string, req.user!.id, req.ip);
    res.status(201).json({ data: backup });
  }
);

router.get(
  '/:id/backups',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const page = Math.max(1, parseInt(req.query.page as string, 10) || 1);
    const limit = Math.min(100, Math.max(1, parseInt(req.query.limit as string, 10) || 50));
    const result = await backupService.listBackups(req.params.id as string, page, limit);
    res.json(result);
  }
);

// ─── Restores ──────────────────────────────────────────────────────

/**
 * POST /:id/restore
 * Body: { backupId, options? }
 * Starts a restore of the given backup onto this instance. Returns the
 * InstanceRestore row immediately; caller polls GET /:id/restores or
 * GET /:id/restores/:restoreId for status.
 *
 * DESTRUCTIVE: overwrites databases and uploads. Requires SUPER_ADMIN.
 */
router.post(
  '/:id/restore',
  requireRole('SUPER_ADMIN'),
  async (req: Request, res: Response) => {
    const instanceId = req.params.id as string;
    const { backupId, options } = req.body ?? {};
    if (!backupId || typeof backupId !== 'string') {
      res.status(400).json({ error: { message: 'backupId (string) is required', code: 'VALIDATION' } });
      return;
    }

    // Defensive: ensure the backup belongs to this instance
    const backup = await prisma.backup.findUnique({ where: { id: backupId } });
    if (!backup) {
      res.status(404).json({ error: { message: 'Backup not found', code: 'NOT_FOUND' } });
      return;
    }
    if (backup.instanceId !== instanceId) {
      res.status(400).json({
        error: {
          message: 'Backup does not belong to this instance (cross-instance restore is not supported)',
          code: 'CROSS_INSTANCE_RESTORE',
        },
      });
      return;
    }

    const restore = await restoreService.createRestore({
      backupId,
      triggeredById: req.user!.id,
      ipAddress: req.ip,
      options,
    });
    res.status(201).json({ data: restore });
  }
);

router.get(
  '/:id/restores',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const page = Math.max(1, parseInt(req.query.page as string, 10) || 1);
    const limit = Math.min(100, Math.max(1, parseInt(req.query.limit as string, 10) || 50));
    const result = await restoreService.listRestores(req.params.id as string, page, limit);
    res.json(result);
  }
);

router.get(
  '/:id/restores/:restoreId',
  requireRole('SUPER_ADMIN', 'OPERATOR'),
  async (req: Request, res: Response) => {
    const restore = await restoreService.getRestore(req.params.restoreId as string);
    if (restore.instanceId !== req.params.id) {
      res.status(404).json({ error: { message: 'Restore not found', code: 'NOT_FOUND' } });
      return;
    }
    res.json({ data: restore });
  }
);

export default router;
