import { describe, expect, it, vi } from "vitest";
import { buildResourceMonthTemplateCompleteness } from "../router/report-blueprints-support.js";
import {
  DeleteReportTemplateInputSchema,
  deleteReportTemplate,
  listReportTemplates,
  SaveReportTemplateInputSchema,
  saveReportTemplate,
} from "../router/report-template-procedure-support.js";

function createContext(reportTemplate: Record<string, unknown>) {
  return {
    db: {
      reportTemplate,
    },
    dbUser: {
      id: "user_controller",
      systemRole: "CONTROLLER",
      permissionOverrides: null,
    },
  } as const;
}

describe("report template procedure support", () => {
  it("reuses the shared resource month completeness basis", () => {
    expect(buildResourceMonthTemplateCompleteness([
      "monthKey",
      "displayName",
      "countryName",
      "federalState",
      "metroCityName",
      "monthlyPublicHolidayCount",
      "monthlyPublicHolidayHoursDeduction",
      "monthlyAbsenceDayEquivalent",
      "monthlyAbsenceHoursDeduction",
      "monthlySahHours",
      "monthlyTargetHours",
      "monthlyActualBookedHours",
      "monthlyUnassignedHours",
    ])).toMatchObject({
      scope: "resource_month",
      isAuditReady: true,
      isRecommendedComplete: false,
      minimumAuditColumnCount: 13,
      selectedMinimumAuditColumnCount: 13,
      missingMinimumAuditColumns: [],
      missingRecommendedColumns: expect.arrayContaining([
        "eid",
        "chapter",
        "monthlyExpectedBookedHours",
      ]),
    });
  });

  it("lists shared and owned templates with parsed config and ownership flags", async () => {
    const updatedAt = new Date("2026-03-31T10:00:00.000Z");
    const ctx = createContext({
      findMany: vi.fn().mockResolvedValue([
        {
          id: "tpl_1",
          name: "Owned resource month",
          description: "Monthly default",
          entity: "RESOURCE_MONTH",
          config: {
            entity: "resource_month",
            columns: ["displayName", "monthlySahHours"],
            filters: [],
            periodMonth: "2026-03",
          },
          isShared: false,
          ownerId: "user_controller",
          updatedAt,
        },
        {
          id: "tpl_2",
          name: "Shared project",
          description: null,
          entity: "PROJECT",
          config: {
            entity: "project",
            columns: ["name"],
            filters: [],
          },
          isShared: true,
          ownerId: "user_other",
          updatedAt,
        },
      ]),
    });

    const result = await listReportTemplates(ctx);

    expect(ctx.db.reportTemplate.findMany).toHaveBeenCalledWith({
      where: {
        OR: [
          { ownerId: "user_controller" },
          { isShared: true },
        ],
      },
      orderBy: [{ name: "asc" }],
      select: {
        id: true,
        name: true,
        description: true,
        entity: true,
        config: true,
        isShared: true,
        ownerId: true,
        updatedAt: true,
      },
    });
    expect(result).toEqual([
      expect.objectContaining({
        id: "tpl_1",
        name: "Owned resource month",
        description: "Monthly default",
        entity: "resource_month",
        config: {
          entity: "resource_month",
          columns: ["displayName", "monthlySahHours"],
          filters: [],
          periodMonth: "2026-03",
          sortDir: "asc",
        },
        isShared: false,
        isOwner: true,
        completeness: expect.objectContaining({
          scope: "resource_month",
          isAuditReady: false,
          isRecommendedComplete: false,
          recommendedColumnCount: 26,
          selectedRecommendedColumnCount: 2,
          minimumAuditColumnCount: 13,
          selectedMinimumAuditColumnCount: 2,
          missingRecommendedColumns: expect.arrayContaining([
            "monthKey",
            "monthlyTargetHours",
            "monthlyUnassignedHours",
          ]),
          missingMinimumAuditColumns: expect.arrayContaining([
            "monthKey",
            "countryName",
            "monthlyTargetHours",
          ]),
        }),
        updatedAt,
      }),
      expect.objectContaining({
        id: "tpl_2",
        name: "Shared project",
        description: null,
        entity: "project",
        config: {
          entity: "project",
          columns: ["name"],
          filters: [],
          sortDir: "asc",
        },
        isShared: true,
        isOwner: false,
        completeness: null,
        updatedAt,
      }),
    ]);
  });

  it("marks resource month templates audit-ready once the minimum audit basis is present", async () => {
    const updatedAt = new Date("2026-03-31T10:30:00.000Z");
    const ctx = createContext({
      findMany: vi.fn().mockResolvedValue([
        {
          id: "tpl_audit",
          name: "Audit-ready resource month",
          description: null,
          entity: "RESOURCE_MONTH",
          config: {
            entity: "resource_month",
            columns: [
              "monthKey",
              "displayName",
              "countryName",
              "federalState",
              "metroCityName",
              "monthlyPublicHolidayCount",
              "monthlyPublicHolidayHoursDeduction",
              "monthlyAbsenceDayEquivalent",
              "monthlyAbsenceHoursDeduction",
              "monthlySahHours",
              "monthlyTargetHours",
              "monthlyActualBookedHours",
              "monthlyUnassignedHours",
            ],
            filters: [],
            periodMonth: "2026-03",
          },
          isShared: false,
          ownerId: "user_controller",
          updatedAt,
        },
      ]),
    });

    const [result] = await listReportTemplates(ctx);

    expect(result?.completeness).toMatchObject({
      scope: "resource_month",
      isAuditReady: true,
      isRecommendedComplete: false,
      minimumAuditColumnCount: 13,
      selectedMinimumAuditColumnCount: 13,
      missingMinimumAuditColumns: [],
      missingRecommendedColumns: expect.arrayContaining([
        "eid",
        "chapter",
        "monthlyExpectedBookedHours",
      ]),
    });
  });

  it("upserts new templates under the current owner with converted entity values", async () => {
    const upsert = vi.fn().mockResolvedValue({
      id: "tpl_new",
      updatedAt: new Date("2026-03-31T11:00:00.000Z"),
    });
    const ctx = createContext({
      upsert,
    });
    const input = SaveReportTemplateInputSchema.parse({
      name: "Monthly default",
      description: "Team-wide default",
      isShared: true,
      config: {
        entity: "resource_month",
        columns: ["displayName", "monthlyTargetHours"],
        filters: [],
        periodMonth: "2026-03",
      },
    });

    const result = await saveReportTemplate(ctx, input);

    expect(result).toEqual({
      id: "tpl_new",
      updatedAt: new Date("2026-03-31T11:00:00.000Z"),
    });
    expect(upsert).toHaveBeenCalledWith({
      where: {
        ownerId_name: {
          ownerId: "user_controller",
          name: "Monthly default",
        },
      },
      update: {
        description: "Team-wide default",
        entity: "RESOURCE_MONTH",
        config: {
          entity: "resource_month",
          columns: ["displayName", "monthlyTargetHours"],
          filters: [],
          periodMonth: "2026-03",
          sortDir: "asc",
        },
        isShared: true,
      },
      create: {
        ownerId: "user_controller",
        name: "Monthly default",
        description: "Team-wide default",
        entity: "RESOURCE_MONTH",
        config: {
          entity: "resource_month",
          columns: ["displayName", "monthlyTargetHours"],
          filters: [],
          periodMonth: "2026-03",
          sortDir: "asc",
        },
        isShared: true,
      },
      select: { id: true, updatedAt: true },
    });
  });

  it("updates owned templates and rejects updates for foreign templates", async () => {
    const findUnique = vi.fn()
      .mockResolvedValueOnce({ ownerId: "user_controller" })
      .mockResolvedValueOnce({ ownerId: "user_other" });
    const update = vi.fn().mockResolvedValue({
      id: "tpl_1",
      updatedAt: new Date("2026-03-31T12:00:00.000Z"),
    });
    const ctx = createContext({
      findUnique,
      update,
    });

    const ownedResult = await saveReportTemplate(ctx, SaveReportTemplateInputSchema.parse({
      id: "tpl_1",
      name: "Owned template",
      description: "Updated",
      config: {
        entity: "project",
        columns: ["name"],
        filters: [],
      },
    }));

    expect(ownedResult).toEqual({
      id: "tpl_1",
      updatedAt: new Date("2026-03-31T12:00:00.000Z"),
    });
    expect(update).toHaveBeenCalledWith({
      where: { id: "tpl_1" },
      data: {
        name: "Owned template",
        description: "Updated",
        entity: "PROJECT",
        config: {
          entity: "project",
          columns: ["name"],
          filters: [],
          sortDir: "asc",
        },
        isShared: false,
      },
      select: { id: true, updatedAt: true },
    });

    await expect(saveReportTemplate(ctx, SaveReportTemplateInputSchema.parse({
      id: "tpl_2",
      name: "Foreign template",
      config: {
        entity: "resource",
        columns: ["displayName"],
        filters: [],
      },
    }))).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Template cannot be updated",
    });
  });

  it("clears an existing description when the template is saved with a blank description", async () => {
    const findUnique = vi.fn().mockResolvedValue({ ownerId: "user_controller" });
    const update = vi.fn().mockResolvedValue({
      id: "tpl_1",
      updatedAt: new Date("2026-03-31T13:00:00.000Z"),
    });
    const ctx = createContext({
      findUnique,
      update,
    });

    const result = await saveReportTemplate(ctx, SaveReportTemplateInputSchema.parse({
      id: "tpl_1",
      name: "Owned template",
      description: "   ",
      config: {
        entity: "project",
        columns: ["name"],
        filters: [],
      },
    }));

    expect(result).toEqual({
      id: "tpl_1",
      updatedAt: new Date("2026-03-31T13:00:00.000Z"),
    });
    expect(update).toHaveBeenCalledWith({
      where: { id: "tpl_1" },
      data: {
        name: "Owned template",
        description: null,
        entity: "PROJECT",
        config: {
          entity: "project",
          columns: ["name"],
          filters: [],
          sortDir: "asc",
        },
        isShared: false,
      },
      select: { id: true, updatedAt: true },
    });
  });

  it("stores blank descriptions as null for new templates", async () => {
    const upsert = vi.fn().mockResolvedValue({
      id: "tpl_new",
      updatedAt: new Date("2026-03-31T14:00:00.000Z"),
    });
    const ctx = createContext({
      upsert,
    });

    await saveReportTemplate(ctx, SaveReportTemplateInputSchema.parse({
      name: "Monthly default",
      description: "   ",
      isShared: true,
      config: {
        entity: "resource_month",
        columns: ["displayName", "monthlyTargetHours"],
        filters: [],
        periodMonth: "2026-03",
      },
    }));

    expect(upsert).toHaveBeenCalledWith({
      where: {
        ownerId_name: {
          ownerId: "user_controller",
          name: "Monthly default",
        },
      },
      update: {
        description: null,
        entity: "RESOURCE_MONTH",
        config: {
          entity: "resource_month",
          columns: ["displayName", "monthlyTargetHours"],
          filters: [],
          periodMonth: "2026-03",
          sortDir: "asc",
        },
        isShared: true,
      },
      create: {
        ownerId: "user_controller",
        name: "Monthly default",
        description: null,
        entity: "RESOURCE_MONTH",
        config: {
          entity: "resource_month",
          columns: ["displayName", "monthlyTargetHours"],
          filters: [],
          periodMonth: "2026-03",
          sortDir: "asc",
        },
        isShared: true,
      },
      select: { id: true, updatedAt: true },
    });
  });

  it("maps duplicate template names to a conflict error instead of leaking a raw database error", async () => {
    const update = vi.fn().mockRejectedValue({ code: "P2002" });
    const findUnique = vi.fn().mockResolvedValue({ ownerId: "user_controller" });
    const ctx = createContext({
      findUnique,
      update,
    });

    await expect(saveReportTemplate(ctx, SaveReportTemplateInputSchema.parse({
      id: "tpl_1",
      name: "Monthly default",
      config: {
        entity: "project",
        columns: ["name"],
        filters: [],
      },
    }))).rejects.toMatchObject({
      code: "CONFLICT",
      message: 'A report template named "Monthly default" already exists',
    });
  });

  it("deletes only owned templates", async () => {
    const findUnique = vi.fn()
      .mockResolvedValueOnce({ ownerId: "user_controller" })
      .mockResolvedValueOnce({ ownerId: "user_other" });
    const del = vi.fn().mockResolvedValue(undefined);
    const ctx = createContext({
      findUnique,
      delete: del,
    });

    const result = await deleteReportTemplate(ctx, DeleteReportTemplateInputSchema.parse({ id: "tpl_1" }));

    expect(result).toEqual({ ok: true });
    expect(del).toHaveBeenCalledWith({ where: { id: "tpl_1" } });

    await expect(deleteReportTemplate(ctx, DeleteReportTemplateInputSchema.parse({ id: "tpl_2" }))).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Template cannot be deleted",
    });
  });
});
