import { Router, type Request, type Response } from 'express';
import { z } from 'zod';
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import { readFileSync } from 'fs';
import { join } from 'path';
import { parse as parseYAML } from 'yaml';
import { authenticate } from '../../middleware/auth.middleware';
import { requireRole } from '../../middleware/rbac.middleware';
import { redis } from '../../config/redis';
import { pangolinClient, type CreateHttpResourcePayload, type CreateTargetPayload } from '../../services/pangolin.client';
import { updateEnvFile } from '../../services/env-writer.service';
import { dockerService } from '../../services/docker.service';
import { env } from '../../config/env';
import { logger } from '../../utils/logger';

// Rate limiter for expensive Pangolin operations
const pangolinSetupLimiter = rateLimit({
  windowMs: 5 * 60 * 1000, // 5 minutes
  max: 3, // Max 3 operations per 5 minutes per user
  keyGenerator: (req: Request) => `pangolin-setup:${req.user?.id || req.ip}`,
  standardHeaders: true,
  legacyHeaders: false,
  store: new RedisStore({
    sendCommand: (command: string, ...args: string[]) => redis.call(command, ...args) as Promise<any>,
    prefix: 'rl:pangolin-setup:',
  }),
  message: {
    error: {
      message: 'Too many Pangolin setup operations. Please wait 5 minutes before trying again.',
      code: 'PANGOLIN_RATE_LIMIT_EXCEEDED',
    },
  },
});

// --- Resource Definitions from YAML ---

interface ResourceDefinition {
  subdomain: string;
  name: string;
  container: string;
  port: number;
  target_ip: string;
  target_port: number;
  required: boolean;
  profile?: string;
}

interface ResourceConfig {
  resources: ResourceDefinition[];
}

/**
 * Load resource definitions from YAML config file
 */
function loadResourceDefinitions(): ResourceDefinition[] {
  try {
    const configPath = join(__dirname, '../../../configs/pangolin/resources.yml');
    const fileContent = readFileSync(configPath, 'utf8');
    const config = parseYAML(fileContent) as ResourceConfig;

    if (!config || !Array.isArray(config.resources)) {
      logger.error('Invalid resources.yml format');
      return [];
    }

    // Apply defaults for target_ip/target_port
    const resources = config.resources.map(r => ({
      ...r,
      target_ip: r.target_ip || 'nginx',
      target_port: r.target_port || 80,
    }));

    logger.info(`Loaded ${resources.length} resource definitions from YAML`);
    return resources;
  } catch (err) {
    logger.error('Failed to load resource definitions:', err);
    return [];
  }
}

/**
 * Validate container is running before creating resource
 */
async function validateContainer(
  def: ResourceDefinition
): Promise<{ valid: boolean; warning?: string; shouldSkip: boolean }> {
  try {
    if (def.profile === 'monitoring') {
      const grafanaHost = new URL(env.GRAFANA_URL).hostname;
      const grafanaStatus = await dockerService.getContainerStatus(grafanaHost);
      if (!grafanaStatus.running) {
        return {
          valid: false,
          warning: `Monitoring profile not active, skipping ${def.name}`,
          shouldSkip: true,
        };
      }
    }

    const status = await dockerService.getContainerStatus(def.container);

    if (!status.running) {
      if (def.required) {
        return {
          valid: false,
          warning: `Required container not running: ${def.container}`,
          shouldSkip: false,
        };
      } else {
        return {
          valid: false,
          warning: `Optional container not running: ${def.container}, skipping`,
          shouldSkip: true,
        };
      }
    }

    return { valid: true, shouldSkip: false };
  } catch (err) {
    logger.error(`Container validation failed for ${def.container}:`, err);
    return {
      valid: false,
      warning: `Failed to check container status: ${def.container}`,
      shouldSkip: !def.required,
    };
  }
}

/**
 * Suggest next available subnet based on existing sites
 */
function suggestNextSubnet(sites: Array<{ address?: string; subnet?: string }>): string {
  if (!sites || sites.length === 0) {
    return '100.89.128.4/30';
  }

  const subnets = sites
    .map(s => s.subnet)
    .filter((s): s is string => Boolean(s))
    .map(s => {
      const match = s.match(/^100\.89\.128\.(\d+)\/30$/);
      return { subnet: s, octet: match ? parseInt(match[1], 10) : 0 };
    })
    .sort((a, b) => a.octet - b.octet);

  if (subnets.length === 0) {
    return '100.89.128.4/30';
  }

  const lastSubnet = subnets[subnets.length - 1].subnet;
  if (!lastSubnet) {
    return '100.89.128.4/30';
  }

  const match = lastSubnet.match(/^100\.89\.128\.(\d+)\/30$/);
  if (match && match[1]) {
    const lastOctet = parseInt(match[1], 10);
    const nextOctet = lastOctet + 4;
    if (nextOctet <= 252) {
      return `100.89.128.${nextOctet}/30`;
    }
  }

  return '100.89.128.4/30';
}

const router = Router();

// --- All endpoints require SUPER_ADMIN authentication ---

router.use(authenticate);
router.use(requireRole('SUPER_ADMIN'));

// GET /api/pangolin/status — Health + connection info + site ID validation
router.get('/status', async (_req: Request, res: Response) => {
  try {
    const configured = pangolinClient.configured;
    let healthy = false;
    let siteIdValid: boolean | null = null;
    let resolvedSiteId: string | null = null;
    let siteIdMismatch = false;

    if (configured) {
      healthy = await pangolinClient.healthCheck();

      // Validate site ID by checking if it exists in the org
      // Pangolin returns siteId as a number; env stores it as a string — compare with String()
      if (healthy) {
        const envSiteId = env.PANGOLIN_SITE_ID;
        if (envSiteId) {
          try {
            const sites = await pangolinClient.listSites();

            // Match env siteId against org sites (coerce types for comparison)
            const match = sites.find(s =>
              String(s.siteId) === envSiteId || s.niceId === envSiteId
            );

            if (match) {
              siteIdValid = true;
              resolvedSiteId = String(match.siteId);
            } else {
              siteIdValid = false;
              siteIdMismatch = true;
              logger.warn(`PANGOLIN_SITE_ID "${envSiteId}" not found in org (${sites.length} sites available)`);
            }
          } catch (err) {
            logger.warn('Could not validate site ID (non-critical):', err instanceof Error ? err.message : err);
          }
        }
      }
    }

    res.json({
      configured,
      healthy,
      pangolinUrl: env.PANGOLIN_API_URL || null,
      orgId: env.PANGOLIN_ORG_ID || null,
      siteId: env.PANGOLIN_SITE_ID || null,
      newtConfigured: !!(env.PANGOLIN_NEWT_ID && env.PANGOLIN_NEWT_SECRET),
      siteIdValid,
      resolvedSiteId,
      siteIdMismatch,
    });
  } catch (err) {
    logger.error('Pangolin status check failed:', err);
    res.status(500).json({ error: { message: 'Failed to check Pangolin status', code: 'PANGOLIN_ERROR' } });
  }
});

// GET /api/pangolin/config — Current env config
router.get('/config', (_req: Request, res: Response) => {
  res.json({
    pangolinApiUrl: env.PANGOLIN_API_URL || null,
    pangolinEndpoint: env.PANGOLIN_ENDPOINT || null,
    orgId: env.PANGOLIN_ORG_ID || null,
    siteId: env.PANGOLIN_SITE_ID || null,
    newtId: env.PANGOLIN_NEWT_ID || null,
    newtConfigured: !!(env.PANGOLIN_NEWT_ID && env.PANGOLIN_NEWT_SECRET),
    domain: env.DOMAIN,
  });
});

// GET /api/pangolin/newt-status — Check newt container status
router.get('/newt-status', async (_req: Request, res: Response) => {
  try {
    const containerStatus = await dockerService.getContainerStatus(env.NEWT_CONTAINER_NAME);

    res.json({
      newtConfigured: !!(env.PANGOLIN_NEWT_ID && env.PANGOLIN_NEWT_SECRET),
      containerRunning: containerStatus.running,
      containerStatus: containerStatus.status,
      ready: containerStatus.running && !!(env.PANGOLIN_NEWT_ID && env.PANGOLIN_NEWT_SECRET),
      error: containerStatus.error,
    });
  } catch (err) {
    logger.error('Newt status check failed:', err);
    res.status(500).json({
      error: { message: 'Failed to check newt status', code: 'DOCKER_ERROR' }
    });
  }
});

// POST /api/pangolin/newt-restart — Restart/recreate newt container
router.post('/newt-restart', async (_req: Request, res: Response) => {
  try {
    if (!env.PANGOLIN_NEWT_ID || !env.PANGOLIN_NEWT_SECRET) {
      res.status(400).json({
        error: {
          message: 'Newt credentials not configured. Run setup first and update .env file.',
          code: 'NOT_CONFIGURED'
        },
      });
      return;
    }

    const result = await dockerService.restartContainer(env.NEWT_COMPOSE_SERVICE);

    if (!result.success) {
      res.status(500).json({
        error: { message: 'Failed to restart container', code: 'DOCKER_ERROR' },
        details: result.output,
      });
      return;
    }

    res.json({
      success: true,
      message: 'Newt container restarted successfully',
      output: result.output,
    });
  } catch (err) {
    logger.error('Newt restart failed:', err);
    res.status(500).json({
      error: { message: 'Failed to restart newt container', code: 'DOCKER_ERROR' }
    });
  }
});

// GET /api/pangolin/sites — List sites (with newtId matching for site picker)
router.get('/sites', async (_req: Request, res: Response) => {
  try {
    const sites = await pangolinClient.listSites();
    const currentNewtId = env.PANGOLIN_NEWT_ID || null;
    const currentSiteId = env.PANGOLIN_SITE_ID || null;

    // Annotate each site with whether it matches current env config
    // Pangolin returns siteId as a number; env stores it as a string — compare with String()
    const annotatedSites = sites.map(s => ({
      ...s,
      isCurrentSite: currentSiteId
        ? (String(s.siteId) === currentSiteId || s.niceId === currentSiteId)
        : false,
    }));

    res.json({ sites: annotatedSites, currentNewtId, currentSiteId });
  } catch (err) {
    const msg = err instanceof Error ? err.message : 'Unknown error';
    res.status(500).json({ error: { message: msg, code: 'PANGOLIN_ERROR' } });
  }
});

// POST /api/pangolin/connect-site — Connect to an existing site (write site ID + newt creds to .env)
const connectSiteSchema = z.object({
  siteId: z.union([z.string().min(1).max(200), z.number().int().positive()]).transform(String),
});

router.post('/connect-site', pangolinSetupLimiter, async (req: Request, res: Response) => {
  try {
    if (!pangolinClient.configured) {
      res.status(400).json({
        error: { message: 'Pangolin not configured', code: 'NOT_CONFIGURED' },
      });
      return;
    }

    const { siteId } = connectSiteSchema.parse(req.body);

    // Verify the site exists in the org
    const sites = await pangolinClient.listSites();
    const site = sites.find(s => String(s.siteId) === siteId || s.niceId === siteId);

    if (!site) {
      res.status(404).json({
        error: { message: `Site "${siteId}" not found in organization`, code: 'SITE_NOT_FOUND' },
      });
      return;
    }

    // Build env updates — always write the site ID (coerce to string for .env)
    const envUpdates: Record<string, string> = {
      PANGOLIN_SITE_ID: String(site.siteId),
    };

    // If the site has a Pangolin endpoint, write that too
    if (env.PANGOLIN_API_URL) {
      // Derive the endpoint from the API URL (strip /v1 path)
      const endpoint = env.PANGOLIN_API_URL.replace(/\/v1\/?$/, '');
      envUpdates.PANGOLIN_ENDPOINT = endpoint;
    }

    // Write to .env
    const envResult = updateEnvFile(envUpdates);

    logger.info(`Connected to Pangolin site: ${site.siteId} (name: ${site.name})`);

    res.json({
      success: true,
      site: {
        siteId: site.siteId,
        name: site.name,
        niceId: site.niceId,
        online: site.online,
      },
      envUpdate: envResult,
      message: `Connected to site "${site.name}". Restart the API container to apply the new PANGOLIN_SITE_ID.`,
    });
  } catch (err) {
    if (err instanceof z.ZodError) {
      res.status(400).json({ error: { message: 'Invalid request body', code: 'VALIDATION_ERROR' } });
      return;
    }
    const msg = err instanceof Error ? err.message : 'Unknown error';
    logger.error('Connect site failed:', err);
    res.status(500).json({ error: { message: msg, code: 'PANGOLIN_ERROR' } });
  }
});

// GET /api/pangolin/exit-nodes — List available exit nodes
router.get('/exit-nodes', async (_req: Request, res: Response) => {
  try {
    const exitNodes = await pangolinClient.listExitNodes();
    res.json({ exitNodes });
  } catch (err) {
    const msg = err instanceof Error ? err.message : 'Unknown error';
    logger.warn('Failed to list exit nodes:', msg);
    res.status(500).json({
      error: {
        message: 'Failed to retrieve exit nodes from Pangolin API',
        code: 'PANGOLIN_ERROR'
      }
    });
  }
});

// GET /api/pangolin/resource-definitions — List resource definitions from YAML config
router.get('/resource-definitions', (_req: Request, res: Response) => {
  try {
    const resourceDefs = loadResourceDefinitions();
    res.json({
      domain: env.DOMAIN,
      resources: resourceDefs.map(def => ({
        name: def.name,
        subdomain: def.subdomain,
        fullDomain: def.subdomain ? `${def.subdomain}.${env.DOMAIN}` : env.DOMAIN,
        port: def.port,
        container: def.container,
        target_ip: def.target_ip,
        target_port: def.target_port,
        required: def.required,
      })),
    });
  } catch (err) {
    const msg = err instanceof Error ? err.message : 'Unknown error';
    logger.error('Failed to load resource definitions:', err);
    res.status(500).json({ error: { message: msg, code: 'CONFIG_ERROR' } });
  }
});

// GET /api/pangolin/resource-status — Cross-referenced live status of all resources
router.get('/resource-status', async (_req: Request, res: Response) => {
  try {
    if (!pangolinClient.configured) {
      res.status(400).json({ error: { message: 'Pangolin not configured', code: 'NOT_CONFIGURED' } });
      return;
    }

    const domain = env.DOMAIN;
    const siteId = env.PANGOLIN_SITE_ID || '';
    const resourceDefs = loadResourceDefinitions();
    const liveResources = await pangolinClient.listResources();

    // Build lookup by fullDomain
    const liveByDomain = new Map(liveResources.map(r => [r.fullDomain || '', r]));

    // Cross-reference YAML definitions with live state
    const resources = resourceDefs.map(def => {
      const fullDomain = def.subdomain ? `${def.subdomain}.${domain}` : domain;
      const live = liveByDomain.get(fullDomain);

      // Find matching target (from inline targets array)
      const targets = live?.targets || [];
      const target = targets[0]; // Primary target

      const expectedIp = def.target_ip;
      const expectedPort = def.target_port;
      const targetCorrect = target
        ? target.ip === expectedIp && target.port === expectedPort
        : false;

      // Remove from map so we can find "extras" later
      if (live) liveByDomain.delete(fullDomain);

      return {
        name: def.name,
        subdomain: def.subdomain,
        fullDomain,
        required: def.required,
        container: def.container,
        profile: def.profile || null,
        expectedTargetIp: expectedIp,
        expectedTargetPort: expectedPort,
        exists: !!live,
        resourceId: live?.resourceId || null,
        hasTarget: !!target,
        targetCorrect,
        actualTargetIp: target?.ip || null,
        actualTargetPort: target?.port || null,
        targetId: target?.targetId || null,
        targetEnabled: target?.enabled ?? null,
        ssl: live?.ssl ?? null,
        sso: live?.blockAccess ?? null,
        blockAccess: live?.blockAccess ?? null,
        enabled: live?.active ?? null,
      };
    });

    // Remaining entries in liveByDomain are "extras" — in Pangolin but not in YAML
    const extras = Array.from(liveByDomain.values()).map(r => {
      const target = r.targets?.[0];
      return {
        name: r.name,
        subdomain: r.subdomain || null,
        fullDomain: r.fullDomain || '',
        required: false,
        container: null,
        profile: null,
        expectedTargetIp: null,
        expectedTargetPort: null,
        exists: true,
        resourceId: r.resourceId,
        hasTarget: !!target,
        targetCorrect: true, // No expected value to compare against
        actualTargetIp: target?.ip || null,
        actualTargetPort: target?.port || null,
        targetId: target?.targetId || null,
        targetEnabled: target?.enabled ?? null,
        ssl: r.ssl ?? null,
        sso: r.blockAccess ?? null,
        blockAccess: r.blockAccess ?? null,
        enabled: r.active ?? null,
      };
    });

    // Summary counts
    const healthy = resources.filter(r => r.exists && r.hasTarget && r.targetCorrect).length;
    const misconfigured = resources.filter(r => r.exists && (!r.hasTarget || !r.targetCorrect)).length;
    const missing = resources.filter(r => !r.exists).length;

    res.json({
      resources,
      extras,
      summary: {
        total: resources.length,
        healthy,
        misconfigured,
        missing,
        extras: extras.length,
      },
      siteId,
      domain,
    });
  } catch (err) {
    const msg = err instanceof Error ? err.message : 'Unknown error';
    logger.error('Resource status check failed:', err);
    res.status(500).json({ error: { message: msg, code: 'PANGOLIN_ERROR' } });
  }
});

// GET /api/pangolin/resources — List resources
router.get('/resources', async (_req: Request, res: Response) => {
  try {
    const resources = await pangolinClient.listResources();
    res.json({ resources });
  } catch (err) {
    const msg = err instanceof Error ? err.message : 'Unknown error';
    res.status(500).json({ error: { message: msg, code: 'PANGOLIN_ERROR' } });
  }
});

// POST /api/pangolin/setup — Full automated setup: site + resources + targets + .env update
router.post('/setup', pangolinSetupLimiter, async (req: Request, res: Response) => {
  try {
    if (!pangolinClient.configured) {
      res.status(400).json({
        error: { message: 'Pangolin not configured. Set PANGOLIN_API_URL, PANGOLIN_API_KEY, and PANGOLIN_ORG_ID in .env', code: 'NOT_CONFIGURED' },
      });
      return;
    }

    const domain = env.DOMAIN;
    const siteName = req.body?.siteName || `changemaker-${domain}`;
    const exitNodeId = req.body?.exitNodeId;
    const autoWriteEnv = req.body?.autoWriteEnv !== false; // Default true

    // Validate exit node ID if provided
    if (exitNodeId !== undefined && exitNodeId !== null && exitNodeId !== '') {
      if (typeof exitNodeId !== 'string' || exitNodeId.length < 3 || exitNodeId.length > 100) {
        res.status(400).json({
          error: { message: 'Invalid exit node ID format', code: 'INVALID_EXIT_NODE' },
        });
        return;
      }

      try {
        const availableNodes = await pangolinClient.listExitNodes();
        if (availableNodes.length > 0) {
          const node = availableNodes.find(n => n.exitNodeId === exitNodeId);
          if (!node) {
            res.status(400).json({
              error: { message: 'Exit node not found in available nodes', code: 'EXIT_NODE_NOT_FOUND' },
            });
            return;
          }
          if (!node.online) {
            logger.warn(`Creating site with offline exit node: ${exitNodeId}`);
          }
        }
      } catch {
        logger.warn('Could not verify exit node (endpoint unavailable)');
      }
    }

    // === STEP 1: Get Newt credentials from Pangolin ===
    logger.info('Setup Step 1: Fetching Newt credentials via pickSiteDefaults...');
    let newtId: string;
    let newtSecret: string;
    let address: string;
    let usedPickDefaults = false;

    try {
      const defaults = await pangolinClient.pickSiteDefaults();
      newtId = defaults.newtId;
      newtSecret = defaults.newtSecret;
      address = defaults.address;
      usedPickDefaults = true;
      logger.info(`pickSiteDefaults succeeded: newtId=${newtId}`);
    } catch (err) {
      // Fallback: older Pangolin versions may not have this endpoint
      logger.warn('pickSiteDefaults not available, will parse Newt creds from site creation response');
      newtId = '';
      newtSecret = '';
      address = '';
    }

    // === STEP 2: Create site ===
    logger.info(`Setup Step 2: Creating site "${siteName}"...`);
    const sitePayload: Record<string, unknown> = {
      name: siteName,
      type: 'newt',
      ...(exitNodeId && { exitNodeId }),
    };

    // Pass Newt credentials from pickSiteDefaults if available
    if (usedPickDefaults) {
      sitePayload.newtId = newtId;
      sitePayload.secret = newtSecret;
      if (address) sitePayload.address = address;
    }

    const site = await pangolinClient.createSite(sitePayload as any);
    const siteId = site.siteId;
    logger.info(`Site created: ${siteId}`);

    // If we didn't use pickSiteDefaults, try to get creds from site response
    if (!usedPickDefaults && site.newt) {
      newtId = site.newt.newtId;
      newtSecret = site.newt.secret;
      logger.info(`Got Newt credentials from site creation response: newtId=${newtId}`);
    }

    // === STEP 3: Get domain ID (once, for all resources) ===
    logger.info('Setup Step 3: Looking up domain...');
    const domains = await pangolinClient.listDomains();
    const matchingDomain = domains.find(d => domain.endsWith(d.baseDomain));

    if (!matchingDomain) {
      res.status(400).json({
        error: { message: `Domain "${domain}" not registered in Pangolin. Register it first.`, code: 'DOMAIN_NOT_FOUND' },
      });
      return;
    }
    logger.info(`Using domain: ${matchingDomain.baseDomain} (ID: ${matchingDomain.domainId})`);

    // === STEP 4: Create resources + targets ===
    logger.info('Setup Step 4: Creating resources and targets...');
    const resourceDefs = loadResourceDefinitions();
    if (resourceDefs.length === 0) {
      logger.error('No resource definitions found - check configs/pangolin/resources.yml');
    }

    const created: Array<{ subdomain: string; name: string; resourceId: string; targetCreated: boolean }> = [];
    const errors: string[] = [];
    const warnings: string[] = [];

    for (const def of resourceDefs) {
      const fullDomain = def.subdomain ? `${def.subdomain}.${domain}` : domain;

      // Validate container
      const validation = await validateContainer(def);
      if (!validation.valid) {
        if (validation.shouldSkip) {
          warnings.push(`${fullDomain}: ${validation.warning}`);
          logger.warn(validation.warning);
          continue;
        } else {
          errors.push(`${fullDomain}: ${validation.warning}`);
          logger.error(validation.warning);
          continue;
        }
      }

      try {
        // Step 4a: Create HTTP resource
        // Root domain: omit subdomain field entirely (Pangolin rejects empty string but accepts absent field)
        const resourcePayload: CreateHttpResourcePayload = {
          name: def.name,
          domainId: matchingDomain.domainId,
          ...(def.subdomain ? { subdomain: def.subdomain } : {}),
          http: true,
          protocol: 'tcp',
        };

        const resource = await pangolinClient.createResource(resourcePayload);
        const resourceId = resource.resourceId;
        logger.info(`Created resource: ${fullDomain} (ID: ${resourceId})`);

        // Make resource publicly accessible (no SSO auth, no access block)
        try {
          await pangolinClient.updateResource(resourceId, { sso: false, blockAccess: false });
          logger.info(`Set ${fullDomain} as public (no auth)`);
        } catch (updateErr) {
          logger.warn(`Created ${fullDomain} but failed to set public access:`, updateErr);
        }

        // Step 4b: Create target (THE MISSING STEP!)
        let targetCreated = false;
        try {
          const targetPayload: CreateTargetPayload = {
            siteId,
            ip: def.target_ip,
            port: def.target_port,
            method: 'http',
            enabled: true,
          };

          await pangolinClient.createTarget(resourceId, targetPayload);
          targetCreated = true;
          logger.info(`Created target for ${fullDomain}: ${def.target_ip}:${def.target_port}`);
        } catch (targetErr) {
          const msg = targetErr instanceof Error ? targetErr.message : 'Unknown error';
          errors.push(`${fullDomain} (target): ${msg}`);
          logger.error(`Failed to create target for ${fullDomain}: ${msg}`);
        }

        created.push({ subdomain: def.subdomain || '(root)', name: def.name, resourceId, targetCreated });
      } catch (err) {
        const msg = err instanceof Error ? err.message : 'Unknown error';
        errors.push(`${fullDomain}: ${msg}`);
        logger.warn(`Failed to create resource for ${fullDomain}: ${msg}`);
      }
    }

    // === STEP 5: Auto-write .env file ===
    let envWriteResult = null;
    if (autoWriteEnv && newtId && newtSecret) {
      logger.info('Setup Step 5: Writing credentials to .env...');
      const pangolinEndpoint = env.PANGOLIN_ENDPOINT || env.PANGOLIN_API_URL.replace('/v1', '');

      envWriteResult = updateEnvFile({
        PANGOLIN_SITE_ID: siteId,
        PANGOLIN_NEWT_ID: newtId,
        PANGOLIN_NEWT_SECRET: newtSecret,
        PANGOLIN_ENDPOINT: pangolinEndpoint,
      });

      if (envWriteResult.success) {
        logger.info('Credentials written to .env successfully');
      } else {
        logger.error(`Failed to write .env: ${envWriteResult.error}`);
        warnings.push(`.env auto-write failed: ${envWriteResult.error}`);
      }
    }

    // === STEP 6: Restart Newt container ===
    let newtRestarted = false;
    if (autoWriteEnv && envWriteResult?.success) {
      logger.info('Setup Step 6: Restarting Newt container...');
      try {
        const result = await dockerService.restartContainer(env.NEWT_COMPOSE_SERVICE);
        newtRestarted = result.success;
        if (!result.success) {
          warnings.push(`Newt restart failed: ${result.output}`);
        }
      } catch (err) {
        const msg = err instanceof Error ? err.message : 'Unknown error';
        warnings.push(`Newt restart error: ${msg}`);
      }
    }

    res.json({
      success: true,
      site: {
        siteId,
        name: siteName,
      },
      newt: {
        newtId: newtId || null,
        newtSecret: newtSecret ? '***' : null, // Don't expose in response when auto-written
        credentialsAutoWritten: envWriteResult?.success || false,
        containerRestarted: newtRestarted,
      },
      resources: {
        created: created.length,
        total: resourceDefs.length,
        items: created,
        errors,
        warnings,
      },
      envUpdate: envWriteResult ? {
        success: envWriteResult.success,
        updated: envWriteResult.updated,
        added: envWriteResult.added,
        error: envWriteResult.error,
      } : null,
      // Fallback instructions if auto-write failed
      ...(!envWriteResult?.success && newtId && {
        manualInstructions: [
          'Auto-write to .env failed. Add these manually:',
          `PANGOLIN_SITE_ID=${siteId}`,
          `PANGOLIN_NEWT_ID=${newtId}`,
          `PANGOLIN_NEWT_SECRET=${newtSecret}`,
          `PANGOLIN_ENDPOINT=${env.PANGOLIN_ENDPOINT || env.PANGOLIN_API_URL.replace('/v1', '')}`,
          'Then restart the newt container: docker compose up -d newt',
        ],
      }),
    });
  } catch (err) {
    const msg = err instanceof Error ? err.message : 'Unknown error';
    logger.error('Pangolin setup failed:', err);
    res.status(500).json({ error: { message: msg, code: 'PANGOLIN_ERROR' } });
  }
});

// POST /api/pangolin/sync — Sync resources (create missing, verify targets)
router.post('/sync', pangolinSetupLimiter, async (_req: Request, res: Response) => {
  try {
    if (!pangolinClient.configured) {
      res.status(400).json({ error: { message: 'Pangolin not configured', code: 'NOT_CONFIGURED' } });
      return;
    }

    const siteSlug = env.PANGOLIN_SITE_ID;
    if (!siteSlug) {
      res.status(400).json({ error: { message: 'PANGOLIN_SITE_ID not set. Run setup first.', code: 'NO_SITE' } });
      return;
    }

    // Resolve numeric siteId from slug (Pangolin targets require numeric siteId)
    let siteId: string | number = siteSlug;
    if (isNaN(Number(siteSlug))) {
      const sites = await pangolinClient.listSites();
      const match = sites.find(s => s.niceId === siteSlug || s.name === siteSlug);
      if (match) {
        siteId = match.siteId;
        logger.info(`Resolved site slug "${siteSlug}" to numeric siteId ${siteId}`);
      } else {
        logger.warn(`Could not resolve site slug "${siteSlug}" to numeric ID, using as-is`);
      }
    }

    const domain = env.DOMAIN;
    const resourceDefs = loadResourceDefinitions();
    if (resourceDefs.length === 0) {
      res.status(500).json({
        error: { message: 'No resource definitions found. Check configs/pangolin/resources.yml', code: 'NO_CONFIG' }
      });
      return;
    }

    // Get existing resources and domain info
    const [existing, domains] = await Promise.all([
      pangolinClient.listResources(),
      pangolinClient.listDomains(),
    ]);

    const matchingDomain = domains.find(d => domain.endsWith(d.baseDomain));
    if (!matchingDomain) {
      res.status(400).json({
        error: { message: `Domain "${domain}" not registered in Pangolin`, code: 'DOMAIN_NOT_FOUND' }
      });
      return;
    }

    // Build lookup by full domain
    const existingByDomain = new Map(existing.map(r => [r.fullDomain || '', r]));

    const created: string[] = [];
    const reassigned: string[] = [];
    const targetFixed: string[] = [];
    const skipped: string[] = [];
    const warnings: string[] = [];
    const errors: string[] = [];

    // Create resource + public access + target. Shared by "new" and "reassign"
    // flows so `--pangolin-site new` installs can rebuild after a site rotation.
    const createResourceForDef = async (def: ResourceDefinition, fullDomain: string) => {
      const resource = await pangolinClient.createResource({
        name: def.name,
        domainId: matchingDomain.domainId,
        ...(def.subdomain ? { subdomain: def.subdomain } : {}),
        http: true,
        protocol: 'tcp',
      });

      try {
        await pangolinClient.updateResource(resource.resourceId, { sso: false, blockAccess: false });
      } catch {
        logger.warn(`Created ${fullDomain} but failed to set public access`);
      }

      try {
        await pangolinClient.createTarget(resource.resourceId, {
          siteId,
          ip: def.target_ip,
          port: def.target_port,
          method: 'http',
          enabled: true,
        });
      } catch (targetErr) {
        const msg = targetErr instanceof Error ? targetErr.message : 'Unknown error';
        errors.push(`${fullDomain} (target): ${msg}`);
      }
    };

    for (const def of resourceDefs) {
      const fullDomain = def.subdomain ? `${def.subdomain}.${domain}` : domain;

      // Validate container
      const validation = await validateContainer(def);
      if (!validation.valid) {
        if (validation.shouldSkip) {
          warnings.push(`${fullDomain}: ${validation.warning}`);
          continue;
        } else {
          errors.push(`${fullDomain}: ${validation.warning}`);
          continue;
        }
      }

      const existingResource = existingByDomain.get(fullDomain);

      if (existingResource) {
        // Resource exists — verify target points at the CURRENT site.
        try {
          const targets = await pangolinClient.listTargets(existingResource.resourceId);
          const currentTargetSiteId = targets[0]?.siteId;
          const siteMismatch =
            targets.length > 0 && Number(currentTargetSiteId) !== Number(siteId);

          if (siteMismatch) {
            // Stale siteId from a previous `--pangolin-site new` install.
            // Delete and recreate against the current site.
            logger.warn(
              `Resource ${fullDomain} bound to stale siteId ${currentTargetSiteId}, reassigning to ${siteId}`,
            );
            try {
              await pangolinClient.deleteResource(existingResource.resourceId);
              await createResourceForDef(def, fullDomain);
              reassigned.push(fullDomain);
              logger.info(`Reassigned ${fullDomain} to siteId ${siteId}`);
            } catch (err) {
              const msg = err instanceof Error ? err.message : 'Unknown error';
              errors.push(`${fullDomain} (reassign): ${msg}`);
              logger.error(`Failed to reassign resource ${fullDomain}:`, err);
            }
          } else if (targets.length === 0) {
            // Missing target — create one
            logger.info(`Resource ${fullDomain} has no target, creating one...`);
            await pangolinClient.createTarget(existingResource.resourceId, {
              siteId,
              ip: def.target_ip,
              port: def.target_port,
              method: 'http',
              enabled: true,
            });
            targetFixed.push(fullDomain);
            logger.info(`Created missing target for ${fullDomain}`);
          } else {
            skipped.push(fullDomain);
          }
        } catch (err) {
          const msg = err instanceof Error ? err.message : 'Unknown error';
          // If listing targets fails, try to create one anyway
          try {
            await pangolinClient.createTarget(existingResource.resourceId, {
              siteId,
              ip: def.target_ip,
              port: def.target_port,
              method: 'http',
              enabled: true,
            });
            targetFixed.push(fullDomain);
          } catch (targetErr) {
            errors.push(`${fullDomain} (target check): ${msg}`);
          }
        }
      } else {
        // Create new resource + target
        try {
          await createResourceForDef(def, fullDomain);
          created.push(fullDomain);
          logger.info(`Created resource + target: ${fullDomain}`);
        } catch (err) {
          const msg = err instanceof Error ? err.message : 'Unknown error';
          errors.push(`${fullDomain}: ${msg}`);
          logger.error(`Failed to create resource ${fullDomain}:`, err);
        }
      }
    }

    res.json({
      success: true,
      created: created.length,
      reassigned: reassigned.length,
      targetFixed: targetFixed.length,
      skipped: skipped.length,
      warnings: warnings.length,
      errors: errors.length,
      details: { created, reassigned, targetFixed, skipped, warnings, errors },
    });
  } catch (err) {
    const msg = err instanceof Error ? err.message : 'Unknown error';
    logger.error('Pangolin sync failed:', err);
    res.status(500).json({ error: { message: msg, code: 'PANGOLIN_ERROR' } });
  }
});

// DELETE /api/pangolin/resource/:id — Delete a resource
router.delete('/resource/:id', async (req: Request, res: Response) => {
  try {
    const resourceId = req.params.id as string;
    await pangolinClient.deleteResource(resourceId);
    res.json({ success: true });
  } catch (err) {
    const msg = err instanceof Error ? err.message : 'Unknown error';
    res.status(500).json({ error: { message: msg, code: 'PANGOLIN_ERROR' } });
  }
});

// POST /api/pangolin/test-2step — Test 2-step resource + target creation (diagnostic endpoint)
router.post('/test-2step', pangolinSetupLimiter, async (req: Request, res: Response) => {
  try {
    if (!pangolinClient.configured) {
      res.status(400).json({
        error: { message: 'Pangolin not configured', code: 'NOT_CONFIGURED' },
      });
      return;
    }

    const domain = env.DOMAIN;
    const testSubdomain = 'test-2step-' + Math.random().toString(36).substring(7);

    logger.info(`Starting 2-step test for ${testSubdomain}.${domain}`);

    // Get domain info
    const domains = await pangolinClient.listDomains();
    const matchingDomain = domains.find(d => domain.endsWith(d.baseDomain));

    if (!matchingDomain) {
      res.status(400).json({
        error: { message: `Domain not found: ${domain}`, code: 'DOMAIN_NOT_FOUND' },
      });
      return;
    }

    // Get siteId
    let siteId = env.PANGOLIN_SITE_ID;
    if (!siteId) {
      const sites = await pangolinClient.listSites();
      if (sites.length === 0) {
        res.status(400).json({
          error: { message: 'No sites available. Run setup first.', code: 'NO_SITE' },
        });
        return;
      }
      siteId = sites[0].siteId;
    }

    const results: Record<string, unknown> = {
      testName: `2-Step Resource + Target (${testSubdomain})`,
      domainId: matchingDomain.domainId,
      siteId,
      steps: [],
    };

    // --- Step 1: Create resource ---
    let resourceId: string | undefined;
    try {
      const createdResource = await pangolinClient.createResource({
        name: `Test 2-Step - ${testSubdomain}`,
        domainId: matchingDomain.domainId,
        subdomain: testSubdomain,
        http: true,
        protocol: 'tcp',
      });
      resourceId = createdResource.resourceId;
      (results.steps as any[]).push({
        step: 1, name: 'Create HTTP resource', status: 'success', resourceId,
      });
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Unknown error';
      (results.steps as any[]).push({
        step: 1, name: 'Create HTTP resource', status: 'failed', error: msg,
      });
      res.status(500).json({ success: false, results });
      return;
    }

    // --- Step 2: Create target (PUT, correct payload) ---
    try {
      const targetPayload: CreateTargetPayload = {
        siteId,
        ip: 'nginx',
        port: 80,
        method: 'http',
        enabled: true,
      };
      const createdTarget = await pangolinClient.createTarget(resourceId!, targetPayload);
      (results.steps as any[]).push({
        step: 2, name: 'Create target', status: 'success',
        payload: targetPayload, response: createdTarget,
      });
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Unknown error';
      (results.steps as any[]).push({
        step: 2, name: 'Create target', status: 'failed',
        error: msg,
      });
    }

    // --- Step 3: Verify resource ---
    try {
      const verified = await pangolinClient.getResource(resourceId!);
      (results.steps as any[]).push({
        step: 3, name: 'Verify resource', status: 'success', resource: verified,
      });
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Unknown error';
      (results.steps as any[]).push({
        step: 3, name: 'Verify resource', status: 'failed', error: msg,
      });
    }

    // --- Step 4: Cleanup ---
    try {
      await pangolinClient.deleteResource(resourceId!);
      (results.steps as any[]).push({
        step: 4, name: 'Cleanup', status: 'success',
      });
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Unknown error';
      (results.steps as any[]).push({
        step: 4, name: 'Cleanup', status: 'failed', error: msg, resourceId,
      });
    }

    res.json({ success: true, message: '2-step test completed', results });
  } catch (err) {
    const msg = err instanceof Error ? err.message : 'Unknown error';
    logger.error('2-step test failed:', err);
    res.status(500).json({ error: { message: msg, code: 'PANGOLIN_ERROR' } });
  }
});

// PUT /api/pangolin/resource/:id — Update a resource
const updateResourceSchema = z.object({
  name: z.string().max(200).optional(),
  subdomain: z.string().max(200).optional(),
  ssl: z.boolean().optional(),
  blockAccess: z.boolean().optional(),
  proxyPort: z.number().int().optional(),
  protocol: z.string().max(20).optional(),
  domainId: z.string().max(200).optional(),
  isBaseDomain: z.boolean().optional(),
  http: z.boolean().optional(),
  https: z.boolean().optional(),
}).passthrough();

router.put('/resource/:id', async (req: Request, res: Response) => {
  try {
    const resourceId = req.params.id as string;
    const body = updateResourceSchema.parse(req.body);
    const resource = await pangolinClient.updateResource(resourceId, body);
    res.json({ success: true, resource });
  } catch (err) {
    const msg = err instanceof Error ? err.message : 'Unknown error';
    logger.error('Pangolin resource update failed:', err);
    res.status(500).json({ error: { message: msg, code: 'PANGOLIN_ERROR' } });
  }
});

// GET /api/pangolin/certificate/:domainId/:domain — Get certificate info
router.get('/certificate/:domainId/:domain', async (req: Request, res: Response) => {
  try {
    const { domainId, domain } = req.params;
    const certificate = await pangolinClient.getCertificate(domainId as string, domain as string);
    res.json({ certificate });
  } catch (err) {
    const msg = err instanceof Error ? err.message : 'Unknown error';
    res.status(500).json({ error: { message: msg, code: 'PANGOLIN_ERROR' } });
  }
});

// POST /api/pangolin/certificate/:certId — Update certificate
const updateCertificateSchema = z.object({
  autoRenew: z.boolean().optional(),
  isWildcard: z.boolean().optional(),
}).passthrough();

router.post('/certificate/:certId', async (req: Request, res: Response) => {
  try {
    const certId = req.params.certId as string;
    const body = updateCertificateSchema.parse(req.body);
    const certificate = await pangolinClient.updateCertificate(certId, body);
    res.json({ success: true, certificate });
  } catch (err) {
    const msg = err instanceof Error ? err.message : 'Unknown error';
    res.status(500).json({ error: { message: msg, code: 'PANGOLIN_ERROR' } });
  }
});

// GET /api/pangolin/resource/:id/clients — List connected clients for a resource
router.get('/resource/:id/clients', async (req: Request, res: Response) => {
  try {
    const resourceId = req.params.id as string;
    const clients = await pangolinClient.listClients(resourceId);
    res.json({ clients });
  } catch (err) {
    const msg = err instanceof Error ? err.message : 'Unknown error';
    res.status(500).json({ error: { message: msg, code: 'PANGOLIN_ERROR' } });
  }
});

export const pangolinRouter = router;
