import { ChevronRight } from "lucide-react";
import Link from "next/link";
import { DraftsCard } from "@/components/dashboard/DraftsCard";
import { Card, CARD_CLASS, CardTitle } from "@/components/ui/Card";
import { actionBadgeStyle } from "@/lib/colors";
import { addDaysIso, fmtDate, todayIso } from "@/lib/format";
import { deriveStatusAsOf } from "@/lib/reports";
import { createClient } from "@/lib/supabase/server";
import { fetchAllRows } from "@/lib/supabase/query";

// Each KPI carries a colour already; the accent bar repeats it in a second
// channel so the tiles are scannable as a row rather than six identical
// boxes, and so the meaning does not rest on hue alone.
const TONE: Record<string, { text: string; bar: string }> = {
  default: { text: "text-ink", bar: "bg-ink-muted" },
  success: { text: "text-success-text", bar: "bg-success-text" },
  danger: { text: "text-danger-text", bar: "bg-danger-text" },
  warning: { text: "text-warning-text", bar: "bg-warning-text" },
  brand: { text: "text-brand-700", bar: "bg-brand-500" },
};

const DOT_STYLES: Record<string, string> = {
  Eintritt: "bg-success-text",
  Wiedereintritt: "bg-success-text",
  Rückkehr: "bg-success-text",
  Austritt: "bg-danger-text",
  Versetzung: "bg-info-text",
  Beförderung: "bg-purple-text",
  Reorganisation: "bg-purple-text",
  Karenz: "bg-warning-text",
  Vertragsänderung: "bg-warning-text",
  Stammdatenänderung: "bg-warning-text",
  Gehaltsanpassung: "bg-warning-text",
};

const KIND_LABEL = { hire: "Eintritt", exit: "Austritt", return: "Rückkehr aus Abwesenheit" } as const;

export default async function DashboardPage() {
  const supabase = await createClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();
  const { data: drafts } = user
    ? await supabase
        .from("hire_drafts")
        .select("id, step, payload, updated_at")
        .eq("created_by", user.id)
        .order("updated_at", { ascending: false })
    : { data: [] };

  // Built as strings, not by round-tripping a local Date through
  // toISOString(): in any positive-offset zone new Date(year, 0, 1) is still
  // the previous year in UTC, which shifted the whole YTD window a day early
  // and dropped 31 December from it entirely.
  const today = todayIso();
  const year = today.slice(0, 4);
  const yearStart = `${year}-01-01`;
  const yearEnd = `${year}-12-31`;
  const in60Iso = addDaysIso(today, 60);

  // Headcount, FTE, Karenz and the division bars all come from one full read
  // and the *derived* status, not from the `employees.status` column.
  //
  // That column only ever reflects what the last mutation or cron run wrote,
  // while every report derives status from entry/exit/karenz dates — so a
  // planned hire whose start date has passed, or a Karenz that ended without
  // anyone recording the return, made the dashboard and the Berichte page
  // disagree about the same headcount. Same derivation, same numbers.
  // It also replaces four separate count queries with one.
  const [
    staffRows,
    hiresYtdRes,
    exitsYtdRes,
    openPositionsRes,
    divisionsRes,
    upcomingHiresRes,
    upcomingExitsRes,
    upcomingReturnsRes,
    historyRes,
  ] = await Promise.all([
    fetchAllRows(() =>
      supabase
        .from("employees")
        .select("weekly_hours, division_id, entry_date, exit_date, karenz_start_date, karenz_return_date")
        .order("id")
    ),
    // Entries/exits count history events, which is what the linked report
    // counts too. `entry_date` would also sweep up rehires, whose event is
    // logged as 'Wiedereintritt' — the tile and its destination then showed
    // different numbers for the same year.
    supabase
      .from("employee_history")
      .select("id", { count: "exact", head: true })
      .in("event_type", ["Eintritt", "Wiedereintritt"])
      .gte("event_date", yearStart)
      .lte("event_date", yearEnd),
    supabase
      .from("employee_history")
      .select("id", { count: "exact", head: true })
      .eq("event_type", "Austritt")
      .gte("event_date", yearStart)
      .lte("event_date", yearEnd),
    supabase.from("positions").select("id", { count: "exact", head: true }).eq("status", "open"),
    supabase.from("divisions").select("id, name"),
    supabase
      .from("employees")
      .select("id, first_name, last_name, entry_date")
      .eq("status", "Geplant")
      .gte("entry_date", today)
      .lte("entry_date", in60Iso),
    supabase
      .from("employees")
      .select("id, first_name, last_name, exit_date")
      .not("exit_date", "is", null)
      .gte("exit_date", today)
      .lte("exit_date", in60Iso),
    supabase
      .from("employees")
      .select("id, first_name, last_name, karenz_return_date")
      .eq("status", "Karenz")
      .not("karenz_return_date", "is", null)
      .gte("karenz_return_date", today)
      .lte("karenz_return_date", in60Iso),
    supabase
      .from("employee_history")
      .select("id, employee_id, event_date, event_type, description")
      .order("event_date", { ascending: false })
      .order("created_at", { ascending: false })
      .limit(10),
  ]);

  // "Aktiv" means status Aktiv — somebody on Karenz is employed but not
  // active, and is counted by its own tile instead. FTE follows the same
  // set: Karenz contributes no capacity, so including it would overstate
  // what the company can actually staff.
  //
  // Note this is narrower than DEFAULT_STATUSES in lib/reports (Aktiv +
  // Karenz), which still governs what the Berichte page shows when no
  // status filter is chosen.
  const statusOf = (row: (typeof staffRows)[number]) => deriveStatusAsOf(row, today);
  const activeStaff = staffRows.filter((row) => statusOf(row) === "Aktiv");
  const activeCount = activeStaff.length;
  const karenzCount = staffRows.filter((row) => statusOf(row) === "Karenz").length;
  const fte = activeStaff.reduce((sum, row) => sum + Number(row.weekly_hours), 0) / 38.5;

  const headcountByDivision = new Map<string, number>();
  for (const row of activeStaff) {
    if (!row.division_id) continue;
    headcountByDivision.set(row.division_id, (headcountByDivision.get(row.division_id) ?? 0) + 1);
  }
  const divisionBars = (divisionsRes.data ?? [])
    .map((d) => ({ name: d.name, count: headcountByDivision.get(d.id) ?? 0 }))
    .sort((a, b) => b.count - a.count);
  const maxDivisionCount = Math.max(1, ...divisionBars.map((d) => d.count));

  type UpcomingItem = { id: string; label: string; date: string; kind: keyof typeof KIND_LABEL };
  const upcoming: UpcomingItem[] = [
    ...(upcomingHiresRes.data ?? []).map((e) => ({
      id: e.id,
      label: `${e.first_name} ${e.last_name}`,
      date: e.entry_date,
      kind: "hire" as const,
    })),
    ...(upcomingExitsRes.data ?? []).map((e) => ({
      id: e.id,
      label: `${e.first_name} ${e.last_name}`,
      date: e.exit_date!,
      kind: "exit" as const,
    })),
    ...(upcomingReturnsRes.data ?? []).map((e) => ({
      id: e.id,
      label: `${e.first_name} ${e.last_name}`,
      date: e.karenz_return_date!,
      kind: "return" as const,
    })),
  ]
    .sort((a, b) => a.date.localeCompare(b.date))
    .slice(0, 8);

  const historyEmployeeIds = Array.from(new Set((historyRes.data ?? []).map((h) => h.employee_id)));
  const historyEmployeesRes = historyEmployeeIds.length
    ? await supabase.from("employees").select("id, first_name, last_name").in("id", historyEmployeeIds)
    : { data: [] as { id: string; first_name: string; last_name: string }[] };
  const employeeNameById = new Map((historyEmployeesRes.data ?? []).map((e) => [e.id, `${e.first_name} ${e.last_name}`]));

  // Each tile links to the view that shows what it counts, with the filters
  // pre-applied.
  //
  // Two of them cannot match exactly, and it is worth knowing which: the
  // headcount tiles filter `employees` and their targets filter the same
  // table, so those agree. Eintritte/Austritte count `employees.entry_date`
  // / `exit_date`, while the events report counts `employee_history` rows —
  // and rehire_employee sets entry_date but logs the event as
  // 'Wiedereintritt'. A year with rehires therefore shows a slightly higher
  // number on the tile than in the linked report.
  const kpis = [
    {
      label: "Aktive Mitarbeiter:innen",
      value: activeCount,
      tone: "default",
      href: "/employees?status=Aktiv",
    },
    { label: "FTE", value: fte.toFixed(1), tone: "default", href: "/reports?mode=snapshot&measure=fte&status=Aktiv" },
    {
      label: "Eintritte (Jahr)",
      value: hiresYtdRes.count ?? 0,
      tone: "success",
      href: `/reports?mode=events&eventType=Eintritt&from=${yearStart}&to=${yearEnd}`,
    },
    {
      label: "Austritte (Jahr)",
      value: exitsYtdRes.count ?? 0,
      tone: "danger",
      href: `/reports?mode=events&eventType=Austritt&from=${yearStart}&to=${yearEnd}`,
    },
    { label: "Langzeitabwesend", value: karenzCount, tone: "warning", href: "/employees?status=Karenz" },
    { label: "Offene Positionen", value: openPositionsRes.count ?? 0, tone: "brand", href: "/positions" },
  ];

  return (
    <div className="flex flex-col gap-6">
      {drafts && drafts.length > 0 && <DraftsCard drafts={drafts} />}
      <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-6">
        {kpis.map((kpi) => (
          <Link
            key={kpi.label}
            href={kpi.href}
            className={`${CARD_CLASS} group relative overflow-hidden p-4 pl-5 transition-shadow hover:shadow-[var(--shadow-card-hover)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-500`}
          >
            <span className={`absolute inset-y-0 left-0 w-1 ${TONE[kpi.tone].bar}`} aria-hidden />
            {/* Number first in the visual order: it is what the eye is
                looking for, and the label only qualifies it. */}
            <div className={`text-3xl font-extrabold leading-none tabular-nums ${TONE[kpi.tone].text}`}>{kpi.value}</div>
            <div className="mt-1.5 flex items-center gap-1 text-xs font-semibold leading-tight text-ink-muted">
              {kpi.label}
              <ChevronRight className="h-3 w-3 shrink-0 opacity-0 transition-opacity group-hover:opacity-100" aria-hidden />
            </div>
          </Link>
        ))}
      </div>

      <div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
        <Card>
          {/* Aktive, not headcount: the bars count the same set as the tile
              above them, which excludes Karenz. */}
          <CardTitle className="mb-3">Aktive nach Bereich</CardTitle>
          <div className="flex flex-col gap-2.5">
            {divisionBars.map((d) => (
              <div key={d.name}>
                <div className="mb-1 flex justify-between text-xs">
                  <span className="text-ink-body">{d.name}</span>
                  <span className="font-semibold tabular-nums text-ink">{d.count}</span>
                </div>
                {/* Rounded ends and a minimum width so the smallest division
                    still reads as a bar rather than a stray pixel. */}
                <div className="h-1.5 overflow-hidden rounded-full bg-surface">
                  <div
                    className="h-full rounded-full bg-brand-500"
                    style={{ width: `${Math.max(2, (d.count / maxDivisionCount) * 100)}%` }}
                  />
                </div>
              </div>
            ))}
            {divisionBars.length === 0 && <p className="text-sm text-ink-muted">Keine Daten vorhanden.</p>}
          </div>
        </Card>

        <Card>
          <CardTitle className="mb-1">Anstehend (60 Tage)</CardTitle>
          <ul className="flex flex-col divide-y divide-border-subtle">
            {upcoming.map((item) => (
              <li key={`${item.kind}-${item.id}`}>
                <Link
                  href={`/employees/${item.id}`}
                  className="-mx-2 flex items-center justify-between gap-2 rounded px-2 py-2.5 text-sm hover:bg-surface"
                >
                  <span className="min-w-0">
                    <span className="block truncate font-semibold text-ink">{item.label}</span>
                    <span className="text-xs text-ink-muted">{KIND_LABEL[item.kind]}</span>
                  </span>
                  <span className="shrink-0 text-xs font-semibold tabular-nums text-ink-muted">{fmtDate(item.date)}</span>
                </Link>
              </li>
            ))}
            {upcoming.length === 0 && <p className="py-2 text-sm text-ink-muted">Keine anstehenden Ereignisse.</p>}
          </ul>
        </Card>

        <Card>
          <CardTitle className="mb-1">Letzte Aktivitäten</CardTitle>
          <ul className="flex flex-col divide-y divide-border-subtle">
            {(historyRes.data ?? []).map((h) => (
              <li key={h.id} className="flex gap-2.5 py-2.5">
                {/* Dot aligned to the first line of text, not centred on the
                    whole row, so it stays put as descriptions wrap. */}
                <span className={`mt-1.5 h-2 w-2 shrink-0 rounded-full ${DOT_STYLES[h.event_type] ?? "bg-ink-muted"}`} aria-hidden />
                <div className="min-w-0 flex-1">
                  <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
                    <span className="text-sm font-semibold text-ink">{employeeNameById.get(h.employee_id) ?? "Unbekannt"}</span>
                    <span className={`rounded-full px-2 py-0.5 text-[11px] font-semibold ${actionBadgeStyle(h.event_type)}`}>
                      {h.event_type}
                    </span>
                  </div>
                  <p className="mt-0.5 text-xs leading-relaxed text-ink-muted">{h.description}</p>
                </div>
              </li>
            ))}
            {(historyRes.data ?? []).length === 0 && <p className="py-2 text-sm text-ink-muted">Keine Aktivitäten vorhanden.</p>}
          </ul>
        </Card>
      </div>
    </div>
  );
}
