import {
  applyExperienceMultipliers,
  applyExperienceMultipliersBatch,
} from "@capakraken/engine";
import {
  CreateExperienceMultiplierSetSchema,
  UpdateExperienceMultiplierSetSchema,
  ApplyExperienceMultipliersSchema,
} from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { findUniqueOrThrow } from "../db/helpers.js";
import { createTRPCRouter, controllerProcedure, managerProcedure } from "../trpc.js";
import { createAuditEntry } from "../lib/audit.js";
import {
  buildExperienceMultiplierCreateManyRows,
  buildExperienceMultiplierDemandLineUpdateData,
  buildExperienceMultiplierInput,
  buildExperienceMultiplierSetCreateData,
  buildExperienceMultiplierSetUpdateData,
  experienceMultiplierRuleInclude,
  hasExperienceMultiplierChanges,
  toExperienceMultiplierEngineRules,
} from "./experience-multiplier-support.js";

export const experienceMultiplierRouter = createTRPCRouter({
  list: controllerProcedure.query(async ({ ctx }) => {
    return ctx.db.experienceMultiplierSet.findMany({
      include: experienceMultiplierRuleInclude,
      orderBy: [{ isDefault: "desc" }, { name: "asc" }],
    });
  }),

  getById: controllerProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const set = await findUniqueOrThrow(
        ctx.db.experienceMultiplierSet.findUnique({
          where: { id: input.id },
          include: experienceMultiplierRuleInclude,
        }),
        "Experience multiplier set",
      );
      return set;
    }),

  create: managerProcedure
    .input(CreateExperienceMultiplierSetSchema)
    .mutation(async ({ ctx, input }) => {
      if (input.isDefault) {
        await ctx.db.experienceMultiplierSet.updateMany({
          where: { isDefault: true },
          data: { isDefault: false },
        });
      }

      const set = await ctx.db.experienceMultiplierSet.create({
        data: buildExperienceMultiplierSetCreateData(input),
        include: experienceMultiplierRuleInclude,
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "ExperienceMultiplierSet",
        entityId: set.id,
        entityName: set.name,
        action: "CREATE",
        userId: ctx.dbUser?.id,
        after: { name: input.name, isDefault: input.isDefault, ruleCount: input.rules.length },
        source: "ui",
      });

      return set;
    }),

  update: managerProcedure
    .input(UpdateExperienceMultiplierSetSchema)
    .mutation(async ({ ctx, input }) => {
      const before = await findUniqueOrThrow(
        ctx.db.experienceMultiplierSet.findUnique({ where: { id: input.id }, include: experienceMultiplierRuleInclude }),
        "Experience multiplier set",
      );

      if (input.isDefault) {
        await ctx.db.experienceMultiplierSet.updateMany({
          where: { isDefault: true, id: { not: input.id } },
          data: { isDefault: false },
        });
      }

      if (input.rules) {
        await ctx.db.experienceMultiplierRule.deleteMany({ where: { multiplierSetId: input.id } });
        await ctx.db.experienceMultiplierRule.createMany({
          data: buildExperienceMultiplierCreateManyRows(input.rules, input.id),
        });
      }

      const updated = await ctx.db.experienceMultiplierSet.update({
        where: { id: input.id },
        data: buildExperienceMultiplierSetUpdateData(input),
        include: experienceMultiplierRuleInclude,
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "ExperienceMultiplierSet",
        entityId: input.id,
        entityName: updated.name,
        action: "UPDATE",
        userId: ctx.dbUser?.id,
        before: { name: before.name, isDefault: before.isDefault, ruleCount: before.rules.length },
        after: { name: updated.name, isDefault: updated.isDefault, ruleCount: updated.rules.length },
        source: "ui",
      });

      return updated;
    }),

  delete: managerProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const set = await findUniqueOrThrow(
        ctx.db.experienceMultiplierSet.findUnique({ where: { id: input.id } }),
        "Experience multiplier set",
      );
      await ctx.db.experienceMultiplierSet.delete({ where: { id: input.id } });

      void createAuditEntry({
        db: ctx.db,
        entityType: "ExperienceMultiplierSet",
        entityId: input.id,
        entityName: set.name,
        action: "DELETE",
        userId: ctx.dbUser?.id,
        source: "ui",
      });

      return { id: input.id };
    }),

  /** Preview the rate adjustment without persisting */
  preview: controllerProcedure
    .input(z.object({
      estimateId: z.string(),
      multiplierSetId: z.string(),
    }))
    .query(async ({ ctx, input }) => {
      const [estimate, multiplierSet] = await Promise.all([
        findUniqueOrThrow(
          ctx.db.estimate.findUnique({
            where: { id: input.estimateId },
            include: {
              versions: {
                orderBy: { versionNumber: "desc" },
                take: 1,
                include: { demandLines: { orderBy: { createdAt: "asc" } } },
              },
            },
          }),
          "Estimate",
        ),
        findUniqueOrThrow(
          ctx.db.experienceMultiplierSet.findUnique({
            where: { id: input.multiplierSetId },
            include: experienceMultiplierRuleInclude,
          }),
          "Experience multiplier set",
        ),
      ]);

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

      const engineRules = toExperienceMultiplierEngineRules(multiplierSet.rules);
      const demandLines = version.demandLines;

      const previews = demandLines.map((line) => {
        const result = applyExperienceMultipliers(
          buildExperienceMultiplierInput(line),
          engineRules,
        );

        return {
          demandLineId: line.id,
          name: line.name,
          chapter: line.chapter,
          originalCostRateCents: line.costRateCents,
          originalBillRateCents: line.billRateCents,
          originalHours: line.hours,
          adjustedCostRateCents: result.adjustedCostRateCents,
          adjustedBillRateCents: result.adjustedBillRateCents,
          adjustedHours: result.adjustedHours,
          appliedRules: result.appliedRules,
          hasChanges: hasExperienceMultiplierChanges(line, result),
        };
      });

      const linesChanged = previews.filter((p) => p.hasChanges).length;
      const totalOriginalCostCents = demandLines.reduce((s, l) => s + l.costRateCents * l.hours, 0);
      const totalAdjustedCostCents = previews.reduce((s, p) => s + p.adjustedCostRateCents * p.adjustedHours, 0);

      return {
        previews,
        demandLineCount: demandLines.length,
        linesChanged,
        totalOriginalCostCents: Math.round(totalOriginalCostCents),
        totalAdjustedCostCents: Math.round(totalAdjustedCostCents),
        multiplierSetName: multiplierSet.name,
        ruleCount: multiplierSet.rules.length,
      };
    }),

  /** Apply multipliers to demand lines on the working version */
  applyRules: managerProcedure
    .input(ApplyExperienceMultipliersSchema)
    .mutation(async ({ ctx, input }) => {
      const [estimate, multiplierSet] = await Promise.all([
        findUniqueOrThrow(
          ctx.db.estimate.findUnique({
            where: { id: input.estimateId },
            include: {
              versions: {
                orderBy: { versionNumber: "desc" },
                take: 1,
                include: { demandLines: true },
              },
            },
          }),
          "Estimate",
        ),
        findUniqueOrThrow(
          ctx.db.experienceMultiplierSet.findUnique({
            where: { id: input.multiplierSetId },
            include: experienceMultiplierRuleInclude,
          }),
          "Experience multiplier set",
        ),
      ]);

      const version = estimate.versions[0];
      if (!version) throw new TRPCError({ code: "NOT_FOUND", message: "Estimate has no versions" });
      if (version.status !== "WORKING") {
        throw new TRPCError({ code: "BAD_REQUEST", message: "Can only apply multipliers to a WORKING version" });
      }

      const engineRules = toExperienceMultiplierEngineRules(multiplierSet.rules);
      const demandLines = version.demandLines;

      const inputs = demandLines.map((line) => buildExperienceMultiplierInput(line));

      const batch = applyExperienceMultipliersBatch(inputs, engineRules);

      // Update each demand line that changed
      let updatedCount = 0;
      for (let i = 0; i < demandLines.length; i++) {
        const line = demandLines[i]!;
        const result = batch.results[i]!;

        if (hasExperienceMultiplierChanges(line, result)) {
          await ctx.db.estimateDemandLine.update({
            where: { id: line.id },
            data: buildExperienceMultiplierDemandLineUpdateData({
              line,
              result,
              multiplierSet,
            }),
          });
          updatedCount++;
        }
      }

      // Audit log
      await ctx.db.auditLog.create({
        data: {
          entityType: "Estimate",
          entityId: estimate.id,
          action: "UPDATE",
          ...(ctx.dbUser?.id ? { userId: ctx.dbUser.id } : {}),
          changes: {
            after: {
              experienceMultipliersApplied: {
                setId: multiplierSet.id,
                setName: multiplierSet.name,
                linesUpdated: updatedCount,
                totalOriginalHours: batch.totalOriginalHours,
                totalAdjustedHours: batch.totalAdjustedHours,
              },
            },
          },
        },
      });

      return {
        linesUpdated: updatedCount,
        totalOriginalHours: batch.totalOriginalHours,
        totalAdjustedHours: batch.totalAdjustedHours,
      };
    }),
});
