import { SystemRole } from "@capakraken/shared";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { computationGraphRouter } from "../router/computation-graph.js";
import { createCallerFactory } from "../trpc.js";

const createCaller = createCallerFactory(computationGraphRouter);

type ResourceGraphMeta = {
  countryCode: string | null;
  countryName: string | null;
  federalState: string | null;
  metroCityName: string | null;
  resolvedHolidays: Array<{
    date: string;
    name: string;
    scope: "COUNTRY" | "STATE" | "CITY";
    calendarName: string | null;
  }>;
  factors: {
    baseAvailableHours: number;
    effectiveAvailableHours: number;
    publicHolidayCount: number;
    publicHolidayWorkdayCount: number;
    publicHolidayHoursDeduction: number;
    absenceDayCount: number;
    absenceHoursDeduction: number;
  };
};

type ResourceGraphDetail = {
  resource: { id: string; eid: string; displayName: string };
  availableDomains: string[];
  requestedDomain: string | null;
  totalNodeCount: number;
  totalLinkCount: number;
  selectedNodeCount: number;
  selectedLinkCount: number;
  nodes: Array<{ id: string; domain: string }>;
};

type ProjectGraphDetail = {
  project: { id: string; shortCode: string; name: string };
  availableDomains: string[];
  requestedDomain: string | null;
  totalNodeCount: number;
  selectedNodeCount: number;
  selectedLinkCount: number;
  nodes: Array<{ id: string; domain: string }>;
  links?: Array<{ source: string; target: string }>;
  meta: {
    projectName: string;
    projectCode: string;
  };
};

function createControllerCaller(db: Record<string, unknown>) {
  return createCaller({
    session: {
      user: { email: "controller@example.com", name: "Controller", image: null },
      expires: "2026-03-14T00:00:00.000Z",
    },
    db: db as never,
    dbUser: {
      id: "user_controller",
      systemRole: SystemRole.CONTROLLER,
      permissionOverrides: null,
    },
  });
}

function createDb(resourceFindImpl: ReturnType<typeof vi.fn>) {
  return {
    resource: {
      findUniqueOrThrow: resourceFindImpl,
    },
    assignment: {
      findMany: vi.fn().mockResolvedValue([]),
    },
    vacation: {
      findMany: vi.fn().mockResolvedValue([]),
    },
    holidayCalendar: {
      findMany: vi.fn().mockResolvedValue([]),
    },
    calculationRule: {
      findMany: vi.fn().mockResolvedValue([]),
    },
  };
}

function buildResource(overrides: Record<string, unknown> = {}) {
  return {
    id: "resource_1",
    displayName: "Bruce Banner",
    eid: "bruce.banner",
    fte: 1,
    lcrCents: 5_000,
    chargeabilityTarget: 80,
    countryId: "country_de",
    federalState: "BY",
    metroCityId: null,
    availability: {
      monday: 8,
      tuesday: 8,
      wednesday: 8,
      thursday: 8,
      friday: 8,
      saturday: 0,
      sunday: 0,
    },
    country: {
      id: "country_de",
      code: "DE",
      name: "Deutschland",
      dailyWorkingHours: 8,
      scheduleRules: null,
    },
    metroCity: null,
    managementLevelGroup: {
      id: "mlg_1",
      name: "Senior",
      targetPercentage: 0.8,
    },
    ...overrides,
  };
}

function createProjectDb(projectFindImpl: ReturnType<typeof vi.fn>) {
  return {
    project: {
      findUniqueOrThrow: projectFindImpl,
    },
    estimate: {
      findFirst: vi.fn().mockResolvedValue(null),
    },
    assignment: {
      findMany: vi.fn().mockResolvedValue([
        {
          status: "CONFIRMED",
          dailyCostCents: 4_000,
          startDate: new Date("2026-01-05T00:00:00.000Z"),
          endDate: new Date("2026-01-30T00:00:00.000Z"),
          hoursPerDay: 4,
        },
      ]),
    },
    effortRule: {
      count: vi.fn().mockResolvedValue(0),
    },
    experienceMultiplierRule: {
      count: vi.fn().mockResolvedValue(0),
    },
  };
}

function buildProject(overrides: Record<string, unknown> = {}) {
  return {
    id: "project_1",
    name: "Gelddruckmaschine",
    shortCode: "GDM",
    budgetCents: 100_000,
    winProbability: 75,
    startDate: new Date("2026-01-05T00:00:00.000Z"),
    endDate: new Date("2026-02-28T00:00:00.000Z"),
    ...overrides,
  };
}

describe("computation graph router", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("exposes location context and city-local holidays in the resource graph", async () => {
    const db = createDb(vi.fn().mockResolvedValue(buildResource({
      id: "resource_augsburg",
      metroCityId: "city_augsburg",
      metroCity: { id: "city_augsburg", name: "Augsburg" },
    })));

    const caller = createControllerCaller(db);
    const result = await caller.getResourceData({
      resourceId: "resource_augsburg",
      month: "2026-08",
    });
    const meta = result.meta as ResourceGraphMeta;
    const nodeIds = result.nodes.map((node) => node.id);
    const holidayExamples = result.nodes.find((node) => node.id === "input.holidayExamples");

    expect(new Set(nodeIds).size).toBe(nodeIds.length);
    expect(nodeIds).toEqual(expect.arrayContaining([
      "input.country",
      "input.state",
      "input.city",
      "input.holidayContext",
      "input.holidayExamples",
      "sah.baseHours",
      "sah.publicHolidayHours",
      "sah.absenceHours",
    ]));
    expect(meta).toMatchObject({
      countryCode: "DE",
      countryName: "Deutschland",
      federalState: "BY",
      metroCityName: "Augsburg",
    });
    expect(meta.resolvedHolidays).toEqual(expect.arrayContaining([
      expect.objectContaining({
        date: "2026-08-08",
        name: "Augsburger Friedensfest",
        scope: "CITY",
      }),
    ]));
    expect(meta.factors.publicHolidayCount).toBeGreaterThan(0);
    expect(meta.factors.publicHolidayWorkdayCount).toBe(0);
    expect(holidayExamples?.value).toEqual(expect.stringContaining("Augsburger Friedensfest"));
  });

  it("derives different effective SAH values for Bavaria and Hamburg", async () => {
    const db = createDb(vi.fn()
      .mockResolvedValueOnce(buildResource({
        id: "resource_by",
        federalState: "BY",
        managementLevelGroup: null,
      }))
      .mockResolvedValueOnce(buildResource({
        id: "resource_hh",
        federalState: "HH",
        managementLevelGroup: null,
      })));

    const caller = createControllerCaller(db);
    const bavaria = await caller.getResourceData({
      resourceId: "resource_by",
      month: "2026-01",
    });
    const hamburg = await caller.getResourceData({
      resourceId: "resource_hh",
      month: "2026-01",
    });

    const bavariaMeta = bavaria.meta as ResourceGraphMeta;
    const hamburgMeta = hamburg.meta as ResourceGraphMeta;

    expect(bavariaMeta.federalState).toBe("BY");
    expect(hamburgMeta.federalState).toBe("HH");
    expect(bavariaMeta.factors.baseAvailableHours).toBe(176);
    expect(hamburgMeta.factors.baseAvailableHours).toBe(176);
    expect(bavariaMeta.factors.effectiveAvailableHours).toBe(160);
    expect(hamburgMeta.factors.effectiveAvailableHours).toBe(168);
    expect(bavariaMeta.factors.publicHolidayWorkdayCount).toBe(2);
    expect(hamburgMeta.factors.publicHolidayWorkdayCount).toBe(1);
    expect(bavariaMeta.factors.publicHolidayHoursDeduction).toBe(16);
    expect(hamburgMeta.factors.publicHolidayHoursDeduction).toBe(8);
    expect(bavariaMeta.resolvedHolidays).toEqual(expect.arrayContaining([
      expect.objectContaining({ name: "Heilige Drei Könige", date: "2026-01-06", scope: "STATE" }),
    ]));
    expect(hamburgMeta.resolvedHolidays).not.toEqual(expect.arrayContaining([
      expect.objectContaining({ name: "Heilige Drei Könige", date: "2026-01-06" }),
    ]));
  });

  it("returns a filtered resource detail graph with canonical selection metadata", async () => {
    const db = createDb(vi.fn().mockResolvedValue(buildResource({
      id: "resource_augsburg",
      metroCityId: "city_augsburg",
      metroCity: { id: "city_augsburg", name: "Augsburg" },
    })));

    const caller = createControllerCaller(db);
    const result = await caller.getResourceDataDetail({
      resourceId: "resource_augsburg",
      month: "2026-08",
      domain: "SAH",
    }) as ResourceGraphDetail;

    expect(result.resource).toEqual({
      id: "resource_augsburg",
      eid: "bruce.banner",
      displayName: "Bruce Banner",
    });
    expect(result.availableDomains).toEqual(expect.arrayContaining(["INPUT", "SAH", "ALLOCATION", "CHARGEABILITY"]));
    expect(result.requestedDomain).toBe("SAH");
    expect(result.totalNodeCount).toBeGreaterThan(result.selectedNodeCount);
    expect(result.totalLinkCount).toBeGreaterThan(0);
    expect(result.selectedNodeCount).toBeGreaterThan(0);
    expect(result.selectedLinkCount).toBe(0);
    expect(result.nodes.every((node) => node.domain === "SAH")).toBe(true);
  });

  it("returns a filtered project detail graph with canonical project identity", async () => {
    const db = createProjectDb(vi.fn().mockResolvedValue(buildProject()));

    const caller = createControllerCaller(db);
    const result = await caller.getProjectDataDetail({
      projectId: "project_1",
      domain: "BUDGET",
      includeLinks: true,
    }) as ProjectGraphDetail;

    expect(result.project).toEqual({
      id: "project_1",
      shortCode: "GDM",
      name: "Gelddruckmaschine",
    });
    expect(result.meta).toEqual({
      projectName: "Gelddruckmaschine",
      projectCode: "GDM",
    });
    expect(result.availableDomains).toEqual(expect.arrayContaining(["INPUT", "BUDGET"]));
    expect(result.requestedDomain).toBe("BUDGET");
    expect(result.totalNodeCount).toBeGreaterThan(result.selectedNodeCount);
    expect(result.selectedNodeCount).toBeGreaterThan(0);
    expect(result.selectedLinkCount).toBeGreaterThan(0);
    expect(result.nodes.every((node) => node.domain === "BUDGET")).toBe(true);
    expect(result.links?.length).toBe(result.selectedLinkCount);
  });

  it("returns the project snapshot graph through the base router contract", async () => {
    const db = createProjectDb(vi.fn().mockResolvedValue(buildProject()));

    const caller = createControllerCaller(db);
    const result = await caller.getProjectData({
      projectId: "project_1",
    });

    expect(result.meta).toEqual({
      projectName: "Gelddruckmaschine",
      projectCode: "GDM",
    });
    expect(result.nodes.map((node) => node.id)).toEqual(expect.arrayContaining([
      "input.budgetCents",
      "input.winProbability",
      "budget.allocatedCents",
    ]));
    expect(result.links.length).toBeGreaterThan(0);
  });
});
