import { Prisma } from "@capakraken/db";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import type { TRPCContext } from "../trpc.js";
import {
  buildResourceMonthTemplateCompleteness,
  type ResourceMonthTemplateCompleteness,
} from "./report-blueprints-support.js";
import {
  type EntityKey,
  ReportTemplateConfigSchema,
  validateReportInput,
} from "./report-query-config.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;
};

type ReportTemplateContext = Pick<TRPCContext, "db" | "dbUser">;

export const SaveReportTemplateInputSchema = 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,
});

export const DeleteReportTemplateInputSchema = z.object({
  id: z.string(),
});

export async function listReportTemplates(ctx: ReportTemplateContext) {
  const ownerId = ctx.dbUser!.id;
  const templates = await ctx.db.reportTemplate.findMany({
    where: {
      OR: [
        { ownerId },
        { 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) => mapTemplateRecord(template, ownerId));
}

export async function saveReportTemplate(
  ctx: ReportTemplateContext,
  input: z.infer<typeof SaveReportTemplateInputSchema>,
) {
  const ownerId = ctx.dbUser!.id;
  validateReportInput(input.config);
  const payload = input.config as unknown as Prisma.InputJsonValue;
  const entity = toTemplateEntity(input.config.entity);

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

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

    try {
      return await ctx.db.reportTemplate.update({
        where: { id: input.id },
        data: buildTemplateUpdateData(input, entity, payload),
        select: { id: true, updatedAt: true },
      });
    } catch (error) {
      throw mapTemplateWriteError(error, input.name);
    }
  }

  try {
    return await ctx.db.reportTemplate.upsert({
      where: {
        ownerId_name: {
          ownerId,
          name: input.name,
        },
      },
      update: buildTemplateUpsertUpdateData(input, entity, payload),
      create: buildTemplateCreateData(ownerId, input, entity, payload),
      select: { id: true, updatedAt: true },
    });
  } catch (error) {
    throw mapTemplateWriteError(error, input.name);
  }
}

export async function deleteReportTemplate(
  ctx: ReportTemplateContext,
  input: z.infer<typeof DeleteReportTemplateInputSchema>,
) {
  const ownerId = ctx.dbUser!.id;
  const existing = await ctx.db.reportTemplate.findUnique({
    where: { id: input.id },
    select: { ownerId: true },
  });

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

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

function normalizeTemplateDescription(description: string | undefined): string | null | undefined {
  if (description === undefined) {
    return undefined;
  }
  return description.length > 0 ? description : null;
}

function mapTemplateWriteError(error: unknown, templateName: string): TRPCError {
  if (
    typeof error === "object"
    && error !== null
    && "code" in error
    && error.code === "P2002"
  ) {
    return new TRPCError({
      code: "CONFLICT",
      message: `A report template named "${templateName}" already exists`,
    });
  }

  if (error instanceof TRPCError) {
    return error;
  }

  return new TRPCError({
    code: "INTERNAL_SERVER_ERROR",
    message: "Failed to save report template",
    cause: error,
  });
}

function buildTemplateCreateData(
  ownerId: string,
  input: z.infer<typeof SaveReportTemplateInputSchema>,
  entity: ReportTemplateEntity,
  config: Prisma.InputJsonValue,
) : Prisma.ReportTemplateUncheckedCreateInput {
  const description = normalizeTemplateDescription(input.description);
  const data: Prisma.ReportTemplateUncheckedCreateInput = {
    ownerId,
    name: input.name,
    entity,
    config,
    isShared: input.isShared,
  };

  if (description !== undefined) {
    data.description = description;
  }

  return data;
}

function buildTemplateUpdateData(
  input: z.infer<typeof SaveReportTemplateInputSchema>,
  entity: ReportTemplateEntity,
  config: Prisma.InputJsonValue,
): Prisma.ReportTemplateUncheckedUpdateInput {
  const description = normalizeTemplateDescription(input.description);
  const data: Prisma.ReportTemplateUncheckedUpdateInput = {
    name: input.name,
    entity,
    config,
    isShared: input.isShared,
  };

  if (description !== undefined) {
    data.description = description;
  }

  return data;
}

function buildTemplateUpsertUpdateData(
  input: z.infer<typeof SaveReportTemplateInputSchema>,
  entity: ReportTemplateEntity,
  config: Prisma.InputJsonValue,
): Prisma.ReportTemplateUncheckedUpdateInput {
  const description = normalizeTemplateDescription(input.description);
  const data: Prisma.ReportTemplateUncheckedUpdateInput = {
    entity,
    config,
    isShared: input.isShared,
  };

  if (description !== undefined) {
    data.description = description;
  }

  return data;
}

function mapTemplateRecord(template: ReportTemplateRecord, ownerId: string) {
  const config = ReportTemplateConfigSchema.parse(template.config);
  return {
    id: template.id,
    name: template.name,
    description: template.description,
    entity: fromTemplateEntity(template.entity),
    config,
    isShared: template.isShared,
    isOwner: template.ownerId === ownerId,
    completeness: getTemplateCompleteness(config),
    updatedAt: template.updatedAt,
  };
}

function getTemplateCompleteness(
  config: z.infer<typeof ReportTemplateConfigSchema>,
): ResourceMonthTemplateCompleteness | null {
  if (config.entity !== "resource_month") {
    return null;
  }

  return buildResourceMonthTemplateCompleteness(config.columns);
}

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}` });
  }
}
