/**
 * Remote tunnel management service.
 *
 * Orchestrates Pangolin site/resource/target creation on behalf of remote CML
 * instances, then pushes Newt credentials to the remote host via the mTLS agent.
 * The CCP holds the Pangolin API token centrally — remote instances never touch
 * the Pangolin API themselves.
 */
import { AuditAction, Prisma } from '@prisma/client';
import { prisma } from '../lib/prisma';
import { env } from '../config/env';
import { AppError } from '../middleware/error-handler';
import { logger } from '../utils/logger';
import { getRemoteDriverForInstance } from './execution-driver';
import {
  CcpPangolinClient,
  type PangolinDomain,
  type PangolinResource,
} from './ccp-pangolin.client';

// ─── Resource definitions ──────────────────────────────────────────

interface ResourceDef {
  subdomain: string;
  name: string;
  required?: boolean;
  featureFlag?: string;
}

const RESOURCE_DEFINITIONS: ResourceDef[] = [
  { subdomain: 'app',      name: 'Admin GUI',    required: true },
  { subdomain: 'api',      name: 'API',          required: true },
  { subdomain: '',         name: 'Public Site',   required: true },
  { subdomain: 'media',    name: 'Media API',    featureFlag: 'enableMedia' },
  { subdomain: 'db',       name: 'NocoDB',       required: false },
  { subdomain: 'docs',     name: 'Docs',         required: false },
  { subdomain: 'code',     name: 'Code Server',  required: false },
  { subdomain: 'git',      name: 'Gitea',        required: false },
  { subdomain: 'home',     name: 'Homepage',     required: false },
  { subdomain: 'listmonk', name: 'Listmonk',     featureFlag: 'enableListmonk' },
  { subdomain: 'qr',       name: 'Mini QR',      required: false },
  { subdomain: 'draw',     name: 'Excalidraw',   required: false },
  { subdomain: 'vault',    name: 'Vaultwarden',  required: false },
  { subdomain: 'mail',     name: 'MailHog',      required: false },
  { subdomain: 'chat',     name: 'Rocket.Chat',  featureFlag: 'enableChat' },
  { subdomain: 'events',   name: 'Gancio',       featureFlag: 'enableGancio' },
  { subdomain: 'meet',     name: 'Jitsi Meet',   featureFlag: 'enableMeet' },
  { subdomain: 'grafana',  name: 'Grafana',      featureFlag: 'enableMonitoring' },
];

// ─── Helpers ───────────────────────────────────────────────────────

function getPangolinClient(): CcpPangolinClient {
  if (!env.PANGOLIN_API_URL || !env.PANGOLIN_API_KEY || !env.PANGOLIN_ORG_ID) {
    throw new AppError(
      501,
      'Pangolin API not configured on this CCP. Set PANGOLIN_API_URL, PANGOLIN_API_KEY, PANGOLIN_ORG_ID in the CCP .env file.',
      'PANGOLIN_NOT_CONFIGURED'
    );
  }
  return new CcpPangolinClient(env.PANGOLIN_API_URL, env.PANGOLIN_API_KEY, env.PANGOLIN_ORG_ID);
}

function fullSubdomain(prefix: string, sub: string): string {
  if (!prefix) return sub;   // no prefix → use subdomain as-is (e.g., "app")
  if (!sub) return prefix;   // root domain → prefix alone (e.g., "ck")
  return `${prefix}-${sub}`; // e.g., "ck-app", "ck-api"
}

function shouldCreateResource(
  def: ResourceDef,
  instance: Record<string, unknown>
): boolean {
  if (def.required) return true;
  if (def.featureFlag) return !!(instance as Record<string, unknown>)[def.featureFlag];
  return true; // optional with no feature flag → always create
}

async function findDomainForInstance(
  client: CcpPangolinClient,
  instanceDomain: string
): Promise<PangolinDomain> {
  const domains = await client.listDomains();
  // Match the instance's domain against registered Pangolin base domains
  // e.g., instance.domain = "cursedknowledge.org" → look for base domain "cursedknowledge.org"
  // or broader: instance.domain = "app.example.com" → look for "example.com"
  const exact = domains.find((d) => d.baseDomain === instanceDomain);
  if (exact) return exact;

  // Try matching parent domain (e.g., sub.example.com → example.com)
  const parts = instanceDomain.split('.');
  for (let i = 1; i < parts.length - 1; i++) {
    const parent = parts.slice(i).join('.');
    const match = domains.find((d) => d.baseDomain === parent);
    if (match) return match;
  }

  throw new AppError(
    400,
    `No Pangolin domain matches instance domain "${instanceDomain}". Available: ${domains.map((d) => d.baseDomain).join(', ')}`,
    'DOMAIN_NOT_FOUND'
  );
}

// ─── Setup ─────────────────────────────────────────────────────────

export interface SetupTunnelOptions {
  subdomainPrefix?: string;
}

export interface TunnelSetupResult {
  siteId: string;
  newtId: string;
  endpoint: string;
  resourceCount: number;
  resources: Array<{ subdomain: string; name: string; resourceId: string }>;
}

export async function setupTunnel(
  instanceId: string,
  options: SetupTunnelOptions,
  userId?: string,
  ipAddress?: string | null
): Promise<TunnelSetupResult> {
  const client = getPangolinClient();

  const instance = await prisma.instance.findUnique({ where: { id: instanceId } });
  if (!instance) throw new AppError(404, 'Instance not found', 'NOT_FOUND');
  if (!instance.isRemote) throw new AppError(400, 'Tunnel setup via Pangolin API is only for remote instances', 'NOT_REMOTE');
  if (instance.pangolinSiteId) {
    throw new AppError(400, 'Tunnel is already configured. Use sync to update resources, or teardown first.', 'ALREADY_CONFIGURED');
  }

  // Empty prefix means resources use standard subdomains (app., api., etc.)
  // matching the instance's nginx config. A prefix like "ck" creates
  // ck-app., ck-api., etc. for multi-tenant Pangolin domains.
  const prefix = options.subdomainPrefix ?? '';

  const driver = await getRemoteDriverForInstance({
    id: instance.id,
    slug: instance.slug,
    isRemote: instance.isRemote,
    agentUrl: instance.agentUrl,
  });

  // 1. Get Newt credentials
  logger.info(`[tunnel] ${instance.slug}: picking site defaults`);
  const defaults = await client.pickSiteDefaults();

  // 2. Create site
  logger.info(`[tunnel] ${instance.slug}: creating Pangolin site`);
  const site = await client.createSite({
    name: instance.slug,
    type: 'newt',
    newtId: defaults.newtId,
    secret: defaults.newtSecret,
    address: defaults.address,
  });
  const siteId = String(site.siteId);
  const newtId = site.newt?.newtId || defaults.newtId;
  const newtSecret = site.newt?.secret || defaults.newtSecret;

  // The Pangolin endpoint (what Newt connects to) may be different from
  // the API URL. E.g., API = api.bnkserve.org/v1, endpoint = pangolin.bnkserve.org.
  // If PANGOLIN_ENDPOINT is set, use it. Otherwise derive from API URL.
  let endpoint = env.PANGOLIN_ENDPOINT || '';
  if (!endpoint) {
    const endpointUrl = new URL(env.PANGOLIN_API_URL);
    endpoint = `${endpointUrl.protocol}//${endpointUrl.hostname}${endpointUrl.port ? ':' + endpointUrl.port : ''}`;
  }

  // 3. Find matching domain
  const domain = await findDomainForInstance(client, instance.domain);
  logger.info(`[tunnel] ${instance.slug}: matched domain ${domain.baseDomain} (id: ${domain.domainId})`);

  // 4. Create resources + targets
  const createdResources: Array<{ subdomain: string; name: string; resourceId: string }> = [];
  const existingResources = await client.listResources();

  for (const def of RESOURCE_DEFINITIONS) {
    if (!shouldCreateResource(def, instance as unknown as Record<string, unknown>)) {
      logger.debug(`[tunnel] ${instance.slug}: skipping ${def.name} (feature not enabled)`);
      continue;
    }

    const sub = fullSubdomain(prefix, def.subdomain);
    // Build the expected full domain so we can do an idempotent check against
    // Pangolin's existing resources. Pangolin returns `fullDomain` not `subdomain`.
    const expectedFullDomain = sub
      ? `${sub}.${domain.baseDomain}`
      : domain.baseDomain;

    // Idempotent: skip if a resource with this fullDomain already exists
    const existing = existingResources.find(
      (r) => r.fullDomain === expectedFullDomain
    );
    if (existing) {
      logger.debug(`[tunnel] ${instance.slug}: resource ${def.name} (${expectedFullDomain}) already exists`);
      createdResources.push({ subdomain: sub, name: def.name, resourceId: String(existing.resourceId) });
      continue;
    }

    try {
      const resourcePayload: Record<string, unknown> = {
        name: def.name,
        domainId: domain.domainId,
        http: true,
        protocol: 'tcp',
      };
      // Root domain: omit subdomain entirely (empty string is rejected by Pangolin)
      if (sub) resourcePayload.subdomain = sub;

      const resource = await client.createResource(resourcePayload as unknown as Parameters<typeof client.createResource>[0]);

      // Make the resource public (no SSO, no access block)
      try {
        await client.updateResource(resource.resourceId, { sso: false, blockAccess: false });
      } catch (err) {
        logger.warn(`[tunnel] ${instance.slug}: failed to make ${def.name} public: ${(err as Error).message}`);
      }

      // Create target pointing to nginx:80 on the remote host
      await client.createTarget(resource.resourceId, {
        siteId: Number(siteId),
        ip: 'nginx',
        port: 80,
        method: 'http',
        enabled: true,
      });

      createdResources.push({ subdomain: sub, name: def.name, resourceId: resource.resourceId });
      logger.info(`[tunnel] ${instance.slug}: created resource ${def.name} → ${sub}.${domain.baseDomain}`);
    } catch (err) {
      if (def.required) throw err;
      logger.warn(`[tunnel] ${instance.slug}: failed to create optional resource ${def.name}: ${(err as Error).message}`);
    }
  }

  // 5. Push Newt credentials to remote .env
  logger.info(`[tunnel] ${instance.slug}: pushing Newt credentials to remote .env`);
  const envLines = [
    `PANGOLIN_ENDPOINT=${endpoint}`,
    `PANGOLIN_SITE_ID=${siteId}`,
    `PANGOLIN_NEWT_ID=${newtId}`,
    `PANGOLIN_NEWT_SECRET=${newtSecret}`,
  ].join('\n') + '\n';

  // Read current .env, append/replace Pangolin vars
  const currentEnv = await driver.readEnvFile('');
  const envContent = buildUpdatedEnv(currentEnv, {
    PANGOLIN_ENDPOINT: endpoint,
    PANGOLIN_SITE_ID: siteId,
    PANGOLIN_NEWT_ID: newtId,
    PANGOLIN_NEWT_SECRET: newtSecret,
  });

  await driver.writeFiles('', [{ relativePath: '.env', content: envContent }]);

  // 6. Persist on Instance row
  await prisma.instance.update({
    where: { id: instanceId },
    data: {
      pangolinEndpoint: endpoint,
      pangolinSiteId: siteId,
      pangolinNewtId: newtId,
      pangolinNewtSecret: newtSecret,
      pangolinSubdomainPrefix: prefix,
    },
  });

  // 7. Recreate Newt container to pick up the new .env vars.
  // `docker compose restart` does NOT re-read .env — it only sends SIGTERM+restart.
  // `docker compose up -d newt` detects env var changes (via ${PANGOLIN_NEWT_ID}
  // expansion in docker-compose.yml) and recreates the container automatically.
  logger.info(`[tunnel] ${instance.slug}: recreating newt container with new credentials`);
  try {
    await driver.composeUp('', '', ['newt']);
  } catch (err) {
    logger.warn(`[tunnel] ${instance.slug}: composeUp(newt) failed: ${(err as Error).message}`);
  }

  // 8. Audit log
  if (userId) {
    await prisma.auditLog.create({
      data: {
        userId,
        instanceId,
        action: AuditAction.PANGOLIN_SETUP,
        details: {
          source: 'remote',
          siteId,
          newtId,
          endpoint,
          resourceCount: createdResources.length,
          subdomainPrefix: prefix,
        } as unknown as Prisma.InputJsonValue,
        ipAddress: ipAddress ?? null,
      },
    });
  }

  logger.info(`[tunnel] ${instance.slug}: tunnel setup complete — ${createdResources.length} resources created`);

  return {
    siteId,
    newtId,
    endpoint,
    resourceCount: createdResources.length,
    resources: createdResources,
  };
}

// ─── Sync ──────────────────────────────────────────────────────────

export async function syncResources(
  instanceId: string,
  userId?: string,
  ipAddress?: string | null
) {
  const client = getPangolinClient();
  const instance = await prisma.instance.findUnique({ where: { id: instanceId } });
  if (!instance) throw new AppError(404, 'Instance not found', 'NOT_FOUND');
  if (!instance.pangolinSiteId) throw new AppError(400, 'No tunnel configured', 'NO_TUNNEL');

  const prefix = instance.pangolinSubdomainPrefix ?? '';
  const domain = await findDomainForInstance(client, instance.domain);
  const existingResources = await client.listResources();
  const siteId = instance.pangolinSiteId;

  let created = 0;
  for (const def of RESOURCE_DEFINITIONS) {
    if (!shouldCreateResource(def, instance as unknown as Record<string, unknown>)) continue;

    const sub = fullSubdomain(prefix, def.subdomain);
    const expectedFullDomain = sub ? `${sub}.${domain.baseDomain}` : domain.baseDomain;
    const existing = existingResources.find((r) => r.fullDomain === expectedFullDomain);
    if (existing) continue;

    try {
      const resourcePayload: Record<string, unknown> = {
        name: def.name,
        domainId: domain.domainId,
        http: true,
        protocol: 'tcp',
      };
      if (sub) resourcePayload.subdomain = sub;

      const resource = await client.createResource(resourcePayload as unknown as Parameters<typeof client.createResource>[0]);
      await client.updateResource(resource.resourceId, { sso: false, blockAccess: false });
      await client.createTarget(resource.resourceId, {
        siteId: Number(siteId),
        ip: 'nginx',
        port: 80,
        method: 'http',
        enabled: true,
      });
      created++;
      logger.info(`[tunnel] ${instance.slug}: sync created ${def.name} (${sub})`);
    } catch (err) {
      if (def.required) throw err;
      logger.warn(`[tunnel] ${instance.slug}: sync failed for ${def.name}: ${(err as Error).message}`);
    }
  }

  if (userId) {
    await prisma.auditLog.create({
      data: {
        userId,
        instanceId,
        action: AuditAction.PANGOLIN_SYNC,
        details: { source: 'remote', created, siteId } as unknown as Prisma.InputJsonValue,
        ipAddress: ipAddress ?? null,
      },
    });
  }

  return { synced: true, created };
}

// ─── Teardown ──────────────────────────────────────────────────────

export async function teardownTunnel(
  instanceId: string,
  userId?: string,
  ipAddress?: string | null
) {
  const client = getPangolinClient();
  const instance = await prisma.instance.findUnique({ where: { id: instanceId } });
  if (!instance) throw new AppError(404, 'Instance not found', 'NOT_FOUND');
  if (!instance.pangolinSiteId) throw new AppError(400, 'No tunnel configured', 'NO_TUNNEL');

  const siteId = instance.pangolinSiteId;

  // Pangolin does NOT cascade-delete resources when a site is deleted.
  // We must delete resources first to avoid orphaned entries.
  try {
    const allResources = await client.listResources();
    const siteIdNum = Number(siteId);
    for (const res of allResources) {
      try {
        const targets = await client.listTargets(String(res.resourceId));
        const ours = targets.some((t) => Number(t.siteId) === siteIdNum);
        if (ours) {
          await client.deleteResource(String(res.resourceId));
          logger.info(`[tunnel] ${instance.slug}: deleted resource ${res.name} (${res.fullDomain})`);
        }
      } catch { /* target lookup failed — skip */ }
    }
  } catch (err) {
    logger.warn(`[tunnel] ${instance.slug}: resource cleanup failed: ${(err as Error).message}`);
  }

  // Now delete the site itself
  try {
    await client.deleteSite(siteId);
    logger.info(`[tunnel] ${instance.slug}: deleted Pangolin site ${siteId}`);
  } catch (err) {
    logger.warn(`[tunnel] ${instance.slug}: deleteSite failed (may already be gone): ${(err as Error).message}`);
  }

  // Clear Instance fields
  await prisma.instance.update({
    where: { id: instanceId },
    data: {
      pangolinEndpoint: null,
      pangolinSiteId: null,
      pangolinNewtId: null,
      pangolinNewtSecret: null,
    },
  });

  // Push empty Pangolin vars to remote .env
  if (instance.isRemote) {
    try {
      const driver = await getRemoteDriverForInstance({
        id: instance.id,
        slug: instance.slug,
        isRemote: instance.isRemote,
        agentUrl: instance.agentUrl,
      });
      const currentEnv = await driver.readEnvFile('');
      const envContent = buildUpdatedEnv(currentEnv, {
        PANGOLIN_ENDPOINT: '',
        PANGOLIN_SITE_ID: '',
        PANGOLIN_NEWT_ID: '',
        PANGOLIN_NEWT_SECRET: '',
      });
      await driver.writeFiles('', [{ relativePath: '.env', content: envContent }]);

      // Stop newt container (best effort)
      try {
        await driver.composeStop('', '');
        await driver.composeUp('', ''); // restart everything except newt won't start without creds
      } catch { /* ignore */ }
    } catch (err) {
      logger.warn(`[tunnel] ${instance.slug}: failed to push empty env to remote: ${(err as Error).message}`);
    }
  }

  // Audit log
  if (userId) {
    await prisma.auditLog.create({
      data: {
        userId,
        instanceId,
        action: AuditAction.PANGOLIN_TEARDOWN,
        details: { source: 'remote', siteId } as unknown as Prisma.InputJsonValue,
        ipAddress: ipAddress ?? null,
      },
    });
  }

  return { tornDown: true };
}

// ─── Status ────────────────────────────────────────────────────────

export interface TunnelStatus {
  configured: boolean;
  online?: boolean;
  siteId?: string;
  endpoint?: string;
  resources?: Array<{
    subdomain: string;
    name: string;
    resourceId: string;
    hasTarget: boolean;
    targetIp?: string;
    targetPort?: number;
  }>;
}

export async function getTunnelStatus(instanceId: string): Promise<TunnelStatus> {
  const instance = await prisma.instance.findUnique({ where: { id: instanceId } });
  if (!instance) throw new AppError(404, 'Instance not found', 'NOT_FOUND');

  if (!instance.pangolinSiteId) {
    return { configured: false };
  }

  // For local instances, return stored values without querying Pangolin API
  if (!instance.isRemote) {
    return {
      configured: true,
      siteId: instance.pangolinSiteId ?? undefined,
      endpoint: instance.pangolinEndpoint ?? undefined,
    };
  }

  const client = getPangolinClient();

  let online = false;
  try {
    const site = await client.getSite(instance.pangolinSiteId);
    online = site.online ?? false;
  } catch (err) {
    logger.warn(`[tunnel] ${instance.slug}: getSite failed: ${(err as Error).message}`);
  }

  const resources: TunnelStatus['resources'] = [];
  try {
    const allResources = await client.listResources();
    const siteIdNum = Number(instance.pangolinSiteId);
    // Filter to resources that have a target pointing to our siteId.
    // This is the most reliable filter since it uses the actual Pangolin
    // site association rather than guessing from subdomain names.
    for (const res of allResources) {
      let hasTarget = false;
      let targetIp: string | undefined;
      let targetPort: number | undefined;
      let belongsToUs = false;
      try {
        const targets = await client.listTargets(String(res.resourceId));
        for (const t of targets) {
          if (Number(t.siteId) === siteIdNum) {
            belongsToUs = true;
            hasTarget = true;
            targetIp = t.ip;
            targetPort = t.port;
            break;
          }
        }
      } catch { /* ignore */ }

      if (belongsToUs) {
        // Extract subdomain from fullDomain for display
        const fd = res.fullDomain || '';
        const domainSuffix = `.${instance.domain}`;
        const subdomain = fd.endsWith(domainSuffix)
          ? fd.slice(0, -domainSuffix.length)
          : fd === instance.domain ? '' : fd;
        resources.push({
          subdomain,
          name: res.name,
          resourceId: String(res.resourceId),
          hasTarget,
          targetIp,
          targetPort,
        });
      }
    }
  } catch (err) {
    logger.warn(`[tunnel] ${instance.slug}: listResources failed: ${(err as Error).message}`);
  }

  return {
    configured: true,
    online,
    siteId: instance.pangolinSiteId ?? undefined,
    endpoint: instance.pangolinEndpoint ?? undefined,
    resources,
  };
}

// ─── Import existing tunnel ────────────────────────────────────────

/**
 * Adopt a tunnel that was set up outside the CCP (e.g. by `config.sh --pangolin-site new`
 * on the remote instance itself). Reads the remote `.env` via the agent, pulls the
 * Pangolin vars, verifies the site exists in the CCP's Pangolin org, and persists the
 * values on the Instance record so subsequent sync/teardown/status calls work.
 *
 * We DO NOT fetch the Newt secret from Pangolin — it's only in the remote .env, and
 * Pangolin's API does not expose it after site creation. If the remote .env is missing
 * it, the user has to teardown + setup via CCP instead.
 */
export async function importTunnel(
  instanceId: string,
  userId?: string,
  ipAddress?: string | null
) {
  const client = getPangolinClient();
  const instance = await prisma.instance.findUnique({ where: { id: instanceId } });
  if (!instance) throw new AppError(404, 'Instance not found', 'NOT_FOUND');
  if (!instance.isRemote) throw new AppError(400, 'Import only applies to remote instances', 'NOT_REMOTE');
  if (instance.pangolinSiteId) {
    throw new AppError(400, 'Tunnel already imported/configured — use sync or teardown instead', 'ALREADY_CONFIGURED');
  }

  // 1. Read the remote .env via mTLS agent
  const driver = await getRemoteDriverForInstance({
    id: instance.id,
    slug: instance.slug,
    isRemote: instance.isRemote,
    agentUrl: instance.agentUrl,
  });
  const envVars = await driver.readEnvFile('');
  if (!envVars) {
    throw new AppError(502, 'Could not read .env from remote agent', 'AGENT_READ_FAILED');
  }

  // 2. Extract Pangolin vars
  const siteId = envVars.PANGOLIN_SITE_ID?.trim();
  const newtId = envVars.PANGOLIN_NEWT_ID?.trim();
  const newtSecret = envVars.PANGOLIN_NEWT_SECRET?.trim();
  const endpoint = envVars.PANGOLIN_ENDPOINT?.trim() || env.PANGOLIN_API_URL?.replace(/\/v1$/, '') || '';

  const missing: string[] = [];
  if (!siteId) missing.push('PANGOLIN_SITE_ID');
  if (!newtId) missing.push('PANGOLIN_NEWT_ID');
  if (!newtSecret) missing.push('PANGOLIN_NEWT_SECRET');
  if (missing.length > 0) {
    throw new AppError(
      400,
      `Remote .env missing Pangolin credentials: ${missing.join(', ')}. ` +
      `Either the tunnel was never set up, or it was torn down. ` +
      `Use "Setup Tunnel" to create a fresh one via CCP.`,
      'REMOTE_ENV_MISSING_TUNNEL'
    );
  }

  // 3. Verify the site exists in the CCP's Pangolin org (and that our API key can see it)
  let onlineNow = false;
  try {
    const site = await client.getSite(siteId!);
    onlineNow = site.online ?? false;
  } catch (err) {
    throw new AppError(
      400,
      `Site ${siteId} not found in CCP's Pangolin org (${env.PANGOLIN_ORG_ID}). ` +
      `The remote instance may be using a different Pangolin org than the CCP is configured for. ` +
      `Error: ${(err as Error).message}`,
      'SITE_NOT_IN_CCP_ORG'
    );
  }

  // 4. Persist on Instance record
  await prisma.instance.update({
    where: { id: instanceId },
    data: {
      pangolinEndpoint: endpoint || null,
      pangolinSiteId: siteId,
      pangolinNewtId: newtId,
      pangolinNewtSecret: newtSecret,
      // Note: we do NOT infer pangolinSubdomainPrefix — import assumes standard
      // subdomains (app., api., etc.). If the user was using a prefix, they should
      // teardown + setup via CCP instead.
    },
  });

  // 5. Audit log
  if (userId) {
    await prisma.auditLog.create({
      data: {
        userId,
        instanceId,
        action: AuditAction.PANGOLIN_SETUP,
        details: {
          source: 'import',
          siteId,
          endpoint,
          onlineAtImport: onlineNow,
        } as unknown as Prisma.InputJsonValue,
        ipAddress: ipAddress ?? null,
      },
    });
  }

  logger.info(`[tunnel] ${instance.slug}: imported existing tunnel (site ${siteId}, online=${onlineNow})`);

  return {
    imported: true,
    siteId,
    endpoint,
    online: onlineNow,
  };
}

// ─── .env Helpers ──────────────────────────────────────────────────

/**
 * Quote a .env value if it contains characters that dotenv parsers interpret:
 *   # (comment), = (separator), spaces, quotes, backslashes, newlines.
 * Pangolin-issued UUIDs/base64 secrets typically don't need quoting, but
 * defensive quoting prevents silent corruption if they ever do.
 */
function quoteEnvValue(value: string): string {
  if (/[\s#"'\\=\n\r]/.test(value)) {
    return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`;
  }
  return value;
}

/**
 * Build an updated .env string by replacing/appending the given key-value pairs.
 * Preserves all existing keys not in the update set.
 */
function buildUpdatedEnv(
  currentEnv: Record<string, string> | null,
  updates: Record<string, string>
): string {
  const lines: string[] = [];
  const seen = new Set<string>();

  // If we have the current env, reproduce it with replacements
  if (currentEnv) {
    for (const [key, value] of Object.entries(currentEnv)) {
      if (key in updates) {
        if (updates[key]) lines.push(`${key}=${quoteEnvValue(updates[key]!)}`);
        // If update value is empty, omit the line (remove the var)
        seen.add(key);
      } else {
        lines.push(`${key}=${quoteEnvValue(value)}`);
      }
    }
  }

  // Append new keys not already in the file
  for (const [key, value] of Object.entries(updates)) {
    if (!seen.has(key) && value) {
      lines.push(`${key}=${quoteEnvValue(value)}`);
    }
  }

  return lines.join('\n') + '\n';
}
