import fs from "fs";
import path from "path";
import { HistoryKey, UserKey, CharName, Nation, ClassKey } from "@types";
import { Config } from "@systems/config";
import { Bringer } from "@systems/bringer";
import { Nations } from "@systems/nations";
import { Store } from "@systems/store";
import { Paths } from "@paths";
import { Runtime } from "@systems/runtime";

// ─── Runtime ──────────────────────────────────────────────────────────────────
Runtime.phase("load", () => WRank.load(), { name: "WRank.load" });

const WRANK_PATH = path.join(__dirname, "../../data/wrank.json");
let _data: WRankData = {};

/** Raw shape stored in wrank.json */
interface SerializableWRankEntry {
  userKey:       UserKey;
  characterName: CharName;
  class:         ClassKey;
  nation:        Nation;
  weeklyPoints:  number;
  tgCount:       number;
  currentRank:   number;
  previousRank?: number;
}

export interface WRankWeek {
  weekKey: string;            // "2026-W22"
  entries:    Record<"capella" | "procyon", SerializableWRankEntry[]>; // still serializable for now
  scoreIndex: Record<CharName, HistoryKey[]>;
  bringer: {
    capella: string | null;   // userKey of bringer, null if none qualified
    procyon: string | null;
    capellaOverride?: string; // manually set by officer
    procyonOverride?: string;
  };
}

export interface WRankData {
  [weekKey: string]: WRankWeek;
}

export function loadWRank(): void {
  _data = Store.readOrDefault<WRankData>(Paths.data("wrank.json"), {});
}

export function saveWRank(): void {
  Store.write(Paths.data("wrank.json"), _data);
}

export function getWeekKey(date: Date = new Date()): string {
  const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
  d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
  const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
  const week = Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
  return `${d.getUTCFullYear()}-W${String(week).padStart(2, "0")}`;
}

function ensureWeek(weekKey: string): WRankWeek {
  if (!_data[weekKey]) {
    _data[weekKey] = {
      weekKey,
      entries: { capella: [], procyon: [] },
      scoreIndex: {},
      bringer: { capella: null, procyon: null },
    };
  }
  return _data[weekKey];
}

export function getCurrentWeek(): WRankWeek {
  return ensureWeek(WRank.weekKey());
}

export function getWeek(weekKey: string): WRankWeek | null {
  return _data[weekKey] ?? null;
}

// Add or update a score submission for a player
export function recordScore(
  userKey: string,
  characterName: string,
  cls: ClassKey,
  nation: Nation,
  pts: number,
  historyKey: string // e.g. "2026-05-31-20"
): void {
  const weekKey = WRank.weekKey();
  const week    = ensureWeek(weekKey);
  const list    = week.entries[Nations.key(nation)];

  const existing = list.find((e) => e.characterName === characterName);

  if (existing) {
    // Check if this slot was already counted
    const alreadyCounted = week.scoreIndex[userKey]?.includes(historyKey);
    if (!alreadyCounted) {
      existing.weeklyPoints += pts;
      existing.tgCount      += 1;
    } else {
      // Overwrite: recalculate by removing old pts for this slot
      // We'll just set the new pts — full recalc would require reading history
      // For now, simple overwrite of total is handled at score submission level
      existing.weeklyPoints = existing.weeklyPoints - (existing.weeklyPoints / existing.tgCount) + pts;
    }
    existing.characterName = characterName;
    existing.class         = cls;
    existing.nation        = nation;
  } else {
    list.push({
      userKey,
      characterName,
      class: cls,
      nation,
      weeklyPoints: pts,
      tgCount: 1,
      currentRank: 0,
      previousRank: undefined,
    });
  }

  // Update score index
  const indexKey = characterName;
  if (!week.scoreIndex[indexKey]) week.scoreIndex[indexKey] = [];
  if (!week.scoreIndex[indexKey].includes(historyKey)) {
    week.scoreIndex[indexKey].push(historyKey);
  }

  recomputeRanks(week, nation);
  saveWRank();
}

function recomputeRanks(week: WRankWeek, nation: Nation): void {
  const list   = week.entries[nation.toLowerCase() as "capella" | "procyon"];
  const sorted = [...list].sort((a, b) => b.weeklyPoints - a.weeklyPoints);
  sorted.forEach((entry, i) => {
    const live    = list.find((e) => e.characterName === entry.characterName)!;
    const newRank = i + 1;
    // Only snapshot previousRank when rank actually changes
    if (live.currentRank !== 0 && live.currentRank !== newRank) {
      live.previousRank = live.currentRank;
    }
    live.currentRank = newRank;
  });
}

function updateBringer(week: WRankWeek): void {
  const goal = Config.get({ section: "wrank", key: "goal" });
  for (const nation of ["capella", "procyon"] as const) {
    // Don't overwrite manual override
    if (nation === "capella" && week.bringer.capellaOverride) continue;
    if (nation === "procyon" && week.bringer.procyonOverride) continue;

    const qualified = week.entries[nation]
      .filter((e) => e.tgCount >= goal)
      .sort((a, b) => a.currentRank - b.currentRank);
    week.bringer[nation] = qualified[0]?.characterName ?? null;
  }
}

export function setBringerOverride(nation: Nation, charName: string): void {
  const week = ensureWeek(WRank.weekKey());
  if (nation === Nation.Capella) week.bringer.capellaOverride = charName;
  else week.bringer.procyonOverride = charName;
  saveWRank();
}

export function clearBringerOverride(nation: Nation): void {
  const week = ensureWeek(WRank.weekKey());
  if (nation === Nation.Capella) delete week.bringer.capellaOverride;
  else delete week.bringer.procyonOverride;
  updateBringer(week);
  saveWRank();
}

export function getBringer(nation: Nation): string | null {
  const week = getCurrentWeek();
  if (nation === Nation.Capella) return week.bringer.capellaOverride ?? week.bringer.capella;
  return week.bringer.procyonOverride ?? week.bringer.procyon;
}

export function getEntry(characterName: string, nation: Nation): SerializableWRankEntry | null {
  const week = getCurrentWeek();
  const list = week.entries[nation.toLowerCase() as "capella" | "procyon"];
  console.log(`[getEntry] weekKey=${week.weekKey} nation=${nation} listLength=${list?.length} looking for=${characterName}`);
  console.log(`[getEntry] available:`, list?.map(e => e.characterName));
  return list.find((e) => e.characterName === characterName) ?? null;
}

// Called every Monday 00:00 by cron
export function resetWeek(): void {
  // Week is already archived in _data by weekKey — just ensure next week exists
  const prevWeekKey = WRank.weekKey(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000));
  const prevWeek    = _data[prevWeekKey];
  const newWeek     = ensureWeek(WRank.weekKey(new Date()));

  if (prevWeek) {
    // Carry Bringer forward — W.Rank 1 with goal achieved becomes new Bringer
    Bringer.update({ week: prevWeek });
    for (const nation of [Nation.Capella, Nation.Procyon]) {
      const key     = nation === Nation.Capella ? "capella" : "procyon";
      const bringer = prevWeek.bringer[key];
      if (bringer) {
        // Set as override in new week so it carries forward
        newWeek.bringer[key] = bringer;
      }
    }
  }
  WRank.save();
}

function snapshot(): void {
  const week = WRank.currentWeek();
  for (const nation of ["capella", "procyon"] as const) {
    for (const entry of week.entries[nation]) {
      if (entry.currentRank !== 0) {
        entry.previousRank = entry.currentRank;
      }
    }
  }
  WRank.save();
  console.log("[WRank] Midnight snapshot complete.");
}


export const WRank = {
  save:         saveWRank,
  load:         loadWRank,
  currentWeek:  getCurrentWeek,
  weekFromKey:  getWeek,
  weekKey:      getWeekKey,
                recordScore,
  entry:        getEntry,
                resetWeek,
                snapshot,
};