import type { Prisma } from "@capakraken/db";
import {
  CreateRateCardLineSchema,
  CreateRateCardSchema,
  UpdateRateCardLineSchema,
  UpdateRateCardSchema,
} 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 { ROLE_BRIEF_SELECT } from "../db/selects.js";
import { createAuditEntry } from "../lib/audit.js";
import { fmtEur } from "../lib/format-utils.js";

const lineSelect = {
  id: true,
  rateCardId: true,
  roleId: true,
  chapter: true,
  location: true,
  seniority: true,
  workType: true,
  serviceGroup: true,
  costRateCents: true,
  billRateCents: true,
  machineRateCents: true,
  attributes: true,
  role: { select: ROLE_BRIEF_SELECT },
  createdAt: true,
  updatedAt: true,
} as const;

async function lookupBestRateMatch(
  db: Pick<import("@capakraken/db").PrismaClient, "rateCard" | "role">,
  input: {
    clientId?: string | undefined;
    chapter?: string | undefined;
    managementLevelId?: string | undefined;
    roleName?: string | undefined;
    seniority?: string | undefined;
  },
) {
  const rateCardWhere: Prisma.RateCardWhereInput = { isActive: true };
  if (input.clientId) {
    rateCardWhere.OR = [
      { clientId: input.clientId },
      { clientId: null },
    ];
  }

  const rateCards = await db.rateCard.findMany({
    where: rateCardWhere,
    include: {
      lines: {
        select: {
          id: true,
          chapter: true,
          seniority: true,
          costRateCents: true,
          billRateCents: true,
          role: { select: { id: true, name: true } },
        },
      },
      client: { select: { id: true, name: true } },
    },
    orderBy: [{ effectiveFrom: "desc" }],
  });

  if (rateCards.length === 0) {
    return {
      bestMatch: null,
      alternatives: [],
      totalCandidates: 0,
      message: "No active rate cards found.",
    };
  }

  let roleId: string | undefined;
  if (input.roleName) {
    const role = await db.role.findFirst({
      where: { name: { contains: input.roleName, mode: "insensitive" } },
      select: { id: true },
    });
    if (role) roleId = role.id;
  }

  const scoredLines: Array<{
    rateCardName: string;
    clientId: string | null;
    clientName: string | null;
    lineId: string;
    chapter: string | null;
    seniority: string | null;
    roleName: string | null;
    costRateCents: number;
    billRateCents: number | null;
    score: number;
  }> = [];

  for (const card of rateCards) {
    for (const line of card.lines) {
      let score = 0;
      let mismatch = false;

      if (roleId && line.role) {
        if (line.role.id === roleId) score += 4;
        else mismatch = true;
      }
      if (input.chapter && line.chapter) {
        if (line.chapter.toLowerCase() === input.chapter.toLowerCase()) score += 2;
        else mismatch = true;
      }
      if (input.seniority && line.seniority) {
        if (line.seniority.toLowerCase() === input.seniority.toLowerCase()) score += 1;
        else mismatch = true;
      }
      if (input.clientId && card.client?.id === input.clientId) score += 3;

      if (!mismatch) {
        scoredLines.push({
          rateCardName: card.name,
          clientId: card.client?.id ?? null,
          clientName: card.client?.name ?? null,
          lineId: line.id,
          chapter: line.chapter,
          seniority: line.seniority,
          roleName: line.role?.name ?? null,
          costRateCents: line.costRateCents,
          billRateCents: line.billRateCents ?? null,
          score,
        });
      }
    }
  }

  scoredLines.sort((a, b) => b.score - a.score);

  return {
    bestMatch: scoredLines[0] ?? null,
    alternatives: scoredLines.slice(1, 4),
    totalCandidates: scoredLines.length,
  };
}

export const rateCardRouter = createTRPCRouter({
  list: controllerProcedure
    .input(
      z.object({
        isActive: z.boolean().optional(),
        search: z.string().optional(),
        clientId: z.string().optional(),
        effectiveAt: z.coerce.date().optional(),
      }).optional(),
    )
    .query(async ({ ctx, input }) => {
      return ctx.db.rateCard.findMany({
        where: {
          ...(input?.isActive !== undefined ? { isActive: input.isActive } : {}),
          ...(input?.clientId !== undefined ? { clientId: input.clientId } : {}),
          ...(input?.search
            ? { name: { contains: input.search, mode: "insensitive" as const } }
            : {}),
          ...(input?.effectiveAt
            ? {
                OR: [
                  { effectiveFrom: null },
                  { effectiveFrom: { lte: input.effectiveAt } },
                ],
                AND: [
                  {
                    OR: [
                      { effectiveTo: null },
                      { effectiveTo: { gte: input.effectiveAt } },
                    ],
                  },
                ],
              }
            : {}),
        },
        include: {
          _count: { select: { lines: true } },
          client: { select: { id: true, name: true, code: true } },
        },
        orderBy: [{ isActive: "desc" }, { effectiveFrom: "desc" }, { name: "asc" }],
      });
    }),

  getById: controllerProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const rateCard = await findUniqueOrThrow(
        ctx.db.rateCard.findUnique({
          where: { id: input.id },
          include: {
            client: { select: { id: true, name: true, code: true } },
            lines: {
              select: lineSelect,
              orderBy: [{ chapter: "asc" }, { seniority: "asc" }, { createdAt: "asc" }],
            },
          },
        }),
        "Rate card",
      );
      return rateCard;
    }),

  lookupBestMatch: controllerProcedure
    .input(z.object({
      clientId: z.string().optional(),
      chapter: z.string().optional(),
      managementLevelId: z.string().optional(),
      roleName: z.string().optional(),
      seniority: z.string().optional(),
    }))
    .query(async ({ ctx, input }) => lookupBestRateMatch(ctx.db, input)),

  resolveBestRate: controllerProcedure
    .input(z.object({
      resourceId: z.string().optional(),
      roleName: z.string().optional(),
      date: z.coerce.date().optional(),
    }))
    .query(async ({ ctx, input }) => {
      const effectiveAt = input.date ?? new Date();

      if (input.resourceId) {
        const resource = await findUniqueOrThrow(
          ctx.db.resource.findUnique({
            where: { id: input.resourceId },
            select: {
              id: true,
              displayName: true,
              chapter: true,
              areaRole: { select: { name: true } },
            },
          }),
          "Resource",
        );

        const resolved = await lookupBestRateMatch(ctx.db, {
          ...(resource.chapter ? { chapter: resource.chapter } : {}),
          ...(resource.areaRole?.name ? { roleName: resource.areaRole.name } : {}),
        });

        if (resolved.bestMatch) {
          return {
            rateCard: resolved.bestMatch.rateCardName,
            resource: resource.displayName,
            rate: fmtEur(resolved.bestMatch.costRateCents),
            rateCents: resolved.bestMatch.costRateCents,
            matchedBy: resolved.bestMatch.roleName ? `role: ${resolved.bestMatch.roleName}` : "best_match",
          };
        }
      }

      if (input.roleName) {
        const match = await lookupBestRateMatch(ctx.db, { roleName: input.roleName });
        if (match.bestMatch) {
          return {
            rateCard: match.bestMatch.rateCardName,
            rate: fmtEur(match.bestMatch.costRateCents),
            rateCents: match.bestMatch.costRateCents,
            matchedBy: match.bestMatch.roleName ? `role: ${match.bestMatch.roleName}` : "best_match",
            alternatives: match.alternatives.map((alternative) => ({
              rateCard: alternative.rateCardName,
              role: alternative.roleName,
              chapter: alternative.chapter,
              seniority: alternative.seniority,
              costRate: fmtEur(alternative.costRateCents),
              billRate: alternative.billRateCents != null ? fmtEur(alternative.billRateCents) : null,
            })),
          };
        }
        if (match.totalCandidates === 0) {
          return { error: "No matching rate card line found." };
        }
      }

      const cards = await ctx.db.rateCard.findMany({
        where: {
          isActive: true,
          OR: [
            { effectiveFrom: null },
            { effectiveFrom: { lte: effectiveAt } },
          ],
          AND: [
            {
              OR: [
                { effectiveTo: null },
                { effectiveTo: { gte: effectiveAt } },
              ],
            },
          ],
        },
        include: {
          _count: { select: { lines: true } },
          client: { select: { id: true, name: true, code: true } },
        },
        orderBy: [{ isActive: "desc" }, { effectiveFrom: "desc" }, { name: "asc" }],
      });
      const card = cards[0];
      if (!card) {
        return { error: "No active rate card found for the given date." };
      }
      const detail = await findUniqueOrThrow(
        ctx.db.rateCard.findUnique({
          where: { id: card.id },
          include: {
            client: { select: { id: true, name: true, code: true } },
            lines: {
              select: lineSelect,
              orderBy: [{ chapter: "asc" }, { seniority: "asc" }, { createdAt: "asc" }],
            },
          },
        }),
        "Rate card",
      );

      return {
        rateCard: detail.name,
        lines: detail.lines.map((line) => ({
          role: line.role?.name ?? null,
          seniority: line.seniority,
          chapter: line.chapter,
          location: line.location,
          costRate: fmtEur(line.costRateCents),
          billRate: line.billRateCents != null ? fmtEur(line.billRateCents) : null,
        })),
      };
    }),

  create: managerProcedure
    .input(CreateRateCardSchema)
    .mutation(async ({ ctx, input }) => {
      const { lines, ...cardData } = input;

      const rateCard = await ctx.db.rateCard.create({
        data: {
          name: cardData.name,
          currency: cardData.currency,
          ...(cardData.effectiveFrom !== undefined ? { effectiveFrom: cardData.effectiveFrom } : {}),
          ...(cardData.effectiveTo !== undefined ? { effectiveTo: cardData.effectiveTo } : {}),
          ...(cardData.source !== undefined ? { source: cardData.source } : {}),
          ...(cardData.clientId !== undefined ? { clientId: cardData.clientId } : {}),
          lines: {
            create: lines.map((line) => ({
              ...(line.roleId !== undefined ? { roleId: line.roleId } : {}),
              ...(line.chapter !== undefined ? { chapter: line.chapter } : {}),
              ...(line.location !== undefined ? { location: line.location } : {}),
              ...(line.seniority !== undefined ? { seniority: line.seniority } : {}),
              ...(line.workType !== undefined ? { workType: line.workType } : {}),
              ...(line.serviceGroup !== undefined ? { serviceGroup: line.serviceGroup } : {}),
              costRateCents: line.costRateCents,
              ...(line.billRateCents !== undefined ? { billRateCents: line.billRateCents } : {}),
              ...(line.machineRateCents !== undefined ? { machineRateCents: line.machineRateCents } : {}),
              attributes: line.attributes as Prisma.InputJsonValue,
            })),
          },
        },
        include: {
          lines: { select: lineSelect },
        },
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "RateCard",
        entityId: rateCard.id,
        entityName: rateCard.name,
        action: "CREATE",
        userId: ctx.dbUser?.id,
        after: { name: cardData.name, currency: cardData.currency, lineCount: lines.length },
        source: "ui",
      });

      return rateCard;
    }),

  update: managerProcedure
    .input(z.object({ id: z.string(), data: UpdateRateCardSchema }))
    .mutation(async ({ ctx, input }) => {
      const before = await findUniqueOrThrow(
        ctx.db.rateCard.findUnique({ where: { id: input.id } }),
        "Rate card",
      );

      const updated = await ctx.db.rateCard.update({
        where: { id: input.id },
        data: {
          ...(input.data.name !== undefined ? { name: input.data.name } : {}),
          ...(input.data.currency !== undefined ? { currency: input.data.currency } : {}),
          ...(input.data.effectiveFrom !== undefined ? { effectiveFrom: input.data.effectiveFrom } : {}),
          ...(input.data.effectiveTo !== undefined ? { effectiveTo: input.data.effectiveTo } : {}),
          ...(input.data.source !== undefined ? { source: input.data.source } : {}),
          ...(input.data.clientId !== undefined ? { clientId: input.data.clientId } : {}),
          ...(input.data.isActive !== undefined ? { isActive: input.data.isActive } : {}),
        },
        include: {
          _count: { select: { lines: true } },
          client: { select: { id: true, name: true, code: true } },
        },
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "RateCard",
        entityId: input.id,
        entityName: updated.name,
        action: "UPDATE",
        userId: ctx.dbUser?.id,
        before: before as unknown as Record<string, unknown>,
        after: updated as unknown as Record<string, unknown>,
        source: "ui",
      });

      return updated;
    }),

  deactivate: managerProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const deactivated = await ctx.db.rateCard.update({
        where: { id: input.id },
        data: { isActive: false },
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "RateCard",
        entityId: input.id,
        entityName: deactivated.name,
        action: "DELETE",
        userId: ctx.dbUser?.id,
        source: "ui",
        summary: "Deactivated rate card",
      });

      return deactivated;
    }),

  // ─── Line CRUD ─────────────────────────────────────────────────────────────

  addLine: managerProcedure
    .input(z.object({ rateCardId: z.string(), line: CreateRateCardLineSchema }))
    .mutation(async ({ ctx, input }) => {
      const rateCard = await findUniqueOrThrow(
        ctx.db.rateCard.findUnique({ where: { id: input.rateCardId } }),
        "Rate card",
      );

      const line = await ctx.db.rateCardLine.create({
        data: {
          rateCardId: input.rateCardId,
          ...(input.line.roleId !== undefined ? { roleId: input.line.roleId } : {}),
          ...(input.line.chapter !== undefined ? { chapter: input.line.chapter } : {}),
          ...(input.line.location !== undefined ? { location: input.line.location } : {}),
          ...(input.line.seniority !== undefined ? { seniority: input.line.seniority } : {}),
          ...(input.line.workType !== undefined ? { workType: input.line.workType } : {}),
          ...(input.line.serviceGroup !== undefined ? { serviceGroup: input.line.serviceGroup } : {}),
          costRateCents: input.line.costRateCents,
          ...(input.line.billRateCents !== undefined ? { billRateCents: input.line.billRateCents } : {}),
          ...(input.line.machineRateCents !== undefined ? { machineRateCents: input.line.machineRateCents } : {}),
          attributes: input.line.attributes as Prisma.InputJsonValue,
        },
        select: lineSelect,
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "RateCardLine",
        entityId: line.id,
        entityName: `${rateCard.name} — ${input.line.chapter ?? "line"}`,
        action: "CREATE",
        userId: ctx.dbUser?.id,
        after: { rateCardId: input.rateCardId, costRateCents: input.line.costRateCents, billRateCents: input.line.billRateCents },
        source: "ui",
      });

      return line;
    }),

  updateLine: managerProcedure
    .input(z.object({ lineId: z.string(), data: UpdateRateCardLineSchema }))
    .mutation(async ({ ctx, input }) => {
      const before = await findUniqueOrThrow(
        ctx.db.rateCardLine.findUnique({ where: { id: input.lineId } }),
        "Rate card line",
      );

      const updateData: Prisma.RateCardLineUpdateInput = {};
      if (input.data.roleId !== undefined) updateData.role = input.data.roleId ? { connect: { id: input.data.roleId } } : { disconnect: true };
      if (input.data.chapter !== undefined) updateData.chapter = input.data.chapter;
      if (input.data.location !== undefined) updateData.location = input.data.location;
      if (input.data.seniority !== undefined) updateData.seniority = input.data.seniority;
      if (input.data.workType !== undefined) updateData.workType = input.data.workType;
      if (input.data.serviceGroup !== undefined) updateData.serviceGroup = input.data.serviceGroup;
      if (input.data.costRateCents !== undefined) updateData.costRateCents = input.data.costRateCents;
      if (input.data.billRateCents !== undefined) updateData.billRateCents = input.data.billRateCents;
      if (input.data.machineRateCents !== undefined) updateData.machineRateCents = input.data.machineRateCents;
      if (input.data.attributes !== undefined) updateData.attributes = input.data.attributes as Prisma.InputJsonValue;

      const updated = await ctx.db.rateCardLine.update({
        where: { id: input.lineId },
        data: updateData,
        select: lineSelect,
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "RateCardLine",
        entityId: input.lineId,
        action: "UPDATE",
        userId: ctx.dbUser?.id,
        before: before as unknown as Record<string, unknown>,
        after: updated as unknown as Record<string, unknown>,
        source: "ui",
      });

      return updated;
    }),

  deleteLine: managerProcedure
    .input(z.object({ lineId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const line = await findUniqueOrThrow(
        ctx.db.rateCardLine.findUnique({ where: { id: input.lineId } }),
        "Rate card line",
      );

      await ctx.db.rateCardLine.delete({ where: { id: input.lineId } });

      void createAuditEntry({
        db: ctx.db,
        entityType: "RateCardLine",
        entityId: input.lineId,
        action: "DELETE",
        userId: ctx.dbUser?.id,
        before: line as unknown as Record<string, unknown>,
        source: "ui",
      });

      return { deleted: true };
    }),

  // ─── Batch operations ──────────────────────────────────────────────────────

  replaceLines: managerProcedure
    .input(z.object({
      rateCardId: z.string(),
      lines: z.array(CreateRateCardLineSchema),
    }))
    .mutation(async ({ ctx, input }) => {
      const rateCard = await findUniqueOrThrow(
        ctx.db.rateCard.findUnique({ where: { id: input.rateCardId } }),
        "Rate card",
      );

      const result = await ctx.db.$transaction(async (tx) => {
        await tx.rateCardLine.deleteMany({ where: { rateCardId: input.rateCardId } });

        const created = await Promise.all(
          input.lines.map((line) =>
            tx.rateCardLine.create({
              data: {
                rateCardId: input.rateCardId,
                ...(line.roleId !== undefined ? { roleId: line.roleId } : {}),
                ...(line.chapter !== undefined ? { chapter: line.chapter } : {}),
                ...(line.location !== undefined ? { location: line.location } : {}),
                ...(line.seniority !== undefined ? { seniority: line.seniority } : {}),
                ...(line.workType !== undefined ? { workType: line.workType } : {}),
                ...(line.serviceGroup !== undefined ? { serviceGroup: line.serviceGroup } : {}),
                costRateCents: line.costRateCents,
                ...(line.billRateCents !== undefined ? { billRateCents: line.billRateCents } : {}),
                ...(line.machineRateCents !== undefined ? { machineRateCents: line.machineRateCents } : {}),
                attributes: line.attributes as Prisma.InputJsonValue,
              },
              select: lineSelect,
            }),
          ),
        );

        return created;
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "RateCard",
        entityId: input.rateCardId,
        entityName: rateCard.name,
        action: "UPDATE",
        userId: ctx.dbUser?.id,
        after: { replacedLineCount: result.length },
        source: "ui",
        summary: `Replaced all lines with ${result.length} new lines`,
      });

      return result;
    }),

  // ─── Rate resolution ───────────────────────────────────────────────────────

  resolveRateLine: controllerProcedure
    .input(z.object({
      rateCardId: z.string(),
      roleId: z.string().optional(),
      chapter: z.string().optional(),
      location: z.string().optional(),
      seniority: z.string().optional(),
      workType: z.string().optional(),
    }))
    .query(async ({ ctx, input }) => {
      const { rateCardId, ...criteria } = input;

      // Find the most specific matching line (most criteria matched wins)
      const lines = await ctx.db.rateCardLine.findMany({
        where: { rateCardId },
        select: lineSelect,
      });

      if (lines.length === 0) return null;

      // Score each line by number of matching criteria
      const scored = lines.map((line) => {
        let score = 0;
        let mismatch = false;

        if (criteria.roleId && line.roleId) {
          if (line.roleId === criteria.roleId) score += 4;
          else mismatch = true;
        }
        if (criteria.chapter && line.chapter) {
          if (line.chapter === criteria.chapter) score += 2;
          else mismatch = true;
        }
        if (criteria.location && line.location) {
          if (line.location === criteria.location) score += 1;
          else mismatch = true;
        }
        if (criteria.seniority && line.seniority) {
          if (line.seniority === criteria.seniority) score += 1;
          else mismatch = true;
        }
        if (criteria.workType && line.workType) {
          if (line.workType === criteria.workType) score += 1;
          else mismatch = true;
        }

        return { line, score, mismatch };
      });

      // Filter out mismatches and find best match
      const candidates = scored
        .filter((s) => !s.mismatch)
        .sort((a, b) => b.score - a.score);

      const best = candidates[0];
      return best ? best.line : null;
    }),
});
