import { Router, Request, Response } from 'express';
import rateLimit from 'express-rate-limit';
import { prisma } from '../../lib/prisma';
import { Prisma, AuditAction, InstanceStatus, AgentRegistrationStatus } from '@prisma/client';
import { validateInviteCode, markCodeUsed } from '../../services/invite-code.service';
import { issueAgentCert } from '../../services/certificate.service';
import { authenticate, requireRole } from '../../middleware/auth';
import { AppError } from '../../middleware/error-handler';
import { logger } from '../../utils/logger';

const router = Router();

// SECURITY: Strict rate limiter for unauthenticated agent /register endpoint.
// Registration is the real attack surface (invite-code guessing); keep it tight.
const agentRegistrationLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 10,                    // 10 attempts per window per IP
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'RATE_LIMITED', message: 'Too many registration attempts, try again later' },
});

// Looser limiter for /poll. The agent polls every 30s (60/15min) while waiting
// for admin approval; at the old 10/15min limit it hit 429 after 5 min and
// required a restart to recover. /poll is scoped to a specific registrationId
// and slug, so the blast radius is bounded even at 180/15min (one poll per 5s).
const agentPollLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 180,
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'RATE_LIMITED', message: 'Poll rate exceeded — back off' },
});

// ─── Public Endpoints (used by remote agents during phone-home) ──────

/**
 * POST /api/agents/register
 * Agent phones home with invite code + instance metadata.
 * Creates a PENDING registration for admin approval.
 */
router.post('/register', agentRegistrationLimiter, async (req: Request, res: Response) => {
  const { inviteCode, slug, name, domain, agentUrl, basePath, composeProject, metadata } = req.body;

  if (!inviteCode || !slug || !agentUrl) {
    throw new AppError(400, 'inviteCode, slug, and agentUrl are required');
  }

  // Validate invite code
  const invite = await validateInviteCode(inviteCode);

  // Check for duplicate pending registrations
  const existing = await prisma.agentRegistration.findFirst({
    where: { slug, status: AgentRegistrationStatus.PENDING },
  });
  if (existing) {
    res.json({ registrationId: existing.id, status: 'PENDING' });
    return;
  }

  // Create pending registration
  const registration = await prisma.agentRegistration.create({
    data: {
      inviteCodeId: invite.id,
      slug: slug || '',
      name: name || slug || '',
      domain: domain || '',
      agentUrl,
      basePath: basePath || '',
      composeProject: composeProject || slug || '',
      metadata: metadata || null,
    },
  });

  logger.info(`[agents] New registration request: ${slug} from ${agentUrl} (invite: ${invite.code})`);

  res.status(201).json({
    registrationId: registration.id,
    status: 'PENDING',
    message: 'Registration submitted — waiting for admin approval',
  });
});

/**
 * GET /api/agents/poll
 * Agent polls to check if registration was approved.
 * Returns cert bundle on approval.
 */
router.get('/poll', agentPollLimiter, async (req: Request, res: Response) => {
  const { registrationId, slug } = req.query;

  if (!registrationId && !slug) {
    throw new AppError(400, 'registrationId or slug required');
  }

  const registration = await prisma.agentRegistration.findFirst({
    where: registrationId
      ? { id: registrationId as string }
      : { slug: slug as string, status: { in: [AgentRegistrationStatus.PENDING, AgentRegistrationStatus.APPROVED] } },
    orderBy: { createdAt: 'desc' },
  });

  if (!registration) {
    throw new AppError(404, 'Registration not found');
  }

  if (registration.status === AgentRegistrationStatus.APPROVED && registration.certBundle) {
    // Return cert bundle — agent will save certs and restart with mTLS
    const bundle = registration.certBundle;

    // SECURITY: Wipe the cert bundle (contains private key) after first delivery.
    // The agent gets one chance to retrieve it; after that it's gone from the DB.
    await prisma.agentRegistration.update({
      where: { id: registration.id },
      data: { certBundle: Prisma.DbNull },
    });
    logger.info(`[agents] Cert bundle delivered and wiped for ${registration.slug}`);

    res.json({
      status: 'APPROVED',
      certBundle: bundle,
    });
    return;
  }

  if (registration.status === AgentRegistrationStatus.APPROVED && !registration.certBundle) {
    // Cert bundle was already delivered and wiped — agent must re-issue if it missed it
    res.json({ status: 'APPROVED', certBundle: null, message: 'Certificate bundle already delivered. Contact admin to re-issue.' });
    return;
  }

  if (registration.status === AgentRegistrationStatus.REJECTED) {
    res.json({ status: 'REJECTED' });
    return;
  }

  res.json({ status: 'PENDING' });
});

// ─── Authenticated Endpoints (CCP admin) ─────────────────────────────

/**
 * GET /api/agents/registrations
 * List all agent registrations (pending, approved, rejected).
 */
router.get('/registrations', authenticate, requireRole('SUPER_ADMIN', 'OPERATOR'), async (_req: Request, res: Response) => {
  const registrations = await prisma.agentRegistration.findMany({
    orderBy: { createdAt: 'desc' },
    take: 100,
  });
  res.json(registrations);
});

/**
 * POST /api/agents/registrations/:id/approve
 * Approve a pending registration: issue certs, create Instance, mark approved.
 */
router.post('/registrations/:id/approve', authenticate, requireRole('SUPER_ADMIN'), async (req: Request, res: Response) => {
  const { id } = req.params;
  const registration = await prisma.agentRegistration.findUnique({ where: { id: id as string } });
  if (!registration) throw new AppError(404, 'Registration not found');
  if (registration.status !== AgentRegistrationStatus.PENDING) {
    throw new AppError(400, `Registration is ${registration.status}, not PENDING`);
  }

  // Create the Instance record.
  // The slug is unique. If a stale Instance with the same slug exists (e.g. the
  // operator tore down the underlying stack without running ccp-deregister.sh)
  // surface a clean 409 instead of leaking the raw Prisma error.
  let instance;
  try {
    instance = await prisma.instance.create({
      data: {
        slug: registration.slug,
        name: registration.name,
        domain: registration.domain,
        status: InstanceStatus.STOPPED,
        statusMessage: 'Remote instance registered — agent connecting',
        basePath: registration.basePath,
        composeProject: registration.composeProject,
        portConfig: (registration.metadata as Record<string, unknown>)?.portConfig || { api: 4000, admin: 3000, postgres: 5432, nginx: 80 },
        isRegistered: true,
        isRemote: true,
        agentUrl: registration.agentUrl,
        adminEmail: (registration.metadata as Record<string, unknown>)?.adminEmail as string || 'admin@example.com',
      },
    });
  } catch (err) {
    if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === 'P2002') {
      const target = (err.meta?.target as string[] | undefined) ?? [];
      if (target.includes('slug')) {
        throw new AppError(
          409,
          `Slug '${registration.slug}' is already in use by another Instance. Delete the stale instance first (DELETE /api/instances/:id) or run scripts/ccp-deregister.sh from the target host.`,
          'SLUG_CONFLICT'
        );
      }
    }
    throw err;
  }

  // Issue mTLS certificates
  const certMaterials = await issueAgentCert(instance.id, registration.slug, registration.agentUrl);

  // Mark invite code as used
  const invite = await prisma.agentInviteCode.findUnique({ where: { id: registration.inviteCodeId } });
  if (invite && !invite.usedAt) {
    await markCodeUsed(invite.code, instance.id);
  }

  // Update registration with approval + cert bundle
  await prisma.agentRegistration.update({
    where: { id: id as string },
    data: {
      status: AgentRegistrationStatus.APPROVED,
      instanceId: instance.id,
      approvedById: (req as unknown as { user: { id: string } }).user.id,
      approvedAt: new Date(),
      certBundle: {
        caCertPem: certMaterials.caCertPem,
        agentCertPem: certMaterials.agentCertPem,
        agentKeyPem: certMaterials.agentKeyPem,
        ccpFingerprint: certMaterials.fingerprint,
      },
    },
  });

  // Audit log
  await prisma.auditLog.create({
    data: {
      userId: (req as unknown as { user: { id: string } }).user.id,
      instanceId: instance.id,
      action: AuditAction.AGENT_APPROVE,
      details: { slug: registration.slug, agentUrl: registration.agentUrl },
      ipAddress: req.ip || null,
    },
  });

  logger.info(`[agents] Registration approved: ${registration.slug} → instance ${instance.id}`);

  res.json({
    message: 'Registration approved — agent will receive certificates on next poll',
    instanceId: instance.id,
  });
});

/**
 * POST /api/agents/registrations/:id/reject
 * Reject a pending registration.
 */
router.post('/registrations/:id/reject', authenticate, requireRole('SUPER_ADMIN', 'OPERATOR'), async (req: Request, res: Response) => {
  const { id } = req.params;
  const registration = await prisma.agentRegistration.findUnique({ where: { id: id as string } });
  if (!registration) throw new AppError(404, 'Registration not found');
  if (registration.status !== AgentRegistrationStatus.PENDING) {
    throw new AppError(400, `Registration is ${registration.status}, not PENDING`);
  }

  await prisma.agentRegistration.update({
    where: { id: id as string },
    data: {
      status: AgentRegistrationStatus.REJECTED,
      rejectedAt: new Date(),
    },
  });

  await prisma.auditLog.create({
    data: {
      userId: (req as unknown as { user: { id: string } }).user.id,
      action: AuditAction.AGENT_REJECT,
      details: { slug: registration.slug, agentUrl: registration.agentUrl },
      ipAddress: req.ip || null,
    },
  });

  res.json({ message: 'Registration rejected' });
});

/**
 * POST /api/agents/registrations/:id/reissue-certs
 * Re-issue certificates for an approved registration whose cert bundle was already
 * delivered and wiped (e.g. agent missed the one-shot delivery).
 */
router.post('/registrations/:id/reissue-certs', authenticate, requireRole('SUPER_ADMIN'), async (req: Request, res: Response) => {
  const { id } = req.params;
  const registration = await prisma.agentRegistration.findUnique({ where: { id: id as string } });
  if (!registration) throw new AppError(404, 'Registration not found');
  if (registration.status !== AgentRegistrationStatus.APPROVED) {
    throw new AppError(400, `Registration is ${registration.status}, not APPROVED`);
  }
  if (!registration.instanceId) {
    throw new AppError(400, 'Registration has no linked instance');
  }

  // Re-issue certs and write back to registration for agent to pick up
  const certMaterials = await issueAgentCert(registration.instanceId, registration.slug, registration.agentUrl);

  await prisma.agentRegistration.update({
    where: { id: id as string },
    data: {
      certBundle: {
        caCertPem: certMaterials.caCertPem,
        agentCertPem: certMaterials.agentCertPem,
        agentKeyPem: certMaterials.agentKeyPem,
        ccpFingerprint: certMaterials.fingerprint,
      },
    },
  });

  await prisma.auditLog.create({
    data: {
      userId: (req as unknown as { user: { id: string } }).user.id,
      instanceId: registration.instanceId,
      action: AuditAction.AGENT_APPROVE,
      details: { slug: registration.slug, reason: 'cert-reissue' },
      ipAddress: req.ip || null,
    },
  });

  logger.info(`[agents] Certificates re-issued for ${registration.slug}`);
  res.json({ message: 'Certificates re-issued — agent will receive them on next poll' });
});

export default router;
