import { DEFAULT_OPENAI_MODEL } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import { beforeEach, describe, expect, it, vi } from "vitest";

const aiCompletionCreate = vi.fn();

vi.mock("../ai-client.js", async (importOriginal) => {
  const actual = await importOriginal<typeof import("../ai-client.js")>();
  return {
    ...actual,
    createAiClient: vi.fn(() => ({
      chat: {
        completions: {
          create: aiCompletionCreate,
        },
      },
    })),
    isAiConfigured: vi.fn((settings: unknown) => settings != null),
    loggedAiCall: vi.fn(async (_provider, _model, _promptLength, fn) => fn()),
    parseAiError: vi.fn((error: unknown) => error instanceof Error ? error.message : String(error)),
  };
});

import { createAiClient, isAiConfigured, loggedAiCall } from "../ai-client.js";
import {
  countBusinessDays,
  generateProjectNarrative,
  getCachedNarrative,
} from "../router/insights-procedure-support.js";

function createContext(db: Record<string, unknown>) {
  return { db: db as never };
}

describe("insights procedure support", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    aiCompletionCreate.mockResolvedValue({
      choices: [{ message: { content: "Project is stable but needs one more staffed role." } }],
    });
  });

  it("counts only weekdays between two dates", () => {
    expect(
      countBusinessDays(
        new Date("2026-03-27T00:00:00.000Z"),
        new Date("2026-03-31T00:00:00.000Z"),
      ),
    ).toBe(3);
  });

  it("generates and stores a project narrative", async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date("2026-03-31T10:15:00.000Z"));

    const projectFindUnique = vi.fn().mockResolvedValue({
      id: "project_1",
      name: "Apollo",
      shortCode: "APO",
      status: "ACTIVE",
      startDate: new Date("2026-03-03T00:00:00.000Z"),
      endDate: new Date("2026-04-30T00:00:00.000Z"),
      budgetCents: 200_000_00,
      dynamicFields: { existingFlag: true },
      demandRequirements: [
        {
          id: "dem_1",
          role: "Developer",
          headcount: 2,
          hoursPerDay: 8,
          startDate: new Date("2026-03-10T00:00:00.000Z"),
          endDate: new Date("2026-04-10T00:00:00.000Z"),
          status: "OPEN",
          _count: { assignments: 1 },
        },
      ],
      assignments: [
        {
          id: "asn_1",
          role: "Developer",
          hoursPerDay: 8,
          startDate: new Date("2026-03-10T00:00:00.000Z"),
          endDate: new Date("2026-04-15T00:00:00.000Z"),
          status: "ACTIVE",
          dailyCostCents: 50_000,
          resource: { displayName: "Carol Danvers" },
        },
      ],
    });
    const projectUpdate = vi.fn().mockResolvedValue({ id: "project_1" });

    try {
      const result = await generateProjectNarrative(
        createContext({
          project: {
            findUnique: projectFindUnique,
            update: projectUpdate,
          },
          systemSettings: {
            findUnique: vi.fn().mockResolvedValue({
              id: "singleton",
              aiProvider: "openai",
              azureOpenAiDeployment: DEFAULT_OPENAI_MODEL,
              aiMaxCompletionTokens: 220,
              aiTemperature: 0.4,
            }),
          },
        }),
        { projectId: "project_1" },
      );

      expect(isAiConfigured).toHaveBeenCalledWith(
        expect.objectContaining({ azureOpenAiDeployment: DEFAULT_OPENAI_MODEL }),
      );
      expect(createAiClient).toHaveBeenCalledWith(
        expect.objectContaining({ azureOpenAiDeployment: DEFAULT_OPENAI_MODEL }),
      );
      expect(loggedAiCall).toHaveBeenCalledOnce();
      expect(aiCompletionCreate).toHaveBeenCalledWith(
        expect.objectContaining({
          model: DEFAULT_OPENAI_MODEL,
          max_completion_tokens: 220,
          temperature: 0.4,
          messages: expect.arrayContaining([
            expect.objectContaining({ role: "system" }),
            expect.objectContaining({
              role: "user",
              content: expect.stringContaining("Staffing: 1/2 positions filled (50%)"),
            }),
          ]),
        }),
      );
      expect(projectUpdate).toHaveBeenCalledWith({
        where: { id: "project_1" },
        data: {
          dynamicFields: {
            existingFlag: true,
            aiNarrative: "Project is stable but needs one more staffed role.",
            aiNarrativeGeneratedAt: "2026-03-31T10:15:00.000Z",
          },
        },
      });
      expect(result).toEqual({
        narrative: "Project is stable but needs one more staffed role.",
        generatedAt: "2026-03-31T10:15:00.000Z",
      });
    } finally {
      vi.useRealTimers();
    }
  });

  it("fails when AI settings are missing", async () => {
    await expect(
      generateProjectNarrative(
        createContext({
          project: {
            findUnique: vi.fn().mockResolvedValue({
              id: "project_1",
              name: "Apollo",
              shortCode: "APO",
              status: "ACTIVE",
              startDate: new Date("2026-03-03T00:00:00.000Z"),
              endDate: new Date("2026-04-30T00:00:00.000Z"),
              budgetCents: 200_000_00,
              dynamicFields: null,
              demandRequirements: [],
              assignments: [],
            }),
            update: vi.fn(),
          },
          systemSettings: {
            findUnique: vi.fn().mockResolvedValue(null),
          },
        }),
        { projectId: "project_1" },
      ),
    ).rejects.toEqual(
      expect.objectContaining<Partial<TRPCError>>({
        code: "PRECONDITION_FAILED",
        message: "AI is not configured. Please set credentials in Admin → Settings.",
      }),
    );

    expect(createAiClient).not.toHaveBeenCalled();
    expect(loggedAiCall).not.toHaveBeenCalled();
  });

  it("fails when AI configuration is incomplete", async () => {
    vi.mocked(isAiConfigured).mockReturnValue(false);

    await expect(
      generateProjectNarrative(
        createContext({
          project: {
            findUnique: vi.fn().mockResolvedValue({
              id: "project_1",
              name: "Apollo",
              shortCode: "APO",
              status: "ACTIVE",
              startDate: new Date("2026-03-03T00:00:00.000Z"),
              endDate: new Date("2026-04-30T00:00:00.000Z"),
              budgetCents: 200_000_00,
              dynamicFields: null,
              demandRequirements: [],
              assignments: [],
            }),
            update: vi.fn(),
          },
          systemSettings: {
            findUnique: vi.fn().mockResolvedValue({
              id: "singleton",
              aiProvider: "openai",
              azureOpenAiDeployment: null,
            }),
          },
        }),
        { projectId: "project_1" },
      ),
    ).rejects.toEqual(
      expect.objectContaining<Partial<TRPCError>>({
        code: "PRECONDITION_FAILED",
        message: "AI is not configured. Please set credentials in Admin → Settings.",
      }),
    );
    expect(createAiClient).not.toHaveBeenCalled();
    expect(aiCompletionCreate).not.toHaveBeenCalled();
  });

  it("returns the cached narrative for a project", async () => {
    const result = await getCachedNarrative(
      createContext({
        project: {
          findUnique: vi.fn().mockResolvedValue({
            dynamicFields: {
              aiNarrative: "Cached summary",
              aiNarrativeGeneratedAt: "2026-03-30T08:00:00.000Z",
            },
          }),
        },
      }),
      { projectId: "project_1" },
    );

    expect(result).toEqual({
      narrative: "Cached summary",
      generatedAt: "2026-03-30T08:00:00.000Z",
    });
  });

  it("fails when the project does not exist for the cached narrative lookup", async () => {
    await expect(
      getCachedNarrative(
        createContext({
          project: {
            findUnique: vi.fn().mockResolvedValue(null),
          },
        }),
        { projectId: "missing" },
      ),
    ).rejects.toEqual(
      expect.objectContaining<Partial<TRPCError>>({
        code: "NOT_FOUND",
        message: "Project not found",
      }),
    );
  });
});
