import type { Prisma } from "@capakraken/db";
import { ROLE_BRIEF_SELECT } from "../db/selects.js";
import { findUniqueOrThrow } from "../db/helpers.js";
import { fmtEur } from "../lib/format-utils.js";
import {
  pickBestRateCardLineMatch,
  scoreRateCardLineMatch,
} from "./rate-card-match-support.js";

export const rateCardLineSelect = {
  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;

type RateCardBestMatchInput = {
  clientId?: string | undefined;
  chapter?: string | undefined;
  managementLevelId?: string | undefined;
  roleName?: string | undefined;
  seniority?: string | undefined;
};

type ResolveBestRateInput = {
  resourceId?: string | undefined;
  roleName?: string | undefined;
  date?: Date | undefined;
};

type ResolveRateLineCriteria = {
  roleId?: string | undefined;
  chapter?: string | undefined;
  location?: string | undefined;
  seniority?: string | undefined;
  workType?: string | undefined;
};

const lookupBestRateMatchWeights = {
  roleId: 4,
  chapter: 2,
  seniority: 1,
} as const;

const lookupBestRateCaseInsensitiveKeys = [
  "chapter",
  "seniority",
] as const;

const resolveBestRateLineWeights = {
  roleId: 4,
  chapter: 2,
  location: 1,
  seniority: 1,
  workType: 1,
} as const;

function buildEffectiveRateCardWhere(effectiveAt: Date): Prisma.RateCardWhereInput {
  return {
    isActive: true,
    OR: [
      { effectiveFrom: null },
      { effectiveFrom: { lte: effectiveAt } },
    ],
    AND: [
      {
        OR: [
          { effectiveTo: null },
          { effectiveTo: { gte: effectiveAt } },
        ],
      },
    ],
  };
}

export async function lookupBestRateMatch(
  db: Pick<import("@capakraken/db").PrismaClient, "rateCard" | "role">,
  input: RateCardBestMatchInput,
) {
  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) {
      const scored = scoreRateCardLineMatch({
        roleId: line.role?.id ?? null,
        chapter: line.chapter,
        seniority: line.seniority,
      }, {
        criteria: {
          roleId,
          chapter: input.chapter,
          seniority: input.seniority,
        },
        weights: lookupBestRateMatchWeights,
        caseInsensitiveKeys: lookupBestRateCaseInsensitiveKeys,
      });
      let score = scored.score;
      const mismatch = scored.mismatch;
      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((left, right) => right.score - left.score);

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

export async function resolveBestRate(
  db: Pick<import("@capakraken/db").PrismaClient, "rateCard" | "role" | "resource">,
  input: ResolveBestRateInput,
) {
  const effectiveAt = input.date ?? new Date();

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

    const resolved = await lookupBestRateMatch(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(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 db.rateCard.findMany({
    where: buildEffectiveRateCardWhere(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(
    db.rateCard.findUnique({
      where: { id: card.id },
      include: {
        client: { select: { id: true, name: true, code: true } },
        lines: {
          select: rateCardLineSelect,
          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,
    })),
  };
}

export function resolveBestRateLineMatch<
  TLine extends {
    roleId: string | null;
    chapter: string | null;
    location: string | null;
    seniority: string | null;
    workType: string | null;
  },
>(lines: TLine[], criteria: ResolveRateLineCriteria): TLine | null {
  return pickBestRateCardLineMatch(lines, {
    criteria,
    weights: resolveBestRateLineWeights,
  });
}
