import { z } from "zod";
import { createTRPCRouter, controllerProcedure } from "../trpc.js";
import {
  getDashboardChargeabilityOverview,
  getDashboardDemand,
  getDashboardOverview,
  getDashboardPeakTimes,
  getDashboardTopValueResources,
  getDashboardBudgetForecast,
  getDashboardSkillGaps,
  getDashboardSkillGapSummary,
  getDashboardProjectHealth,
} from "@capakraken/application";
import { anonymizeResources, getAnonymizationDirectory } from "../lib/anonymization.js";
import { cacheGet, cacheSet } from "../lib/cache.js";
import { fmtEur } from "../lib/format-utils.js";

const DEFAULT_TTL = 60; // seconds

function round1(value: number): number {
  return Math.round(value * 10) / 10;
}

function mapProjectHealthDetailRows(rows: Awaited<ReturnType<typeof getDashboardProjectHealth>>) {
  const projects = rows
    .map((project) => {
      const overall = project.compositeScore;
      return {
        projectId: project.id,
        projectName: project.projectName,
        shortCode: project.shortCode,
        status: project.status,
        overall,
        budget: project.budgetHealth,
        staffing: project.staffingHealth,
        timeline: project.timelineHealth,
        rating: overall >= 80 ? "healthy" : overall >= 50 ? "at_risk" : "critical",
      };
    })
    .sort((left, right) => left.overall - right.overall);

  return {
    projects,
    summary: {
      healthy: projects.filter((project) => project.rating === "healthy").length,
      atRisk: projects.filter((project) => project.rating === "at_risk").length,
      critical: projects.filter((project) => project.rating === "critical").length,
    },
  };
}

function mapBudgetForecastDetailRows(rows: Awaited<ReturnType<typeof getDashboardBudgetForecast>>) {
  return {
    forecasts: rows.map((forecast) => ({
      projectId: forecast.projectId ?? null,
      projectName: forecast.projectName,
      shortCode: forecast.shortCode,
      clientId: forecast.clientId,
      clientName: forecast.clientName,
      budget: fmtEur(forecast.budgetCents),
      budgetCents: forecast.budgetCents,
      spent: fmtEur(forecast.spentCents),
      spentCents: forecast.spentCents,
      remaining: fmtEur(forecast.remainingCents ?? (forecast.budgetCents - forecast.spentCents)),
      remainingCents: forecast.remainingCents ?? (forecast.budgetCents - forecast.spentCents),
      projected: forecast.burnRate > 0
        ? fmtEur(forecast.spentCents + Math.max(0, forecast.budgetCents - forecast.spentCents))
        : fmtEur(forecast.spentCents),
      projectedCents: forecast.burnRate > 0
        ? Math.max(forecast.spentCents, forecast.budgetCents)
        : forecast.spentCents,
      burnRate: fmtEur(forecast.burnRate),
      burnRateCents: forecast.burnRate,
      utilization: `${forecast.pctUsed}%`,
      estimatedExhaustionDate: forecast.estimatedExhaustionDate,
      activeAssignmentCount: forecast.activeAssignmentCount ?? null,
      calendarLocations: forecast.calendarLocations ?? [],
      burnStatus: forecast.pctUsed >= 100
        ? "ahead"
        : forecast.burnRate > 0
          ? "on_track"
          : "not_started",
    })),
  };
}

function mapStatisticsDetail(overview: Awaited<ReturnType<typeof getDashboardOverview>>) {
  return {
    activeResources: overview.activeResources,
    totalProjects: overview.totalProjects,
    activeProjects: overview.activeProjects,
    totalAllocations: overview.totalAllocations,
    approvedVacations: overview.approvedVacations,
    totalEstimates: overview.totalEstimates,
    totalBudget: overview.budgetSummary.totalBudgetCents > 0
      ? fmtEur(overview.budgetSummary.totalBudgetCents)
      : "N/A",
    projectsByStatus: Object.fromEntries(
      overview.projectsByStatus.map((entry) => [entry.status, entry.count]),
    ),
    topChapters: [...overview.chapterUtilization]
      .sort((left, right) => right.resourceCount - left.resourceCount)
      .slice(0, 10)
      .map((chapter) => ({
        chapter: chapter.chapter,
        count: chapter.resourceCount,
      })),
  };
}

async function getOverviewCached(db: Parameters<typeof getDashboardOverview>[0]) {
  const cacheKey = "overview";
  const cached = await cacheGet<Awaited<ReturnType<typeof getDashboardOverview>>>(cacheKey);
  if (cached) return cached;

  const result = await getDashboardOverview(db);
  await cacheSet(cacheKey, result, DEFAULT_TTL);
  return result;
}

async function getPeakTimesCached(
  db: Parameters<typeof getDashboardPeakTimes>[0],
  input: { startDate: string; endDate: string; granularity: "week" | "month"; groupBy: "project" | "chapter" | "resource" },
) {
  const cacheKey = `peakTimes:${input.startDate}:${input.endDate}:${input.granularity}:${input.groupBy}`;
  const cached = await cacheGet<Awaited<ReturnType<typeof getDashboardPeakTimes>>>(cacheKey);
  if (cached) return cached;

  const result = await getDashboardPeakTimes(db, {
    startDate: new Date(input.startDate),
    endDate: new Date(input.endDate),
    granularity: input.granularity,
    groupBy: input.groupBy,
  });
  await cacheSet(cacheKey, result, DEFAULT_TTL);
  return result;
}

async function getDemandCached(
  db: Parameters<typeof getDashboardDemand>[0],
  input: { startDate: string; endDate: string; groupBy: "project" | "person" | "chapter" },
) {
  const cacheKey = `demand:${input.startDate}:${input.endDate}:${input.groupBy}`;
  const cached = await cacheGet<Awaited<ReturnType<typeof getDashboardDemand>>>(cacheKey);
  if (cached) return cached;

  const result = await getDashboardDemand(db, {
    startDate: new Date(input.startDate),
    endDate: new Date(input.endDate),
    groupBy: input.groupBy,
  });
  await cacheSet(cacheKey, result, DEFAULT_TTL);
  return result;
}

async function getTopValueResourcesCached(
  db: Parameters<typeof getDashboardTopValueResources>[0],
  input: { limit: number; userRole: string },
) {
  const cacheKey = `topValue:${input.limit}:${input.userRole}`;
  const cached = await cacheGet<Awaited<ReturnType<typeof anonymizeResources>>>(cacheKey);
  if (cached) return cached;

  const [resources, directory] = await Promise.all([
    getDashboardTopValueResources(db, {
      limit: input.limit,
      userRole: input.userRole,
    }),
    getAnonymizationDirectory(db),
  ]);
  const result = anonymizeResources(resources, directory);
  await cacheSet(cacheKey, result, DEFAULT_TTL);
  return result;
}

export const dashboardRouter = createTRPCRouter({
  getOverview: controllerProcedure.query(async ({ ctx }) => {
    return getOverviewCached(ctx.db);
  }),

  getStatisticsDetail: controllerProcedure.query(async ({ ctx }) => {
    const overview = await getOverviewCached(ctx.db);
    return mapStatisticsDetail(overview);
  }),

  getPeakTimes: controllerProcedure
    .input(
      z.object({
        startDate: z.string().datetime(),
        endDate: z.string().datetime(),
        granularity: z.enum(["week", "month"]).default("month"),
        groupBy: z.enum(["project", "chapter", "resource"]).default("project"),
      }),
    )
    .query(async ({ ctx, input }) => {
      return getPeakTimesCached(ctx.db, input);
    }),

  getTopValueResources: controllerProcedure
    .input(z.object({ limit: z.number().int().min(1).max(50).default(10) }))
    .query(async ({ ctx, input }) => {
      const userRole =
        (ctx.session.user as { role?: string } | undefined)?.role ?? "USER";
      return getTopValueResourcesCached(ctx.db, { limit: input.limit, userRole });
    }),

  getDemand: controllerProcedure
    .input(
      z.object({
        startDate: z.string().datetime(),
        endDate: z.string().datetime(),
        groupBy: z.enum(["project", "person", "chapter"]).default("project"),
      }),
    )
    .query(async ({ ctx, input }) => {
      return getDemandCached(ctx.db, input);
    }),

  getDetail: controllerProcedure
    .input(z.object({ section: z.string().optional().default("all") }))
    .query(async ({ ctx, input }) => {
      const section = input.section;
      const result: Record<string, unknown> = {};
      const needsOverview =
        section === "all"
        || section === "peak_times"
        || section === "demand_pipeline"
        || section === "chargeability_overview";
      const overview = needsOverview ? await getOverviewCached(ctx.db) : null;
      const now = new Date();
      const rangeStart = overview?.budgetBasis.windowStart
        ? new Date(overview.budgetBasis.windowStart)
        : new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
      const rangeEnd = overview?.budgetBasis.windowEnd
        ? new Date(overview.budgetBasis.windowEnd)
        : new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 5, 0));
      const userRole =
        (ctx.session.user as { role?: string } | undefined)?.role
        ?? ctx.dbUser?.systemRole
        ?? "USER";

      if (section === "all" || section === "peak_times") {
        const peakTimes = await getPeakTimesCached(ctx.db, {
          startDate: rangeStart.toISOString(),
          endDate: rangeEnd.toISOString(),
          granularity: "month",
          groupBy: "project",
        });

        result.peakTimes = [...peakTimes]
          .sort((left, right) => right.totalHours - left.totalHours)
          .slice(0, 6)
          .map((entry) => ({
            month: entry.period,
            totalHours: round1(entry.totalHours),
            totalHoursPerDay: round1(entry.totalHours),
            capacityHours: round1(entry.capacityHours),
            utilizationPct: entry.utilizationPct ?? null,
          }));
      }

      if (section === "all" || section === "top_resources") {
        const resources = await getTopValueResourcesCached(ctx.db, { limit: 10, userRole });
        result.topResources = resources.map((resource) => {
          const topResource = resource as {
            displayName: string;
            eid: string;
            chapter: string | null;
            lcrCents: number;
            valueScore: number | null;
          };
          return {
            name: topResource.displayName,
            eid: topResource.eid,
            chapter: topResource.chapter ?? null,
            lcr: fmtEur(topResource.lcrCents),
            valueScore: topResource.valueScore ?? null,
          };
        });
      }

      if (section === "all" || section === "demand_pipeline") {
        const demandRows = await getDemandCached(ctx.db, {
          startDate: rangeStart.toISOString(),
          endDate: rangeEnd.toISOString(),
          groupBy: "project",
        });
        result.demandPipeline = demandRows
          .map((row) => ({
            project: `${row.name} (${row.shortCode})`,
            needed: Math.max(0, round1(row.requiredFTEs - row.resourceCount)),
            requiredFTEs: row.requiredFTEs,
            allocatedResources: row.resourceCount,
            allocatedHours: row.allocatedHours,
            calendarLocations: row.derivation?.calendarLocations ?? [],
          }))
          .filter((row) => row.needed > 0)
          .sort((left, right) => right.needed - left.needed)
          .slice(0, 15);
      }

      if (section === "all" || section === "chargeability_overview") {
        result.chargeabilityByChapter = (overview?.chapterUtilization ?? []).map((chapter) => ({
          chapter: chapter.chapter ?? "Unassigned",
          headcount: chapter.resourceCount,
          avgTarget: `${Math.round(chapter.avgChargeabilityTarget)}%`,
        }));
      }

      return result;
    }),

  getChargeabilityOverview: controllerProcedure
    .input(
      z.object({
        includeProposed:     z.boolean().default(false),
        topN:               z.number().int().min(1).max(50).default(10),
        watchlistThreshold: z.number().default(15),
        countryIds:         z.array(z.string()).optional(),
        departed:           z.boolean().optional(),
      }),
    )
    .query(async ({ ctx, input }) => {
      const cacheKey = `chargeability:${input.includeProposed}:${input.topN}:${input.watchlistThreshold}:${(input.countryIds ?? []).join(",")}:${input.departed ?? ""}`;
      type ChargeResult = Awaited<ReturnType<typeof getDashboardChargeabilityOverview>>;
      const cached = await cacheGet<{
        top: unknown[];
        watchlist: unknown[];
        [key: string]: unknown;
      }>(cacheKey);
      if (cached) return cached;

      const [overview, directory] = await Promise.all([
        getDashboardChargeabilityOverview(ctx.db, {
          includeProposed: input.includeProposed,
          topN: input.topN,
          watchlistThreshold: input.watchlistThreshold,
          ...(input.countryIds !== undefined ? { countryIds: input.countryIds } : {}),
          ...(input.departed !== undefined ? { departed: input.departed } : {}),
        }),
        getAnonymizationDirectory(ctx.db),
      ]);

      const result = {
        ...overview,
        top: anonymizeResources(overview.top, directory),
        watchlist: anonymizeResources(overview.watchlist, directory),
      };
      await cacheSet(cacheKey, result, DEFAULT_TTL);
      return result;
    }),

  getBudgetForecast: controllerProcedure.query(async ({ ctx }) => {
    const cacheKey = "budgetForecast";
    const cached = await cacheGet<Awaited<ReturnType<typeof getDashboardBudgetForecast>>>(cacheKey);
    if (cached) return cached;

    const result = await getDashboardBudgetForecast(ctx.db);
    await cacheSet(cacheKey, result, DEFAULT_TTL);
    return result;
  }),

  getBudgetForecastDetail: controllerProcedure.query(async ({ ctx }) => {
    const budgetForecast = await getDashboardBudgetForecast(ctx.db);
    return mapBudgetForecastDetailRows(budgetForecast);
  }),

  getSkillGaps: controllerProcedure.query(async ({ ctx }) => {
    const cacheKey = "skillGaps";
    const cached = await cacheGet<Awaited<ReturnType<typeof getDashboardSkillGaps>>>(cacheKey);
    if (cached) return cached;

    const result = await getDashboardSkillGaps(ctx.db);
    await cacheSet(cacheKey, result, DEFAULT_TTL);
    return result;
  }),

  getSkillGapSummary: controllerProcedure.query(async ({ ctx }) => {
    const cacheKey = "skillGapSummary";
    const cached = await cacheGet<Awaited<ReturnType<typeof getDashboardSkillGapSummary>>>(cacheKey);
    if (cached) return cached;

    const result = await getDashboardSkillGapSummary(ctx.db);
    await cacheSet(cacheKey, result, DEFAULT_TTL);
    return result;
  }),

  getProjectHealth: controllerProcedure.query(async ({ ctx }) => {
    const cacheKey = "projectHealth";
    const cached = await cacheGet<Awaited<ReturnType<typeof getDashboardProjectHealth>>>(cacheKey);
    if (cached) return cached;

    const result = await getDashboardProjectHealth(ctx.db);
    await cacheSet(cacheKey, result, DEFAULT_TTL);
    return result;
  }),

  getProjectHealthDetail: controllerProcedure.query(async ({ ctx }) => {
    const projectHealth = await getDashboardProjectHealth(ctx.db);
    return mapProjectHealthDetailRows(projectHealth);
  }),
});
