import { Prisma } from "@capakraken/db";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { controllerProcedure, createTRPCRouter } from "../trpc.js";
import {
  type EntityKey,
  ReportTemplateConfigSchema,
  validateReportInput,
} from "./report-query-config.js";
import { reportQueryProcedures } from "./report-query-engine.js";

const ReportTemplateEntity = {
  RESOURCE: "RESOURCE",
  PROJECT: "PROJECT",
  ASSIGNMENT: "ASSIGNMENT",
  RESOURCE_MONTH: "RESOURCE_MONTH",
} as const;

type ReportTemplateEntity = (typeof ReportTemplateEntity)[keyof typeof ReportTemplateEntity];

type ReportTemplateRecord = {
  id: string;
  name: string;
  description: string | null;
  entity: ReportTemplateEntity;
  config: unknown;
  isShared: boolean;
  ownerId: string;
  updatedAt: Date;
};

function getReportTemplateDelegate(db: unknown) {
  return (db as {
    reportTemplate: {
      findMany: (args: unknown) => Promise<ReportTemplateRecord[]>;
      findUnique: (args: unknown) => Promise<{ ownerId: string } | null>;
      update: (args: unknown) => Promise<{ id: string; updatedAt: Date }>;
      upsert: (args: unknown) => Promise<{ id: string; updatedAt: Date }>;
      delete: (args: unknown) => Promise<unknown>;
    };
  }).reportTemplate;
}

export const reportRouter = createTRPCRouter({
  ...reportQueryProcedures,

  listTemplates: controllerProcedure.query(async ({ ctx }) => {
    const reportTemplate = getReportTemplateDelegate(ctx.db);
    const templates = await reportTemplate.findMany({
      where: {
        OR: [
          { ownerId: ctx.dbUser!.id },
          { isShared: true },
        ],
      },
      orderBy: [{ name: "asc" }],
      select: {
        id: true,
        name: true,
        description: true,
        entity: true,
        config: true,
        isShared: true,
        ownerId: true,
        updatedAt: true,
      },
    });

    return templates.map((template: ReportTemplateRecord) => ({
      id: template.id,
      name: template.name,
      description: template.description,
      entity: fromTemplateEntity(template.entity),
      config: ReportTemplateConfigSchema.parse(template.config),
      isShared: template.isShared,
      isOwner: template.ownerId === ctx.dbUser!.id,
      updatedAt: template.updatedAt,
    }));
  }),

  saveTemplate: controllerProcedure
    .input(z.object({
      id: z.string().optional(),
      name: z.string().trim().min(1).max(120),
      description: z.string().trim().max(500).optional(),
      isShared: z.boolean().default(false),
      config: ReportTemplateConfigSchema,
    }))
    .mutation(async ({ ctx, input }) => {
      validateReportInput(input.config);
      const reportTemplate = getReportTemplateDelegate(ctx.db);
      const payload = input.config as unknown as Prisma.InputJsonValue;
      const entity = toTemplateEntity(input.config.entity);

      if (input.id) {
        const existing = await reportTemplate.findUnique({
          where: { id: input.id },
          select: { ownerId: true },
        });

        if (!existing || existing.ownerId !== ctx.dbUser!.id) {
          throw new TRPCError({ code: "FORBIDDEN", message: "Template cannot be updated" });
        }

        return reportTemplate.update({
          where: { id: input.id },
          data: {
            name: input.name,
            description: input.description,
            entity,
            config: payload,
            isShared: input.isShared,
          },
          select: { id: true, updatedAt: true },
        });
      }

      return reportTemplate.upsert({
        where: {
          ownerId_name: {
            ownerId: ctx.dbUser!.id,
            name: input.name,
          },
        },
        update: {
          description: input.description,
          entity,
          config: payload,
          isShared: input.isShared,
        },
        create: {
          ownerId: ctx.dbUser!.id,
          name: input.name,
          description: input.description,
          entity,
          config: payload,
          isShared: input.isShared,
        },
        select: { id: true, updatedAt: true },
      });
    }),

  deleteTemplate: controllerProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const reportTemplate = getReportTemplateDelegate(ctx.db);
      const existing = await reportTemplate.findUnique({
        where: { id: input.id },
        select: { ownerId: true },
      });

      if (!existing || existing.ownerId !== ctx.dbUser!.id) {
        throw new TRPCError({ code: "FORBIDDEN", message: "Template cannot be deleted" });
      }

      await reportTemplate.delete({ where: { id: input.id } });
      return { ok: true };
    }),
});

function toTemplateEntity(entity: EntityKey): ReportTemplateEntity {
  switch (entity) {
    case "resource":
      return ReportTemplateEntity.RESOURCE;
    case "project":
      return ReportTemplateEntity.PROJECT;
    case "assignment":
      return ReportTemplateEntity.ASSIGNMENT;
    case "resource_month":
      return ReportTemplateEntity.RESOURCE_MONTH;
    default:
      throw new TRPCError({ code: "BAD_REQUEST", message: `Unknown entity: ${entity}` });
  }
}

function fromTemplateEntity(entity: ReportTemplateEntity): EntityKey {
  switch (entity) {
    case ReportTemplateEntity.RESOURCE:
      return "resource";
    case ReportTemplateEntity.PROJECT:
      return "project";
    case ReportTemplateEntity.ASSIGNMENT:
      return "assignment";
    case ReportTemplateEntity.RESOURCE_MONTH:
      return "resource_month";
    default:
      throw new TRPCError({ code: "BAD_REQUEST", message: `Unknown entity: ${entity}` });
  }
}
