import { recomputeResourceValueScores } from "@capakraken/application";
import { PermissionKey } from "@capakraken/shared";
import { z } from "zod";
import {
  anonymizeResource,
  anonymizeResources,
  getAnonymizationDirectory,
} from "../lib/anonymization.js";
import { resolveResourcePermissions } from "../lib/resource-access.js";
import {
  adminProcedure,
  controllerProcedure,
  protectedProcedure,
  requirePermission,
} from "../trpc.js";

type SkillRow = {
  skill: string;
  category?: string;
  proficiency: number;
  isMainSkill?: boolean;
};

export const resourceAnalyticsProcedures = {
  getSkillsAnalytics: controllerProcedure.query(async ({ ctx }) => {
    const resources = await ctx.db.resource.findMany({
      where: { isActive: true },
      select: { id: true, displayName: true, chapter: true, skills: true },
    });

    const skillMap = new Map<
      string,
      { skill: string; category: string; count: number; totalProficiency: number; chapters: Set<string> }
    >();

    for (const resource of resources) {
      const skills = (resource.skills as unknown as SkillRow[]) ?? [];
      for (const skill of skills) {
        const key = skill.skill;
        if (!skillMap.has(key)) {
          skillMap.set(key, {
            skill: skill.skill,
            category: skill.category ?? "Uncategorized",
            count: 0,
            totalProficiency: 0,
            chapters: new Set(),
          });
        }
        const entry = skillMap.get(key)!;
        entry.count++;
        entry.totalProficiency += skill.proficiency;
        if (resource.chapter) entry.chapters.add(resource.chapter);
      }
    }

    const aggregated = Array.from(skillMap.values())
      .map((entry) => ({
        skill: entry.skill,
        category: entry.category,
        count: entry.count,
        avgProficiency: Math.round((entry.totalProficiency / entry.count) * 10) / 10,
        chapters: Array.from(entry.chapters),
      }))
      .sort((left, right) => right.count - left.count);

    const categories = [...new Set(aggregated.map((entry) => entry.category))].sort();
    const allChapters = [...new Set(resources.map((resource) => resource.chapter).filter(Boolean))].sort() as string[];

    return {
      totalResources: resources.length,
      totalSkillEntries: aggregated.length,
      aggregated,
      categories,
      allChapters,
    };
  }),

  searchBySkills: controllerProcedure
    .input(
      z.object({
        rules: z.array(
          z.object({
            skill: z.string().min(1),
            minProficiency: z.number().int().min(1).max(5).default(1),
          }),
        ),
        chapter: z.string().optional(),
        operator: z.enum(["AND", "OR"]).default("AND"),
      }),
    )
    .query(async ({ ctx, input }) => {
      const { rules, chapter, operator } = input;

      const resources = await ctx.db.resource.findMany({
        where: { isActive: true, ...(chapter ? { chapter } : {}) },
        select: { id: true, eid: true, displayName: true, chapter: true, skills: true },
      });

      const results = resources
        .map((resource) => {
          const skills = (resource.skills as unknown as SkillRow[]) ?? [];

          const matchFn = (rule: { skill: string; minProficiency: number }) => {
            const matchedSkill = skills.find((skill) => skill.skill.toLowerCase().includes(rule.skill.toLowerCase()));
            return matchedSkill && matchedSkill.proficiency >= rule.minProficiency ? matchedSkill : null;
          };

          const matched = rules.map(matchFn);
          const passes = operator === "AND" ? matched.every(Boolean) : matched.some(Boolean);

          if (!passes) return null;

          return {
            id: resource.id,
            eid: resource.eid,
            displayName: resource.displayName,
            chapter: resource.chapter,
            matchedSkills: rules
              .map((rule, index) => {
                const matchedSkill = matched[index];
                return matchedSkill
                  ? {
                      skill: matchedSkill.skill,
                      proficiency: matchedSkill.proficiency,
                      category: matchedSkill.category ?? "",
                    }
                  : null;
              })
              .filter((skill): skill is { skill: string; proficiency: number; category: string } => skill !== null),
          };
        })
        .filter((resource): resource is NonNullable<typeof resource> => resource !== null)
        .sort((left, right) => left.displayName.localeCompare(right.displayName));

      const directory = await getAnonymizationDirectory(ctx.db);
      return anonymizeResources(results, directory);
    }),

  getMyResource: protectedProcedure.query(async ({ ctx }) => {
    const user = await ctx.db.user.findUnique({
      where: { id: ctx.dbUser!.id },
      select: { resource: { select: { id: true, displayName: true, eid: true, chapter: true } } },
    });
    const directory = await getAnonymizationDirectory(ctx.db);
    return user?.resource ? anonymizeResource(user.resource, directory) : null;
  }),

  getValueScores: protectedProcedure
    .input(
      z.object({
        isActive: z.boolean().optional().default(true),
        limit: z.number().int().min(1).max(500).default(100),
      }),
    )
    .query(async ({ ctx, input }) => {
      const permissions = resolveResourcePermissions(ctx);
      requirePermission({ permissions }, PermissionKey.VIEW_SCORES);

      const resources = await ctx.db.resource.findMany({
        where: { isActive: input.isActive },
        select: {
          id: true,
          eid: true,
          displayName: true,
          chapter: true,
          lcrCents: true,
          valueScore: true,
          valueScoreBreakdown: true,
          valueScoreUpdatedAt: true,
        },
        orderBy: [{ valueScore: "desc" }, { displayName: "asc" }],
        take: input.limit,
      });

      const directory = await getAnonymizationDirectory(ctx.db);
      return anonymizeResources(resources, directory);
    }),

  recomputeValueScores: adminProcedure.mutation(async ({ ctx }) => {
    return recomputeResourceValueScores(ctx.db);
  }),
};
