import { spawn } from 'node:child_process';
import {
  adminSettings,
  auditLog,
  buildLogs,
  builds,
  count,
  createDb,
  desc,
  eq,
  gte,
  inArray,
  mcpServers,
  memberships,
  organizations,
  sql,
  toolCallMetrics,
  users,
} from '@bmm/db';
import { SYSTEM_PROMPT } from '@bmm/llm';
import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import { audit } from '../lib/audit.js';
import { encryptionStatus, rotateKeys } from '../lib/crypto.js';
import { getBuildQueue } from '../lib/queue.js';
import { getRedis } from '../lib/redis.js';
import { requireAdmin } from '../plugins/session.js';

const db = createDb();

const PROMPT_KEY = 'system_prompt_override';

export async function adminRoutes(app: FastifyInstance): Promise<void> {
  // ---- Overview metrics ----
  app.get('/v1/admin/overview', { preHandler: requireAdmin }, async (_req, reply) => {
    const dayAgo = new Date(Date.now() - 24 * 3600_000);
    const sevenDaysAgo = new Date(Date.now() - 7 * 86400_000);

    const [
      userCount,
      orgCount,
      serverCount,
      buildCount,
      callCount,
      liveCount,
      failedBuildCount,
      newUsersLast7d,
      newServersLast7d,
    ] = await Promise.all([
      db
        .select({ c: count() })
        .from(users)
        .then((r) => Number(r[0]?.c ?? 0)),
      db
        .select({ c: count() })
        .from(organizations)
        .then((r) => Number(r[0]?.c ?? 0)),
      db
        .select({ c: count() })
        .from(mcpServers)
        .then((r) => Number(r[0]?.c ?? 0)),
      db
        .select({ c: count() })
        .from(builds)
        .then((r) => Number(r[0]?.c ?? 0)),
      db
        .select({ c: count() })
        .from(toolCallMetrics)
        .then((r) => Number(r[0]?.c ?? 0)),
      db
        .select({ c: count() })
        .from(mcpServers)
        .where(eq(mcpServers.status, 'live'))
        .then((r) => Number(r[0]?.c ?? 0)),
      db
        .select({ c: count() })
        .from(builds)
        .where(eq(builds.status, 'failed'))
        .then((r) => Number(r[0]?.c ?? 0)),
      db
        .select({ c: count() })
        .from(users)
        .where(gte(users.createdAt, sevenDaysAgo))
        .then((r) => Number(r[0]?.c ?? 0)),
      db
        .select({ c: count() })
        .from(mcpServers)
        .where(gte(mcpServers.createdAt, sevenDaysAgo))
        .then((r) => Number(r[0]?.c ?? 0)),
    ]);

    // Server status breakdown
    const statusBreakdown = await db
      .select({ status: mcpServers.status, c: count() })
      .from(mcpServers)
      .groupBy(mcpServers.status);

    // Recent activity from audit log
    const recent = await db.select().from(auditLog).orderBy(desc(auditLog.createdAt)).limit(15);

    // Builds in last 24h with status
    const recentBuilds = await db
      .select({ status: builds.status, c: count() })
      .from(builds)
      .where(gte(builds.createdAt, dayAgo))
      .groupBy(builds.status);

    return reply.send({
      totals: {
        users: userCount,
        orgs: orgCount,
        servers: serverCount,
        liveServers: liveCount,
        builds: buildCount,
        failedBuilds: failedBuildCount,
        toolCalls: callCount,
      },
      trends: {
        newUsersLast7d,
        newServersLast7d,
      },
      statusBreakdown,
      recentBuilds24h: recentBuilds,
      recentActivity: recent,
    });
  });

  // ---- Users ----
  app.get('/v1/admin/users', { preHandler: requireAdmin }, async (req, reply) => {
    const Query = z.object({
      search: z.string().optional(),
      limit: z.coerce.number().min(1).max(500).default(100),
    });
    const parsed = Query.safeParse(req.query);
    if (!parsed.success) return reply.code(400).send({ error: 'invalid_query' });

    const rows = await db
      .select()
      .from(users)
      .orderBy(desc(users.createdAt))
      .limit(parsed.data.limit);
    const filtered = parsed.data.search
      ? rows.filter((u) => {
          const q = parsed.data.search!.toLowerCase();
          return (
            (u.email?.toLowerCase().includes(q) ?? false) ||
            (u.phone?.toLowerCase().includes(q) ?? false) ||
            (u.name?.toLowerCase().includes(q) ?? false)
          );
        })
      : rows;

    // attach org + server count
    const enriched = await Promise.all(
      filtered.map(async (u) => {
        const [m] = await db
          .select({
            orgId: memberships.orgId,
            orgName: organizations.name,
            orgSlug: organizations.slug,
            plan: organizations.plan,
            role: memberships.role,
          })
          .from(memberships)
          .innerJoin(organizations, eq(organizations.id, memberships.orgId))
          .where(eq(memberships.userId, u.id))
          .limit(1);

        const [sc] = await db
          .select({ c: count() })
          .from(mcpServers)
          .where(m ? eq(mcpServers.orgId, m.orgId) : sql`false`);

        return {
          id: u.id,
          email: u.email,
          name: u.name,
          isAdmin: u.isAdmin,
          emailVerified: u.emailVerified,
          lastLoginAt: u.lastLoginAt,
          createdAt: u.createdAt,
          org: m ?? null,
          serverCount: Number(sc?.c ?? 0),
        };
      }),
    );

    return reply.send({ users: enriched });
  });

  app.patch('/v1/admin/users/:id', { preHandler: requireAdmin }, async (req, reply) => {
    const Params = z.object({ id: z.string().uuid() });
    const Body = z.object({
      isAdmin: z.boolean().optional(),
      name: z.string().optional(),
    });
    const p = Params.safeParse(req.params);
    const b = Body.safeParse(req.body);
    if (!p.success || !b.success) return reply.code(400).send({ error: 'invalid_input' });

    await db.update(users).set(b.data).where(eq(users.id, p.data.id));
    await audit({
      orgId: req.user!.orgId,
      userId: req.user!.userId,
      action: 'admin.user.update',
      resourceType: 'user',
      resourceId: p.data.id,
      metadata: b.data,
      ipAddress: req.ip,
    });
    return reply.send({ ok: true });
  });

  app.delete('/v1/admin/users/:id', { preHandler: requireAdmin }, async (req, reply) => {
    const Params = z.object({ id: z.string().uuid() });
    const p = Params.safeParse(req.params);
    if (!p.success) return reply.code(400).send({ error: 'invalid_id' });
    if (p.data.id === req.user!.userId) {
      return reply.code(400).send({ error: 'cannot_delete_self' });
    }
    await db.delete(users).where(eq(users.id, p.data.id));
    await audit({
      orgId: req.user!.orgId,
      userId: req.user!.userId,
      action: 'admin.user.delete',
      resourceType: 'user',
      resourceId: p.data.id,
      ipAddress: req.ip,
    });
    return reply.send({ ok: true });
  });

  // ---- Organizations ----
  app.get('/v1/admin/orgs', { preHandler: requireAdmin }, async (_req, reply) => {
    const rows = await db.select().from(organizations).orderBy(desc(organizations.createdAt));

    const enriched = await Promise.all(
      rows.map(async (o) => {
        const [mc] = await db
          .select({ c: count() })
          .from(memberships)
          .where(eq(memberships.orgId, o.id));
        const [sc] = await db
          .select({ c: count() })
          .from(mcpServers)
          .where(eq(mcpServers.orgId, o.id));
        return {
          ...o,
          memberCount: Number(mc?.c ?? 0),
          serverCount: Number(sc?.c ?? 0),
        };
      }),
    );
    return reply.send({ orgs: enriched });
  });

  app.patch('/v1/admin/orgs/:id', { preHandler: requireAdmin }, async (req, reply) => {
    const Params = z.object({ id: z.string().uuid() });
    const Body = z.object({
      plan: z.enum(['hobby', 'pro', 'team', 'enterprise']).optional(),
      monthlyCallQuota: z.coerce.number().int().min(0).optional(),
      suspended: z.boolean().optional(),
      suspendedReason: z.string().max(500).nullable().optional(),
    });
    const p = Params.safeParse(req.params);
    const b = Body.safeParse(req.body);
    if (!p.success || !b.success) return reply.code(400).send({ error: 'invalid_input' });

    await db.update(organizations).set(b.data).where(eq(organizations.id, p.data.id));

    // If suspended, pause all the org's servers
    if (b.data.suspended === true) {
      await db
        .update(mcpServers)
        .set({ status: 'paused', updatedAt: new Date() })
        .where(eq(mcpServers.orgId, p.data.id));
    }
    await audit({
      orgId: req.user!.orgId,
      userId: req.user!.userId,
      action: 'admin.org.update',
      resourceType: 'org',
      resourceId: p.data.id,
      metadata: b.data,
      ipAddress: req.ip,
    });
    return reply.send({ ok: true });
  });

  // ---- Servers ----
  app.get('/v1/admin/servers', { preHandler: requireAdmin }, async (req, reply) => {
    const Query = z.object({
      status: z.string().optional(),
      limit: z.coerce.number().min(1).max(500).default(200),
    });
    const parsed = Query.safeParse(req.query);
    if (!parsed.success) return reply.code(400).send({ error: 'invalid_query' });

    const rows = await db
      .select({
        server: mcpServers,
        org: {
          id: organizations.id,
          name: organizations.name,
          slug: organizations.slug,
          plan: organizations.plan,
        },
      })
      .from(mcpServers)
      .innerJoin(organizations, eq(organizations.id, mcpServers.orgId))
      .orderBy(desc(mcpServers.updatedAt))
      .limit(parsed.data.limit);

    const filtered = parsed.data.status
      ? rows.filter((r) => r.server.status === parsed.data.status)
      : rows;

    return reply.send({ servers: filtered });
  });

  app.post('/v1/admin/servers/:id/rebuild', { preHandler: requireAdmin }, async (req, reply) => {
    const Params = z.object({ id: z.string().uuid() });
    const p = Params.safeParse(req.params);
    if (!p.success) return reply.code(400).send({ error: 'invalid_id' });

    const [server] = await db
      .select()
      .from(mcpServers)
      .where(eq(mcpServers.id, p.data.id))
      .limit(1);
    if (!server) return reply.code(404).send({ error: 'not_found' });

    // Get last build's prompt
    const [lastBuild] = await db
      .select()
      .from(builds)
      .where(eq(builds.serverId, server.id))
      .orderBy(desc(builds.version))
      .limit(1);
    if (!lastBuild) return reply.code(400).send({ error: 'no_previous_build' });

    const nextVersion = (server.currentVersion ?? 0) + 1;
    const [build] = await db
      .insert(builds)
      .values({
        serverId: server.id,
        version: nextVersion,
        prompt: lastBuild.prompt,
        status: 'queued',
      })
      .returning();
    if (!build) return reply.code(500).send({ error: 'build_create_failed' });

    await db
      .update(mcpServers)
      .set({ status: 'queued', updatedAt: new Date() })
      .where(eq(mcpServers.id, server.id));

    await getBuildQueue().add('generate', {
      buildId: build.id,
      serverId: server.id,
      orgId: server.orgId,
      prompt: lastBuild.prompt,
      version: nextVersion,
      slug: server.slug,
      serverName: server.name,
      secrets: {},
    });

    await audit({
      orgId: req.user!.orgId,
      userId: req.user!.userId,
      action: 'admin.server.rebuild',
      resourceType: 'server',
      resourceId: server.id,
      metadata: { newVersion: nextVersion },
      ipAddress: req.ip,
    });

    return reply.send({ build });
  });

  app.delete('/v1/admin/servers/:id', { preHandler: requireAdmin }, async (req, reply) => {
    const Params = z.object({ id: z.string().uuid() });
    const p = Params.safeParse(req.params);
    if (!p.success) return reply.code(400).send({ error: 'invalid_id' });

    const [server] = await db
      .select()
      .from(mcpServers)
      .where(eq(mcpServers.id, p.data.id))
      .limit(1);
    if (!server) return reply.code(404).send({ error: 'not_found' });

    await db.delete(mcpServers).where(eq(mcpServers.id, server.id));
    await audit({
      orgId: req.user!.orgId,
      userId: req.user!.userId,
      action: 'admin.server.delete',
      resourceType: 'server',
      resourceId: server.id,
      metadata: { slug: server.slug, name: server.name },
      ipAddress: req.ip,
    });
    return reply.send({ ok: true });
  });

  // ---- Builds ----
  app.get('/v1/admin/builds', { preHandler: requireAdmin }, async (req, reply) => {
    const Query = z.object({
      status: z.string().optional(),
      limit: z.coerce.number().min(1).max(500).default(100),
    });
    const parsed = Query.safeParse(req.query);
    if (!parsed.success) return reply.code(400).send({ error: 'invalid_query' });

    const rows = await db
      .select({
        build: builds,
        server: { id: mcpServers.id, name: mcpServers.name, slug: mcpServers.slug },
        org: { id: organizations.id, name: organizations.name },
      })
      .from(builds)
      .innerJoin(mcpServers, eq(mcpServers.id, builds.serverId))
      .innerJoin(organizations, eq(organizations.id, mcpServers.orgId))
      .orderBy(desc(builds.createdAt))
      .limit(parsed.data.limit);

    const filtered = parsed.data.status
      ? rows.filter((r) => r.build.status === parsed.data.status)
      : rows;

    return reply.send({ builds: filtered });
  });

  app.get('/v1/admin/builds/:id/logs', { preHandler: requireAdmin }, async (req, reply) => {
    const Params = z.object({ id: z.string().uuid() });
    const p = Params.safeParse(req.params);
    if (!p.success) return reply.code(400).send({ error: 'invalid_id' });
    const logs = await db
      .select()
      .from(buildLogs)
      .where(eq(buildLogs.buildId, p.data.id))
      .orderBy(buildLogs.timestamp);
    return reply.send({ logs });
  });

  // ---- Audit (system-wide) ----
  app.get('/v1/admin/audit', { preHandler: requireAdmin }, async (req, reply) => {
    const Query = z.object({
      action: z.string().optional(),
      resourceType: z.string().optional(),
      limit: z.coerce.number().min(1).max(1000).default(200),
    });
    const parsed = Query.safeParse(req.query);
    if (!parsed.success) return reply.code(400).send({ error: 'invalid_query' });

    let rows = await db
      .select({
        entry: auditLog,
        userEmail: users.email,
      })
      .from(auditLog)
      .leftJoin(users, eq(users.id, auditLog.userId))
      .orderBy(desc(auditLog.createdAt))
      .limit(parsed.data.limit);

    if (parsed.data.action) {
      rows = rows.filter((r) => r.entry.action === parsed.data.action);
    }
    if (parsed.data.resourceType) {
      rows = rows.filter((r) => r.entry.resourceType === parsed.data.resourceType);
    }
    return reply.send({ entries: rows });
  });

  // ---- System health ----
  app.get('/v1/admin/system', { preHandler: requireAdmin }, async (_req, reply) => {
    const t0 = Date.now();
    // DB probe
    let dbOk = false;
    let dbLatency: number | null = null;
    try {
      const start = Date.now();
      await db.execute(sql`SELECT 1`);
      dbLatency = Date.now() - start;
      dbOk = true;
    } catch {
      // remains false
    }

    // Redis probe
    let redisOk = false;
    let redisLatency: number | null = null;
    let queueDepth: number | null = null;
    try {
      const start = Date.now();
      const pong = await getRedis().ping();
      redisLatency = Date.now() - start;
      redisOk = pong === 'PONG';
      const q = getBuildQueue();
      const counts = await q.getJobCounts('waiting', 'active', 'completed', 'failed', 'delayed');
      queueDepth = (counts.waiting ?? 0) + (counts.active ?? 0) + (counts.delayed ?? 0);
    } catch {
      // remains false
    }

    // Docker container count
    const containerCount = await new Promise<number | null>((resolve) => {
      const child = spawn('docker', ['ps', '--filter', 'name=bmm-mcp-', '-q'], {
        stdio: ['ignore', 'pipe', 'ignore'],
        shell: process.platform === 'win32',
      });
      let out = '';
      child.stdout?.on('data', (d: Buffer) => {
        out += d.toString();
      });
      child.on('close', (code) => {
        if (code !== 0) {
          resolve(null);
          return;
        }
        const lines = out
          .split(/\r?\n/)
          .map((l) => l.trim())
          .filter(Boolean);
        resolve(lines.length);
      });
      child.on('error', () => resolve(null));
    });

    return reply.send({
      probedAtMs: Date.now() - t0,
      db: { ok: dbOk, latencyMs: dbLatency },
      redis: { ok: redisOk, latencyMs: redisLatency, queueDepth },
      docker: { containerCount },
    });
  });

  // ---- System prompt (read + override) ----
  app.get('/v1/admin/prompt', { preHandler: requireAdmin }, async (_req, reply) => {
    const [row] = await db
      .select()
      .from(adminSettings)
      .where(eq(adminSettings.key, PROMPT_KEY))
      .limit(1);
    return reply.send({
      builtin: SYSTEM_PROMPT,
      override: row?.value ?? null,
      updatedAt: row?.updatedAt ?? null,
    });
  });

  app.patch('/v1/admin/prompt', { preHandler: requireAdmin }, async (req, reply) => {
    const Body = z.object({
      value: z.string().min(1).max(20_000).nullable(),
    });
    const parsed = Body.safeParse(req.body);
    if (!parsed.success) return reply.code(400).send({ error: 'invalid_input' });

    if (parsed.data.value === null) {
      await db.delete(adminSettings).where(eq(adminSettings.key, PROMPT_KEY));
    } else {
      const [existing] = await db
        .select()
        .from(adminSettings)
        .where(eq(adminSettings.key, PROMPT_KEY))
        .limit(1);
      if (existing) {
        await db
          .update(adminSettings)
          .set({
            value: parsed.data.value,
            updatedAt: new Date(),
            updatedBy: req.user!.userId,
          })
          .where(eq(adminSettings.id, existing.id));
      } else {
        await db.insert(adminSettings).values({
          key: PROMPT_KEY,
          value: parsed.data.value,
          updatedBy: req.user!.userId,
        });
      }
    }
    await audit({
      orgId: req.user!.orgId,
      userId: req.user!.userId,
      action: 'admin.prompt.update',
      resourceType: 'admin_settings',
      resourceId: PROMPT_KEY,
      metadata: { hasOverride: parsed.data.value !== null },
      ipAddress: req.ip,
    });
    return reply.send({ ok: true });
  });

  // ---- Encryption: status + key rotation ----
  app.get('/v1/admin/encryption', { preHandler: requireAdmin }, async (_req, reply) => {
    return reply.send(await encryptionStatus());
  });

  app.post('/v1/admin/encryption/rotate', { preHandler: requireAdmin }, async (req, reply) => {
    try {
      const result = await rotateKeys(req.user!.userId);
      await audit({
        orgId: req.user!.orgId,
        userId: req.user!.userId,
        action: 'admin.encryption.rotate',
        resourceType: 'encryption_key',
        metadata: { newVersion: result.newVersion, reEncrypted: result.reEncrypted },
        ipAddress: req.ip,
      });
      return reply.send({ ok: true, ...result });
    } catch (err) {
      app.log.error({ err }, 'encryption key rotation failed');
      return reply.code(500).send({ error: 'rotation_failed', detail: (err as Error).message });
    }
  });

  void inArray; // referenced for future bulk operations
}
