import { randomUUID } from 'node:crypto';
import type { FastifyInstance } from 'fastify';
import { BadRequestError, ForbiddenError, NotFoundError } from '../../lib/errors.js';
import {
  AgentDefinitionDoc,
  AgentVersionDoc,
  CreateAgentSchema,
  CreateAgentVersionSchema,
  ListAgentsQuerySchema,
  UpdateAgentSchema,
} from './types.js';
import * as repo from './repository.js';

function requireAdmin(req: { jwtPayload?: { sub?: string; role?: string; productId?: string } }): {
  userId: string;
  productId: string;
} {
  const payload = req.jwtPayload;
  if (!payload?.sub) throw new ForbiddenError('Authentication required');
  if (!payload.role || !['super_admin', 'admin'].includes(payload.role)) {
    throw new ForbiddenError('Admin access required');
  }
  return {
    userId: payload.sub,
    productId: payload.productId ?? process.env.DEFAULT_PRODUCT_ID ?? 'lysnrai',
  };
}

export async function agentRoutes(app: FastifyInstance) {
  app.get('/agents', async req => {
    const access = requireAdmin(req);
    const parsed = ListAgentsQuerySchema.safeParse(req.query);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    return repo.listAgents(access.productId, parsed.data);
  });

  app.post('/agents', async req => {
    const access = requireAdmin(req);
    const parsed = CreateAgentSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    const now = new Date().toISOString();
    const agentId = `agt_${randomUUID()}`;
    const versionNumber = 1;

    const agent: AgentDefinitionDoc = {
      id: agentId,
      productId: access.productId,
      key: parsed.data.key,
      name: parsed.data.name,
      description: parsed.data.description,
      ownerTeam: parsed.data.ownerTeam,
      status: 'draft',
      visibility: parsed.data.visibility,
      currentVersion: versionNumber,
      tags: parsed.data.tags,
      metadata: parsed.data.metadata,
      createdAt: now,
      updatedAt: now,
    };

    const created = await repo.createAgent(agent);

    if (parsed.data.initialVersion) {
      const version: AgentVersionDoc = {
        id: `${agentId}:v${versionNumber}`,
        agentId,
        productId: access.productId,
        version: versionNumber,
        status: 'draft',
        prompt: parsed.data.initialVersion.prompt,
        systemInstructions: parsed.data.initialVersion.systemInstructions,
        modelPolicy: parsed.data.initialVersion.modelPolicy,
        toolBindings: parsed.data.initialVersion.toolBindings,
        config: parsed.data.initialVersion.config,
        changeSummary: parsed.data.initialVersion.changeSummary,
        createdBy: access.userId,
        createdAt: now,
      };
      await repo.createAgentVersion(version);
    }

    return created;
  });

  app.get('/agents/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    return repo.getAgent(id, access.productId);
  });

  app.patch('/agents/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const parsed = UpdateAgentSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    return repo.updateAgent(id, access.productId, parsed.data);
  });

  app.get('/agents/:id/versions', async req => {
    requireAdmin(req);
    const { id } = req.params as { id: string };
    return repo.listAgentVersions(id);
  });

  app.post('/agents/:id/versions', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const agent = await repo.getAgent(id, access.productId);
    const parsed = CreateAgentVersionSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    const nextVersion = agent.currentVersion + 1;
    const version: AgentVersionDoc = {
      id: `${id}:v${nextVersion}`,
      agentId: id,
      productId: access.productId,
      version: nextVersion,
      status: parsed.data.status,
      prompt: parsed.data.prompt,
      systemInstructions: parsed.data.systemInstructions,
      modelPolicy: parsed.data.modelPolicy,
      toolBindings: parsed.data.toolBindings,
      config: parsed.data.config,
      changeSummary: parsed.data.changeSummary,
      createdBy: access.userId,
      createdAt: new Date().toISOString(),
    };

    const createdVersion = await repo.createAgentVersion(version);
    await repo.updateAgent(id, access.productId, {
      currentVersion: nextVersion,
      status: parsed.data.status === 'published' ? 'active' : agent.status,
    });
    return createdVersion;
  });

  // ── Get agent by key ───────────────────────────────────
  app.get('/agents/by-key/:key', async req => {
    const access = requireAdmin(req);
    const { key } = req.params as { key: string };
    const agent = await repo.getAgentByKey(access.productId, key);
    if (!agent) throw new NotFoundError(`Agent with key '${key}' not found`);
    return agent;
  });

  // ── Get published version ──────────────────────────────
  app.get('/agents/:id/published', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    await repo.getAgent(id, access.productId);
    const published = await repo.getPublishedVersion(id);
    if (!published) throw new NotFoundError('No published version found');
    return published;
  });

  // ── Publish a version ──────────────────────────────────
  app.post('/agents/:id/versions/:versionId/publish', async req => {
    const access = requireAdmin(req);
    const { id, versionId } = req.params as { id: string; versionId: string };
    await repo.getAgent(id, access.productId);
    const version = await repo.getAgentVersion(versionId, id);

    if (version.status === 'published') {
      throw new BadRequestError('Version is already published');
    }

    // Deprecate any previously published version
    const currentPublished = await repo.getPublishedVersion(id);
    if (currentPublished) {
      await repo.updateAgentVersion(currentPublished.id, id, { status: 'deprecated' });
    }

    const updated = await repo.updateAgentVersion(versionId, id, { status: 'published' });
    await repo.updateAgent(id, access.productId, {
      status: 'active',
      currentVersion: version.version,
    });
    return updated;
  });

  // ── Deprecate a version ────────────────────────────────
  app.post('/agents/:id/versions/:versionId/deprecate', async req => {
    const access = requireAdmin(req);
    const { id, versionId } = req.params as { id: string; versionId: string };
    await repo.getAgent(id, access.productId);
    const version = await repo.getAgentVersion(versionId, id);

    if (version.status === 'deprecated') {
      throw new BadRequestError('Version is already deprecated');
    }

    return repo.updateAgentVersion(versionId, id, { status: 'deprecated' });
  });

  // ── Delete agent (draft only) ──────────────────────────
  app.delete('/agents/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const agent = await repo.getAgent(id, access.productId);
    if (agent.status !== 'draft') {
      throw new BadRequestError('Only draft agents can be deleted');
    }
    await repo.deleteAgent(id, access.productId);
    return { deleted: true };
  });
}
