import type { Prisma } from "@capakraken/db";
import {
  aggregateWeeklyByChapter,
  aggregateWeeklyToMonthly,
  distributeHoursToWeeks,
  generateWeekRange,
} from "@capakraken/engine";
import { PermissionKey, GenerateWeeklyPhasingSchema } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { findUniqueOrThrow } from "../db/helpers.js";
import {
  controllerProcedure,
  managerProcedure,
  requirePermission,
} from "../trpc.js";
import { getEstimateById } from "@capakraken/application";

type WeeklyPhasingMeta = {
  startDate: string;
  endDate: string;
  pattern: string;
  weeklyHours: Record<string, number>;
  generatedAt: string;
};

export const estimatePhasingProcedures = {
  generateWeeklyPhasing: managerProcedure
    .input(GenerateWeeklyPhasingSchema)
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_PROJECTS);

      const estimate = await findUniqueOrThrow(
        getEstimateById(
          ctx.db as unknown as Parameters<typeof getEstimateById>[0],
          input.estimateId,
        ),
        "Estimate",
      );

      const workingVersion = estimate.versions.find(
        (version) => version.status === "WORKING",
      );

      if (!workingVersion) {
        throw new TRPCError({
          code: "PRECONDITION_FAILED",
          message: "Estimate has no working version",
        });
      }

      const pattern = input.pattern ?? "even";
      const updates: Array<{
        id: string;
        monthlySpread: Record<string, number>;
        metadata: Record<string, unknown>;
      }> = [];

      for (const line of workingVersion.demandLines) {
        const result = distributeHoursToWeeks({
          totalHours: line.hours,
          startDate: input.startDate,
          endDate: input.endDate,
          pattern,
        });

        const monthlySpread = aggregateWeeklyToMonthly(result.weeklyHours);
        const existingMetadata = (line.metadata ?? {}) as Record<string, unknown>;
        const metadata = {
          ...existingMetadata,
          weeklyPhasing: {
            startDate: input.startDate,
            endDate: input.endDate,
            pattern,
            weeklyHours: result.weeklyHours,
            generatedAt: new Date().toISOString(),
          },
        };

        updates.push({ id: line.id, monthlySpread, metadata });
      }

      await Promise.all(
        updates.map((update) =>
          ctx.db.estimateDemandLine.update({
            where: { id: update.id },
            data: {
              monthlySpread: update.monthlySpread as Prisma.InputJsonValue,
              metadata: update.metadata as Prisma.InputJsonValue,
            },
          }),
        ),
      );

      return {
        estimateId: input.estimateId,
        versionId: workingVersion.id,
        linesUpdated: updates.length,
        startDate: input.startDate,
        endDate: input.endDate,
        pattern,
      };
    }),

  getWeeklyPhasing: controllerProcedure
    .input(z.object({ estimateId: z.string() }))
    .query(async ({ ctx, input }) => {
      const estimate = await findUniqueOrThrow(
        getEstimateById(
          ctx.db as unknown as Parameters<typeof getEstimateById>[0],
          input.estimateId,
        ),
        "Estimate",
      );

      const version = estimate.versions[0];
      if (!version) {
        throw new TRPCError({
          code: "PRECONDITION_FAILED",
          message: "Estimate has no versions",
        });
      }

      const linesWithPhasing: Array<{
        id: string;
        name: string;
        chapter: string | null;
        hours: number;
        weeklyHours: Record<string, number>;
      }> = [];
      let phasingConfig: { startDate: string; endDate: string; pattern: string } | null = null;

      for (const line of version.demandLines) {
        const meta = (line.metadata ?? {}) as Record<string, unknown>;
        const phasing = meta["weeklyPhasing"] as WeeklyPhasingMeta | undefined;
        if (!phasing) {
          continue;
        }

        if (!phasingConfig) {
          phasingConfig = {
            startDate: phasing.startDate,
            endDate: phasing.endDate,
            pattern: phasing.pattern,
          };
        }

        linesWithPhasing.push({
          id: line.id,
          name: line.name,
          chapter: line.chapter ?? null,
          hours: line.hours,
          weeklyHours: phasing.weeklyHours,
        });
      }

      if (!phasingConfig || linesWithPhasing.length === 0) {
        return {
          estimateId: input.estimateId,
          versionId: version.id,
          hasPhasing: false as const,
          config: null,
          weeks: [],
          lines: [],
          chapterAggregation: {},
        };
      }

      return {
        estimateId: input.estimateId,
        versionId: version.id,
        hasPhasing: true as const,
        config: phasingConfig,
        weeks: generateWeekRange(phasingConfig.startDate, phasingConfig.endDate),
        lines: linesWithPhasing,
        chapterAggregation: aggregateWeeklyByChapter(linesWithPhasing),
      };
    }),
};
