import express from "express";
import request from "supertest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { skillRegistryRoutes } from "../routes/skill-registry.js";
import { errorHandler } from "../middleware/index.js";

// ---------------------------------------------------------------------------
// Mock skillRegistryService
// ---------------------------------------------------------------------------

const mockSkillRegistryService = vi.hoisted(() => ({
  list: vi.fn(),
  getById: vi.fn(),
  getVersions: vi.fn(),
  fetchAll: vi.fn(),
  install: vi.fn(),
  rollback: vi.fn(),
  uninstall: vi.fn(),
}));

vi.mock("../services/skill-registry.js", () => ({
  skillRegistryService: () => mockSkillRegistryService,
}));

// ---------------------------------------------------------------------------
// App factory
// ---------------------------------------------------------------------------

function createApp() {
  const app = express();
  app.use(express.json());
  app.use((req, _res, next) => {
    (req as any).actor = {
      type: "board",
      userId: "local-board",
      companyIds: [],
      source: "local_implicit",
      isInstanceAdmin: false,
    };
    next();
  });
  app.use("/api", skillRegistryRoutes());
  app.use(errorHandler);
  return app;
}

// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------

const skill1 = {
  id: "anthropic-official/bash",
  sourceId: "anthropic-official",
  name: "Bash",
  description: "A bash skill",
  activeVersionId: null,
  removedAt: null,
  createdAt: 1000,
  updatedAt: 1000,
};

const version1 = {
  id: "anthropic-official/bash@abc123",
  skillId: "anthropic-official/bash",
  sha: "abc123",
  cacheDir: "/tmp/cache/bash@abc123",
  fetchedAt: 1000,
};

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("skill registry routes", () => {
  let app: ReturnType<typeof createApp>;

  beforeEach(() => {
    vi.clearAllMocks();
    app = createApp();
  });

  // ---- Test 1: GET /api/skill-registry/skills ----

  describe("GET /api/skill-registry/skills", () => {
    it("returns 200 with JSON array of skills", async () => {
      mockSkillRegistryService.list.mockResolvedValue([skill1]);

      const res = await request(app).get("/api/skill-registry/skills");

      expect(res.status).toBe(200);
      expect(res.body).toEqual([skill1]);
      expect(mockSkillRegistryService.list).toHaveBeenCalledWith({ includeRemoved: false });
    });

    it("passes includeRemoved=true when query param set", async () => {
      mockSkillRegistryService.list.mockResolvedValue([skill1]);

      const res = await request(app).get("/api/skill-registry/skills?includeRemoved=true");

      expect(res.status).toBe(200);
      expect(mockSkillRegistryService.list).toHaveBeenCalledWith({ includeRemoved: true });
    });
  });

  // ---- Test 2: GET /api/skill-registry/skills/:sourceId/:slug ----

  describe("GET /api/skill-registry/skills/:sourceId/:slug", () => {
    it("returns 200 with skill object when found", async () => {
      mockSkillRegistryService.getById.mockResolvedValue(skill1);

      const res = await request(app).get("/api/skill-registry/skills/anthropic-official/bash");

      expect(res.status).toBe(200);
      expect(res.body).toEqual(skill1);
      expect(mockSkillRegistryService.getById).toHaveBeenCalledWith("anthropic-official/bash");
    });

    it("returns 404 when skill not found", async () => {
      mockSkillRegistryService.getById.mockResolvedValue(undefined);

      const res = await request(app).get("/api/skill-registry/skills/unknown/skill");

      expect(res.status).toBe(404);
      expect(res.body).toEqual({ error: "Skill not found" });
    });
  });

  // ---- Test 3: GET /api/skill-registry/skills/:sourceId/:slug/versions ----

  describe("GET /api/skill-registry/skills/:sourceId/:slug/versions", () => {
    it("returns 200 with version array", async () => {
      mockSkillRegistryService.getVersions.mockResolvedValue([version1]);

      const res = await request(app).get("/api/skill-registry/skills/anthropic-official/bash/versions");

      expect(res.status).toBe(200);
      expect(res.body).toEqual([version1]);
      expect(mockSkillRegistryService.getVersions).toHaveBeenCalledWith("anthropic-official/bash");
    });
  });

  // ---- Test 4: POST /api/skill-registry/fetch ----

  describe("POST /api/skill-registry/fetch", () => {
    it("returns 200 with { fetched, errors } object", async () => {
      mockSkillRegistryService.fetchAll.mockResolvedValue({ fetched: 3, errors: [] });

      const res = await request(app).post("/api/skill-registry/fetch");

      expect(res.status).toBe(200);
      expect(res.body).toEqual({ fetched: 3, errors: [] });
    });
  });

  // ---- Test 5: POST /api/skill-registry/skills/:sourceId/:slug/install ----

  describe("POST /api/skill-registry/skills/:sourceId/:slug/install", () => {
    it("returns 200 with install result when agentSkillsDir provided", async () => {
      const installResult = {
        type: "installed",
        skillId: "anthropic-official/bash",
        versionId: "anthropic-official/bash@abc123",
        targetDir: "/agent/skills/bash",
      };
      mockSkillRegistryService.install.mockResolvedValue(installResult);

      const res = await request(app)
        .post("/api/skill-registry/skills/anthropic-official/bash/install")
        .send({ agentSkillsDir: "/agent/skills" });

      expect(res.status).toBe(200);
      expect(res.body).toEqual(installResult);
      expect(mockSkillRegistryService.install).toHaveBeenCalledWith(
        "anthropic-official/bash",
        "/agent/skills",
      );
    });

    it("returns 400 when agentSkillsDir is missing", async () => {
      const res = await request(app)
        .post("/api/skill-registry/skills/anthropic-official/bash/install")
        .send({});

      expect(res.status).toBe(400);
      expect(res.body).toEqual({ error: "agentSkillsDir required" });
    });
  });

  // ---- Test 6: POST /api/skill-registry/skills/:sourceId/:slug/rollback ----

  describe("POST /api/skill-registry/skills/:sourceId/:slug/rollback", () => {
    it("returns 200 when versionId and agentSkillsDir provided", async () => {
      mockSkillRegistryService.rollback.mockResolvedValue(undefined);

      const res = await request(app)
        .post("/api/skill-registry/skills/anthropic-official/bash/rollback")
        .send({ versionId: "anthropic-official/bash@abc123", agentSkillsDir: "/agent/skills" });

      expect(res.status).toBe(200);
      expect(res.body).toEqual({ ok: true });
      expect(mockSkillRegistryService.rollback).toHaveBeenCalledWith(
        "anthropic-official/bash",
        "anthropic-official/bash@abc123",
        "/agent/skills",
      );
    });

    it("returns 400 when versionId is missing", async () => {
      const res = await request(app)
        .post("/api/skill-registry/skills/anthropic-official/bash/rollback")
        .send({ agentSkillsDir: "/agent/skills" });

      expect(res.status).toBe(400);
      expect(res.body).toEqual({ error: "versionId and agentSkillsDir required" });
    });

    it("returns 400 when agentSkillsDir is missing", async () => {
      const res = await request(app)
        .post("/api/skill-registry/skills/anthropic-official/bash/rollback")
        .send({ versionId: "anthropic-official/bash@abc123" });

      expect(res.status).toBe(400);
      expect(res.body).toEqual({ error: "versionId and agentSkillsDir required" });
    });
  });

  // ---- Test 7: DELETE /api/skill-registry/skills/:sourceId/:slug ----

  describe("DELETE /api/skill-registry/skills/:sourceId/:slug", () => {
    it("returns 200 after soft-delete", async () => {
      mockSkillRegistryService.uninstall.mockResolvedValue(undefined);

      const res = await request(app).delete("/api/skill-registry/skills/anthropic-official/bash");

      expect(res.status).toBe(200);
      expect(res.body).toEqual({ ok: true });
      expect(mockSkillRegistryService.uninstall).toHaveBeenCalledWith("anthropic-official/bash");
    });
  });
});
