import { describe, expect, it, vi } from "vitest";
import { SystemRole } from "@capakraken/shared";
import { executeTool, type ToolContext } from "../router/assistant-tools.js";

function createToolContext(
  db: Record<string, unknown>,
  permissions: string[] = [],
  userRole: SystemRole = SystemRole.ADMIN,
): ToolContext {
  return {
    db: db as ToolContext["db"],
    userId: "user_1",
    userRole,
    permissions: new Set(permissions) as ToolContext["permissions"],
    session: {
      user: { email: "assistant@example.com", name: "Assistant User", image: null },
      expires: "2026-03-29T00:00:00.000Z",
    },
    dbUser: {
      id: "user_1",
      systemRole: userRole,
      permissionOverrides: null,
    },
    roleDefaults: null,
  };
}

describe("assistant country tools", () => {
  it("lists countries with schedule rules, active state, and metro cities", async () => {
    const findMany = vi.fn().mockResolvedValue([
      {
        id: "country_de",
        code: "DE",
        name: "Deutschland",
        dailyWorkingHours: 8,
        scheduleRules: null,
        isActive: true,
        metroCities: [{ id: "city_muc", name: "Munich" }],
      },
      {
        id: "country_es",
        code: "ES",
        name: "Spain",
        dailyWorkingHours: 8,
        scheduleRules: null,
        isActive: true,
        metroCities: [{ id: "city_mad", name: "Madrid" }],
      },
    ]);
    const ctx = createToolContext({
      country: {
        findMany,
      },
    });

    const result = await executeTool(
      "list_countries",
      JSON.stringify({ search: "deu" }),
      ctx,
    );

    const parsed = JSON.parse(result.content) as {
      count: number;
      countries: Array<{
        code: string;
        isActive: boolean;
        metroCities: Array<{ id: string; name: string }>;
        cities: string[];
      }>;
    };

    expect(findMany).toHaveBeenCalledWith({
      where: { isActive: true },
      include: { metroCities: { orderBy: { name: "asc" } } },
      orderBy: { name: "asc" },
    });
    expect(parsed.count).toBe(1);
    expect(parsed.countries[0]).toMatchObject({
      code: "DE",
      isActive: true,
      cities: ["Munich"],
      metroCities: [{ id: "city_muc", name: "Munich" }],
    });
  });

  it("gets a country by code and exposes schedule details and resource count", async () => {
    const ctx = createToolContext({
      country: {
        findUnique: vi.fn().mockResolvedValue(null),
        findFirst: vi
          .fn()
          .mockResolvedValueOnce({
            id: "country_es",
            code: "ES",
            name: "Spain",
            dailyWorkingHours: 8,
            scheduleRules: {
              type: "spain",
              fridayHours: 6.5,
              summerPeriod: { from: "07-01", to: "09-15" },
              summerHours: 6.5,
              regularHours: 9,
            },
            isActive: true,
            metroCities: [{ id: "city_mad", name: "Madrid" }],
            _count: { resources: 4 },
          }),
      },
    });

    const result = await executeTool(
      "get_country",
      JSON.stringify({ identifier: "ES" }),
      ctx,
    );

    const parsed = JSON.parse(result.content) as {
      code: string;
      resourceCount: number | null;
      scheduleRules: { type: string };
      metroCities: Array<{ name: string }>;
    };

    expect(parsed).toMatchObject({
      code: "ES",
      resourceCount: 4,
      scheduleRules: { type: "spain" },
      metroCities: [{ name: "Madrid" }],
    });
  });

  it("returns a stable error when a country cannot be resolved", async () => {
    const ctx = createToolContext({
      country: {
        findUnique: vi.fn().mockResolvedValue(null),
        findFirst: vi.fn().mockResolvedValue(null),
      },
    });

    const result = await executeTool(
      "get_country",
      JSON.stringify({ identifier: "Atlantis" }),
      ctx,
    );

    expect(JSON.parse(result.content)).toEqual({
      error: "Country not found with the given criteria.",
    });
  });

  it("creates a country for admin users and returns an invalidation action", async () => {
    const ctx = createToolContext({
      country: {
        findUnique: vi.fn().mockResolvedValue(null),
        create: vi.fn().mockResolvedValue({
          id: "country_es",
          code: "ES",
          name: "Spain",
          dailyWorkingHours: 8,
          scheduleRules: null,
          isActive: true,
          metroCities: [],
          _count: { resources: 0 },
        }),
      },
    });

    const result = await executeTool(
      "create_country",
      JSON.stringify({ code: "ES", name: "Spain", dailyWorkingHours: 8 }),
      ctx,
    );

    expect(result.action).toEqual({
      type: "invalidate",
      scope: ["country", "resource", "holidayCalendar", "vacation"],
    });
    expect(result.data).toMatchObject({
      success: true,
      country: { code: "ES", name: "Spain" },
    });
  });

  it("returns a stable error when creating a country with a duplicate code", async () => {
    const ctx = createToolContext({
      country: {
        findUnique: vi.fn().mockResolvedValue({
          id: "country_es_existing",
          code: "ES",
          name: "Existing Spain",
        }),
      },
    });

    const result = await executeTool(
      "create_country",
      JSON.stringify({ code: "ES", name: "Spain", dailyWorkingHours: 8 }),
      ctx,
    );

    expect(JSON.parse(result.content)).toEqual({
      error: "A country with this code already exists.",
    });
  });

  it("returns a stable error when updating a missing country", async () => {
    const ctx = createToolContext({
      country: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
    });

    const result = await executeTool(
      "update_country",
      JSON.stringify({ id: "country_missing", data: { name: "Atlantis" } }),
      ctx,
    );

    expect(JSON.parse(result.content)).toEqual({
      error: "Country not found with the given criteria.",
    });
  });

  it("refuses country mutations for non-admin users", async () => {
    const ctx = createToolContext({ country: {} }, [], SystemRole.MANAGER);

    const result = await executeTool(
      "create_country",
      JSON.stringify({ code: "ES", name: "Spain" }),
      ctx,
    );

    expect(JSON.parse(result.content)).toEqual({
      error: "Admin role required to perform this action.",
    });
  });

  it("deletes metro cities only when no resources are assigned", async () => {
    const ctx = createToolContext({
      metroCity: {
        findUnique: vi.fn().mockResolvedValue({
          id: "city_ham",
          name: "Hamburg",
          _count: { resources: 0 },
        }),
        delete: vi.fn().mockResolvedValue(undefined),
      },
    });

    const result = await executeTool(
      "delete_metro_city",
      JSON.stringify({ id: "city_ham" }),
      ctx,
    );

    expect(result.action).toEqual({
      type: "invalidate",
      scope: ["country", "resource", "holidayCalendar", "vacation"],
    });
    expect(result.data).toMatchObject({
      success: true,
      message: "Deleted metro city: Hamburg",
    });
  });

  it("returns a stable error when updating a missing metro city", async () => {
    const ctx = createToolContext({
      metroCity: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
    });

    const result = await executeTool(
      "update_metro_city",
      JSON.stringify({ id: "city_missing", data: { name: "Hamburg-Mitte" } }),
      ctx,
    );

    expect(JSON.parse(result.content)).toEqual({
      error: "Metro city not found with the given criteria.",
    });
  });

  it("returns a stable error when deleting a metro city that is still assigned", async () => {
    const ctx = createToolContext({
      metroCity: {
        findUnique: vi.fn().mockResolvedValue({
          id: "city_ham",
          name: "Hamburg",
          _count: { resources: 3 },
        }),
      },
    });

    const result = await executeTool(
      "delete_metro_city",
      JSON.stringify({ id: "city_ham" }),
      ctx,
    );

    expect(JSON.parse(result.content)).toEqual({
      error: "Metro city cannot be deleted while it is still assigned to resources.",
    });
  });
});
