import { Router } from "express";
import os from "node:os";
import { and, eq } from "drizzle-orm";
import type { Db } from "@paperclipai/db";
import { agentService } from "../services/agents.js";
import { resolveAdapterSkillConfig } from "@paperclipai/adapter-utils";
import { skillRegistryService } from "../services/skill-registry.js";
import { skillRatingService } from "../services/skill-registry-ratings.js";
import { getSkillRegistryDb } from "../services/skill-registry-db.js";
import { agentSkills } from "../services/skill-registry-schema.js";
import { assertBoard } from "./authz.js";

/**
 * Resolves the agentSkillsDir for a given agentId by looking up the agent's
 * adapter type and calling resolveAdapterSkillConfig. Throws with a status
 * property so route handlers can forward the correct HTTP status code.
 */
async function resolveSkillsDirForAgent(db: Db, agentId: string): Promise<string> {
  const agent = await agentService(db).getById(agentId);
  if (!agent) throw Object.assign(new Error("Agent not found"), { status: 404 });
  const config = resolveAdapterSkillConfig(agent.adapterType);
  if (!config.supportsInstall || !config.skillDir) {
    throw Object.assign(
      new Error(config.unsupportedReason ?? "Adapter does not support skill install"),
      { status: 422 },
    );
  }
  return config.skillDir.replace(/^~/, os.homedir());
}

/**
 * REST routes for the skill registry.
 *
 * Accepts a db param for agent lookup (to resolve adapter-based skill directories).
 * All route handlers assert `board` access before delegating to skillRegistryService.
 */
export function skillRegistryRoutes(db: Db): Router {
  const router = Router();
  const svc = skillRegistryService();

  // List all skills (soft-deleted excluded by default)
  router.get("/skill-registry/skills", async (req, res) => {
    assertBoard(req);
    const includeRemoved = req.query.includeRemoved === "true";
    const list = await svc.list({ includeRemoved });
    res.json(list);
  });

  // Get versions for a skill — must be registered before the single-skill route
  // to avoid /:id matching "versions" as the id segment
  router.get("/skill-registry/skills/:sourceId/:slug/versions", async (req, res) => {
    assertBoard(req);
    const skillId = `${req.params.sourceId}/${req.params.slug}`;
    const versions = await svc.getVersions(skillId);
    res.json(versions);
  });

  // Install skill to agent directory — resolves path from agentId server-side
  router.post("/skill-registry/skills/:sourceId/:slug/install", async (req, res) => {
    assertBoard(req);
    const skillId = `${req.params.sourceId}/${req.params.slug}`;
    const { agentId } = req.body as { agentId: string };
    if (!agentId) return res.status(400).json({ error: "agentId required" });
    try {
      const agentSkillsDir = await resolveSkillsDirForAgent(db, agentId);
      const result = await svc.install(skillId, agentSkillsDir);
      res.json(result);
    } catch (err: any) {
      const status = err.status ?? 500;
      return res.status(status).json({ error: err.message });
    }
  });

  // Rollback to a specific version — resolves path from agentId server-side
  router.post("/skill-registry/skills/:sourceId/:slug/rollback", async (req, res) => {
    assertBoard(req);
    const skillId = `${req.params.sourceId}/${req.params.slug}`;
    const { versionId, agentId } = req.body as { versionId: string; agentId: string };
    if (!versionId || !agentId) {
      return res.status(400).json({ error: "versionId and agentId required" });
    }
    try {
      const agentSkillsDir = await resolveSkillsDirForAgent(db, agentId);
      await svc.rollback(skillId, versionId, agentSkillsDir);
      res.json({ ok: true });
    } catch (err: any) {
      const status = err.status ?? 500;
      return res.status(status).json({ error: err.message });
    }
  });

  // Soft-delete a skill (also removes files from disk).
  // agentId is passed as a query param (DELETE semantics — no body).
  // Returns 403 if the skill is a native skill (source === 'native').
  router.delete("/skill-registry/skills/:sourceId/:slug", async (req, res) => {
    assertBoard(req);
    const skillId = `${req.params.sourceId}/${req.params.slug}`;
    const agentId = req.query.agentId as string | undefined;
    if (!agentId) return res.status(400).json({ error: "agentId required" });

    // Check native skill protection
    const registryDb = await getSkillRegistryDb();
    const agentSkillRows = await registryDb
      .select()
      .from(agentSkills)
      .where(and(eq(agentSkills.skillId, skillId), eq(agentSkills.agentId, agentId)));
    const agentSkillRow = agentSkillRows[0];
    if (agentSkillRow?.source === "native") {
      return res.status(403).json({ error: "Cannot remove native skills" });
    }

    try {
      const agentSkillsDir = await resolveSkillsDirForAgent(db, agentId);
      await svc.uninstall(skillId, agentSkillsDir);
      res.json({ ok: true });
    } catch (err: any) {
      const status = err.status ?? 500;
      return res.status(status).json({ error: err.message });
    }
  });

  // Submit a personal rating for a skill
  router.post("/skill-registry/skills/:sourceId/:slug/ratings", async (req, res) => {
    assertBoard(req);
    const skillId = `${req.params.sourceId}/${req.params.slug}`;
    const { stars, versionId, note } = req.body as { stars: number; versionId?: string; note?: string };
    if (typeof stars !== "number" || stars < 1 || stars > 5) {
      return res.status(400).json({ error: "stars must be a number between 1 and 5" });
    }
    const ratingSvc = skillRatingService();
    await ratingSvc.rate({ skillId, versionId: versionId ?? null, stars, note: note ?? null });
    res.json({ ok: true });
  });

  // Get personal ratings for a skill
  router.get("/skill-registry/skills/:sourceId/:slug/ratings", async (req, res) => {
    assertBoard(req);
    const skillId = `${req.params.sourceId}/${req.params.slug}`;
    const ratingSvc = skillRatingService();
    const ratings = await ratingSvc.getRatings(skillId);
    res.json(ratings);
  });

  // Get a single skill by id
  router.get("/skill-registry/skills/:sourceId/:slug", async (req, res) => {
    assertBoard(req);
    const skillId = `${req.params.sourceId}/${req.params.slug}`;
    const skill = await svc.getById(skillId);
    if (!skill) return res.status(404).json({ error: "Skill not found" });
    res.json(skill);
  });

  // Trigger fetch from all configured sources
  router.post("/skill-registry/fetch", async (req, res) => {
    assertBoard(req);
    const result = await svc.fetchAll();
    res.json(result);
  });

  return router;
}
