import { DepartmentKey, EmployeeLifecycleEventType, HrUpdateSeverity } from "@prisma/client";
import { prisma } from "@/lib/prisma";
import { buildHrAggregateScore, churnPct } from "@/lib/human-capital/scoring";
import type { HumanCapitalDashboardResponse } from "@/lib/human-capital/types";

const HUMAN_CAPITAL_AUTO_SECTION_KEY = "human-capital-auto-v1";

function monthKey(date: Date): string {
  return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}`;
}

function startOfMonth(date: Date): Date {
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1, 0, 0, 0));
}

function endOfMonth(date: Date): Date {
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0, 23, 59, 59));
}

function monthsBetween(start: Date, end: Date): number {
  const years = end.getUTCFullYear() - start.getUTCFullYear();
  const months = end.getUTCMonth() - start.getUTCMonth();
  const days = end.getUTCDate() - start.getUTCDate();
  return years * 12 + months + (days >= 0 ? 0 : -1);
}

function median(values: number[]): number {
  if (values.length === 0) {
    return 0;
  }

  const sorted = [...values].sort((a, b) => a - b);
  const center = Math.floor(sorted.length / 2);
  if (sorted.length % 2 === 0) {
    return (sorted[center - 1] + sorted[center]) / 2;
  }

  return sorted[center];
}

function bandForTenure(months: number): string {
  if (months < 12) {
    return "<1y";
  }
  if (months < 36) {
    return "1-3y";
  }
  if (months < 60) {
    return "3-5y";
  }
  return "5y+";
}

export async function ensureEmployeeProfiles() {
  const usersWithoutProfile = await prisma.user.findMany({
    where: {
      employeeProfile: null,
    },
    select: {
      id: true,
      createdAt: true,
      department: true,
      status: true,
    },
    take: 2000,
  });

  if (usersWithoutProfile.length === 0) {
    return;
  }

  await prisma.employeeProfile.createMany({
    data: usersWithoutProfile.map((user) => ({
      userId: user.id,
      hireDate: user.createdAt,
      employmentType: "full_time",
      locationId: user.department ?? null,
      employmentStatus: user.status === "active" ? "active" : user.status === "inactive" ? "leave" : "terminated",
    })),
    skipDuplicates: true,
  });
}

export async function getHumanCapitalDashboardData(): Promise<HumanCapitalDashboardResponse> {
  await ensureEmployeeProfiles();

  const now = new Date();
  const currentMonthStart = startOfMonth(now);
  const currentMonthEnd = endOfMonth(now);

  const [activeProfiles, lifecycleEvents, latestWeekKpiRows, updates, previousSnapshot] = await Promise.all([
    prisma.employeeProfile.findMany({
      where: {
        employmentStatus: "active",
      },
      include: {
        user: {
          select: {
            id: true,
            name: true,
            email: true,
            department: true,
            departmentRole: true,
          },
        },
      },
    }),
    prisma.employeeLifecycleEvent.findMany({
      where: {
        effectiveAt: {
          gte: new Date(now.getTime() - 370 * 24 * 60 * 60 * 1000),
        },
      },
      orderBy: { effectiveAt: "desc" },
    }),
    prisma.weeklyKpiRow.findMany({
      where: {
        section: {
          mappedDepartment: DepartmentKey.capital_humano,
          sectionKey: {
            not: HUMAN_CAPITAL_AUTO_SECTION_KEY,
          },
          snapshot: {
            weekStart: {
              gte: new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000),
            },
          },
        },
      },
      orderBy: {
        updatedAt: "desc",
      },
      take: 200,
    }),
    prisma.hrUpdate.findMany({
      where: {
        status: "published",
      },
      orderBy: [{ publishedAt: "desc" }, { createdAt: "desc" }],
      take: 8,
    }),
    prisma.hrMetricSnapshot.findFirst({
      where: {
        department: DepartmentKey.capital_humano,
        snapshotDate: {
          lt: currentMonthStart,
        },
      },
      orderBy: { snapshotDate: "desc" },
    }),
  ]);

  const activeHeadcount = activeProfiles.length;
  const previousHeadcount = previousSnapshot?.headcount ?? activeHeadcount;
  const monthlyExits = lifecycleEvents.filter(
    (event) =>
      event.eventType === EmployeeLifecycleEventType.termination &&
      event.effectiveAt >= currentMonthStart &&
      event.effectiveAt <= currentMonthEnd
  ).length;
  const monthlyHires = lifecycleEvents.filter(
    (event) =>
      (event.eventType === EmployeeLifecycleEventType.hire || event.eventType === EmployeeLifecycleEventType.rehire) &&
      event.effectiveAt >= currentMonthStart &&
      event.effectiveAt <= currentMonthEnd
  ).length;
  const monthlyChurnPct = churnPct(monthlyExits, (activeHeadcount + previousHeadcount) / 2 || 1);

  const tenureMonths = activeProfiles.map((profile) => {
    const hireDate = profile.hireDate ?? now;
    return Math.max(0, monthsBetween(hireDate, now));
  });
  const medianTenureMonths = Math.round(median(tenureMonths) * 10) / 10;

  const kpiRowsWithScore = latestWeekKpiRows.filter((row) => row.compliancePct !== null);
  const kpiCompliancePct =
    kpiRowsWithScore.length > 0
      ? Math.round(
          (kpiRowsWithScore.reduce((acc, row) => acc + (row.compliancePct ?? 0), 0) / kpiRowsWithScore.length) * 10
        ) / 10
      : 0;

  const aggregateScore = buildHrAggregateScore({
    monthlyChurnPct,
    medianTenureMonths,
    activeHeadcount,
    previousHeadcount,
    kpiCompliancePct,
  });

  const churnTrend = Array.from({ length: 6 }).map((_, idx) => {
    const monthDate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - (5 - idx), 1));
    const start = startOfMonth(monthDate);
    const end = endOfMonth(monthDate);
    const isHireEvent = (eventType: EmployeeLifecycleEventType) =>
      eventType === EmployeeLifecycleEventType.hire || eventType === EmployeeLifecycleEventType.rehire;
    const exits = lifecycleEvents.filter(
      (event) => event.eventType === EmployeeLifecycleEventType.termination && event.effectiveAt >= start && event.effectiveAt <= end
    ).length;
    const hires = lifecycleEvents.filter(
      (event) => isHireEvent(event.eventType) && event.effectiveAt >= start && event.effectiveAt <= end
    ).length;
    const eventsAfterMonth = lifecycleEvents.filter((event) => event.effectiveAt > end);
    let monthEndHeadcount = activeHeadcount;
    for (const event of eventsAfterMonth) {
      if (isHireEvent(event.eventType)) {
        monthEndHeadcount -= 1;
      } else if (event.eventType === EmployeeLifecycleEventType.termination) {
        monthEndHeadcount += 1;
      }
    }
    const monthStartHeadcount = monthEndHeadcount - (hires - exits);
    const avgHeadcount = Math.max(1, (monthStartHeadcount + monthEndHeadcount) / 2);
    const churn = churnPct(exits, avgHeadcount);
    return {
      month: monthKey(monthDate),
      churnPct: churn,
      exits,
      hires,
    };
  });

  const tenureBandMap = new Map<string, number>([
    ["<1y", 0],
    ["1-3y", 0],
    ["3-5y", 0],
    ["5y+", 0],
  ]);

  for (const tenure of tenureMonths) {
    const key = bandForTenure(tenure);
    tenureBandMap.set(key, (tenureBandMap.get(key) ?? 0) + 1);
  }

  const tenureBands = Array.from(tenureBandMap.entries()).map(([label, count]) => ({ label, count }));

  const todayIso = now.toISOString().slice(0, 10);
  const existingSnapshot = await prisma.hrMetricSnapshot.findFirst({
    where: {
      department: DepartmentKey.capital_humano,
      snapshotDate: new Date(`${todayIso}T00:00:00.000Z`),
    },
  });

  if (!existingSnapshot) {
    await prisma.hrMetricSnapshot.create({
      data: {
        department: DepartmentKey.capital_humano,
        snapshotDate: new Date(`${todayIso}T00:00:00.000Z`),
        headcount: activeHeadcount,
        hires: monthlyHires,
        exits: monthlyExits,
        churnPct: monthlyChurnPct,
        medianTenureMonths,
        peopleHealthScore: aggregateScore.totalScore,
      },
    });
  }

  return {
    generatedAt: now.toISOString(),
    summary: {
      activeHeadcount,
      previousHeadcount,
      netHeadcountChange: activeHeadcount - previousHeadcount,
      monthlyExits,
      monthlyHires,
      monthlyChurnPct,
      medianTenureMonths,
      kpiCompliancePct,
    },
    aggregateScore,
    churnTrend,
    tenureBands,
    updates: updates.map((update) => ({
      id: update.id,
      title: update.title,
      severity:
        update.severity === HrUpdateSeverity.warning
          ? "warning"
          : update.severity === HrUpdateSeverity.critical
            ? "critical"
            : "info",
      status: update.status,
      publishedAt: update.publishedAt ? update.publishedAt.toISOString() : null,
    })),
  };
}
