"use client";

import type {
  Account,
  AchievementUnlock,
  AnalyticsEvent,
  AuditLogEntry,
  FavoriteEntry,
  LeaderboardEntry,
  PlayHistoryEntry,
  Profile,
} from "./types";
import { newId } from "./ids";

const DB_NAME = "hollowcade";
const DB_VERSION = 1;
const ACTIVE_KEY = "hollowcade_active_profile";
const ACTIVE_ACCOUNT_KEY = "hollowcade_active_account";

type StoreName =
  | "profiles"
  | "playHistory"
  | "favorites"
  | "achievements"
  | "accounts"
  | "leaderboards"
  | "analytics"
  | "audit";

function openDb(): Promise<IDBDatabase> {
  return new Promise((resolve, reject) => {
    if (typeof indexedDB === "undefined") {
      reject(new Error("IndexedDB unavailable"));
      return;
    }
    const req = indexedDB.open(DB_NAME, DB_VERSION);
    req.onupgradeneeded = () => {
      const db = req.result;
      if (!db.objectStoreNames.contains("profiles")) {
        db.createObjectStore("profiles", { keyPath: "id" });
      }
      if (!db.objectStoreNames.contains("playHistory")) {
        const s = db.createObjectStore("playHistory", {
          keyPath: ["profileId", "gameSlug"],
        });
        s.createIndex("byProfile", "profileId", { unique: false });
        s.createIndex("byLastPlayed", "lastPlayedAt", { unique: false });
      }
      if (!db.objectStoreNames.contains("favorites")) {
        const s = db.createObjectStore("favorites", {
          keyPath: ["profileId", "gameSlug"],
        });
        s.createIndex("byProfile", "profileId", { unique: false });
      }
      if (!db.objectStoreNames.contains("achievements")) {
        const s = db.createObjectStore("achievements", {
          keyPath: ["profileId", "achievementId"],
        });
        s.createIndex("byProfile", "profileId", { unique: false });
      }
      if (!db.objectStoreNames.contains("accounts")) {
        const s = db.createObjectStore("accounts", { keyPath: "id" });
        s.createIndex("byUsername", "username", { unique: true });
      }
      if (!db.objectStoreNames.contains("leaderboards")) {
        const s = db.createObjectStore("leaderboards", { keyPath: "id" });
        s.createIndex("byGameKind", ["gameSlug", "kind"], { unique: false });
        s.createIndex("byAccountGameKind", ["accountId", "gameSlug", "kind"], {
          unique: true,
        });
      }
      if (!db.objectStoreNames.contains("analytics")) {
        const s = db.createObjectStore("analytics", { keyPath: "id" });
        s.createIndex("byAt", "at", { unique: false });
        s.createIndex("byType", "type", { unique: false });
      }
      if (!db.objectStoreNames.contains("audit")) {
        db.createObjectStore("audit", { keyPath: "id" });
      }
    };
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error ?? new Error("IDB open failed"));
  });
}

function txDone(tx: IDBTransaction): Promise<void> {
  return new Promise((resolve, reject) => {
    tx.oncomplete = () => resolve();
    tx.onerror = () => reject(tx.error);
    tx.onabort = () => reject(tx.error ?? new Error("aborted"));
  });
}

async function withStore<T>(
  store: StoreName,
  mode: IDBTransactionMode,
  fn: (s: IDBObjectStore) => IDBRequest<T> | void,
): Promise<T | undefined> {
  const db = await openDb();
  const tx = db.transaction(store, mode);
  const s = tx.objectStore(store);
  const req = fn(s);
  if (!req) {
    await txDone(tx);
    return undefined;
  }
  const result = await new Promise<T>((resolve, reject) => {
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
  await txDone(tx);
  return result;
}

async function getAll<T>(store: StoreName): Promise<T[]> {
  return (await withStore<T[]>(store, "readonly", (s) => s.getAll())) ?? [];
}

async function getByIndex<T>(
  store: StoreName,
  index: string,
  key: IDBValidKey,
): Promise<T[]> {
  const db = await openDb();
  const tx = db.transaction(store, "readonly");
  const req = tx.objectStore(store).index(index).getAll(key);
  const result = await new Promise<T[]>((resolve, reject) => {
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
  await txDone(tx);
  return result;
}

export function getActiveProfileId(): string | null {
  if (typeof localStorage === "undefined") return null;
  return localStorage.getItem(ACTIVE_KEY);
}

export function setActiveProfileId(id: string) {
  localStorage.setItem(ACTIVE_KEY, id);
}

export function getActiveAccountId(): string | null {
  if (typeof localStorage === "undefined") return null;
  return localStorage.getItem(ACTIVE_ACCOUNT_KEY);
}

export function setActiveAccountId(id: string | null) {
  if (id) localStorage.setItem(ACTIVE_ACCOUNT_KEY, id);
  else localStorage.removeItem(ACTIVE_ACCOUNT_KEY);
}

export async function listProfiles(): Promise<Profile[]> {
  const all = await getAll<Profile>("profiles");
  return all.sort((a, b) => a.createdAt - b.createdAt);
}

export async function getProfile(id: string): Promise<Profile | undefined> {
  return withStore<Profile>("profiles", "readonly", (s) => s.get(id));
}

export async function putProfile(profile: Profile): Promise<void> {
  await withStore("profiles", "readwrite", (s) => s.put(profile));
}

export async function deleteProfile(id: string): Promise<void> {
  const hist = await getByIndex<PlayHistoryEntry>("playHistory", "byProfile", id);
  const favs = await getByIndex<FavoriteEntry>("favorites", "byProfile", id);
  const achs = await getByIndex<AchievementUnlock>(
    "achievements",
    "byProfile",
    id,
  );
  const db = await openDb();
  const tx = db.transaction(
    ["profiles", "playHistory", "favorites", "achievements"],
    "readwrite",
  );
  tx.objectStore("profiles").delete(id);
  for (const h of hist) {
    tx.objectStore("playHistory").delete([h.profileId, h.gameSlug]);
  }
  for (const f of favs) {
    tx.objectStore("favorites").delete([f.profileId, f.gameSlug]);
  }
  for (const a of achs) {
    tx.objectStore("achievements").delete([a.profileId, a.achievementId]);
  }
  await txDone(tx);
  if (getActiveProfileId() === id) {
    const remaining = await listProfiles();
    if (remaining[0]) setActiveProfileId(remaining[0].id);
  }
}

export async function ensureDefaultProfile(): Promise<Profile> {
  const existing = await listProfiles();
  if (existing.length > 0) {
    const active = getActiveProfileId();
    if (!active || !existing.some((p) => p.id === active)) {
      setActiveProfileId(existing[0].id);
    }
    return existing.find((p) => p.id === getActiveProfileId()) ?? existing[0];
  }
  const profile: Profile = {
    id: newId("profile"),
    displayName: "Player 1",
    avatarId: "default",
    createdAt: Date.now(),
    settings: {},
  };
  await putProfile(profile);
  setActiveProfileId(profile.id);
  return profile;
}

export async function createProfile(displayName: string): Promise<Profile> {
  const profile: Profile = {
    id: newId("profile"),
    displayName: displayName.trim() || "Player",
    avatarId: "default",
    createdAt: Date.now(),
    settings: {},
  };
  await putProfile(profile);
  return profile;
}

export async function getHistoryForProfile(
  profileId: string,
): Promise<PlayHistoryEntry[]> {
  const rows = await getByIndex<PlayHistoryEntry>(
    "playHistory",
    "byProfile",
    profileId,
  );
  return rows.sort((a, b) => b.lastPlayedAt - a.lastPlayedAt);
}

export async function getHistoryEntry(
  profileId: string,
  gameSlug: string,
): Promise<PlayHistoryEntry | undefined> {
  return withStore<PlayHistoryEntry>("playHistory", "readonly", (s) =>
    s.get([profileId, gameSlug]),
  );
}

export async function recordPlaySession(
  profileId: string,
  gameSlug: string,
  seconds: number,
): Promise<PlayHistoryEntry> {
  const prev = await getHistoryEntry(profileId, gameSlug);
  const entry: PlayHistoryEntry = {
    profileId,
    gameSlug,
    lastPlayedAt: Date.now(),
    sessions: (prev?.sessions ?? 0) + (seconds > 0 ? 1 : 0),
    totalSeconds: (prev?.totalSeconds ?? 0) + Math.max(0, Math.floor(seconds)),
  };
  // If this is a mid-session flush, don't inflate session count every flush
  if (prev && seconds > 0 && Date.now() - prev.lastPlayedAt < 120_000) {
    entry.sessions = prev.sessions;
  }
  await withStore("playHistory", "readwrite", (s) => s.put(entry));
  return entry;
}

/** Add play seconds without bumping session count (periodic flush). */
export async function addPlaySeconds(
  profileId: string,
  gameSlug: string,
  seconds: number,
): Promise<PlayHistoryEntry> {
  const prev = await getHistoryEntry(profileId, gameSlug);
  const add = Math.max(0, Math.floor(seconds));
  const entry: PlayHistoryEntry = {
    profileId,
    gameSlug,
    lastPlayedAt: Date.now(),
    sessions: prev?.sessions ?? 1,
    totalSeconds: (prev?.totalSeconds ?? 0) + add,
  };
  if (!prev) entry.sessions = 1;
  await withStore("playHistory", "readwrite", (s) => s.put(entry));
  return entry;
}

export async function startPlaySession(
  profileId: string,
  gameSlug: string,
): Promise<PlayHistoryEntry> {
  const prev = await getHistoryEntry(profileId, gameSlug);
  const entry: PlayHistoryEntry = {
    profileId,
    gameSlug,
    lastPlayedAt: Date.now(),
    sessions: (prev?.sessions ?? 0) + 1,
    totalSeconds: prev?.totalSeconds ?? 0,
  };
  await withStore("playHistory", "readwrite", (s) => s.put(entry));
  return entry;
}

export async function getFavorites(
  profileId: string,
): Promise<FavoriteEntry[]> {
  return getByIndex<FavoriteEntry>("favorites", "byProfile", profileId);
}

export async function isFavorite(
  profileId: string,
  gameSlug: string,
): Promise<boolean> {
  const row = await withStore<FavoriteEntry>("favorites", "readonly", (s) =>
    s.get([profileId, gameSlug]),
  );
  return Boolean(row);
}

export async function toggleFavorite(
  profileId: string,
  gameSlug: string,
): Promise<boolean> {
  const existing = await withStore<FavoriteEntry>("favorites", "readonly", (s) =>
    s.get([profileId, gameSlug]),
  );
  if (existing) {
    await withStore("favorites", "readwrite", (s) =>
      s.delete([profileId, gameSlug]),
    );
    return false;
  }
  const entry: FavoriteEntry = {
    profileId,
    gameSlug,
    favoritedAt: Date.now(),
  };
  await withStore("favorites", "readwrite", (s) => s.put(entry));
  return true;
}

export async function getUnlocks(
  profileId: string,
): Promise<AchievementUnlock[]> {
  return getByIndex<AchievementUnlock>("achievements", "byProfile", profileId);
}

export async function unlockAchievement(
  profileId: string,
  achievementId: string,
): Promise<AchievementUnlock | null> {
  const existing = await withStore<AchievementUnlock>(
    "achievements",
    "readonly",
    (s) => s.get([profileId, achievementId]),
  );
  if (existing) return null;
  const row: AchievementUnlock = {
    profileId,
    achievementId,
    unlockedAt: Date.now(),
  };
  await withStore("achievements", "readwrite", (s) => s.put(row));
  return row;
}

export async function putHistoryEntry(entry: PlayHistoryEntry): Promise<void> {
  await withStore("playHistory", "readwrite", (s) => s.put(entry));
}

export async function putFavoriteEntry(entry: FavoriteEntry): Promise<void> {
  await withStore("favorites", "readwrite", (s) => s.put(entry));
}

export async function putAchievementUnlock(
  entry: AchievementUnlock,
): Promise<void> {
  await withStore("achievements", "readwrite", (s) => s.put(entry));
}

export async function listAccounts(): Promise<Account[]> {
  const all = await getAll<Account>("accounts");
  return all.map(normalizeAccount);
}

export async function deleteAccount(id: string): Promise<void> {
  await deleteLeaderboardForAccount(id);
  await withStore("accounts", "readwrite", (s) => s.delete(id));
  if (getActiveAccountId() === id) setActiveAccountId(null);
}

export async function getAccount(id: string): Promise<Account | undefined> {
  const row = await withStore<Account>("accounts", "readonly", (s) => s.get(id));
  return row ? normalizeAccount(row) : undefined;
}

function normalizeAccount(raw: Account): Account {
  return {
    ...raw,
    avatarId: raw.avatarId ?? "default",
    settings: raw.settings ?? {},
    preferredUi: raw.preferredUi ?? "classic",
    needsUiSetup: raw.needsUiSetup ?? false,
    authProvider: raw.authProvider ?? "local",
  };
}

export async function getAccountByUsername(
  username: string,
): Promise<Account | undefined> {
  const db = await openDb();
  const tx = db.transaction("accounts", "readonly");
  const req = tx
    .objectStore("accounts")
    .index("byUsername")
    .get(username.toLowerCase());
  const result = await new Promise<Account | undefined>((resolve, reject) => {
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
  await txDone(tx);
  return result ? normalizeAccount(result) : undefined;
}

export async function putAccount(account: Account): Promise<void> {
  await withStore("accounts", "readwrite", (s) => s.put(account));
}

export async function upsertLeaderboard(
  entry: Omit<LeaderboardEntry, "id" | "updatedAt"> & { id?: string },
): Promise<LeaderboardEntry> {
  const db = await openDb();
  const tx = db.transaction("leaderboards", "readwrite");
  const store = tx.objectStore("leaderboards");
  const idx = store.index("byAccountGameKind");
  const existingReq = idx.get([
    entry.accountId,
    entry.gameSlug,
    entry.kind,
  ]);
  const existing = await new Promise<LeaderboardEntry | undefined>(
    (resolve, reject) => {
      existingReq.onsuccess = () => resolve(existingReq.result);
      existingReq.onerror = () => reject(existingReq.error);
    },
  );

  let next: LeaderboardEntry;
  if (existing) {
    const better =
      entry.kind === "score" || entry.kind === "play_time" || entry.kind === "achievements"
        ? Math.max(existing.value, entry.value)
        : entry.value;
    next = {
      ...existing,
      displayName: entry.displayName,
      value: better,
      updatedAt: Date.now(),
    };
  } else {
    next = {
      id: entry.id ?? newId("lb"),
      accountId: entry.accountId,
      displayName: entry.displayName,
      gameSlug: entry.gameSlug,
      kind: entry.kind,
      value: entry.value,
      updatedAt: Date.now(),
    };
  }
  store.put(next);
  await txDone(tx);
  return next;
}

export async function getLeaderboard(
  gameSlug: string,
  kind: LeaderboardEntry["kind"],
  limit = 25,
): Promise<LeaderboardEntry[]> {
  const db = await openDb();
  const tx = db.transaction("leaderboards", "readonly");
  const req = tx
    .objectStore("leaderboards")
    .index("byGameKind")
    .getAll([gameSlug, kind]);
  const rows = await new Promise<LeaderboardEntry[]>((resolve, reject) => {
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
  await txDone(tx);
  return rows.sort((a, b) => b.value - a.value).slice(0, limit);
}

export async function getGlobalLeaderboard(
  kind: LeaderboardEntry["kind"],
  limit = 25,
): Promise<LeaderboardEntry[]> {
  const all = await getAll<LeaderboardEntry>("leaderboards");
  const byAccount = new Map<string, LeaderboardEntry>();
  for (const row of all.filter((r) => r.kind === kind)) {
    const prev = byAccount.get(row.accountId);
    if (!prev) {
      byAccount.set(row.accountId, {
        ...row,
        gameSlug: "_global",
        value: row.value,
      });
    } else {
      prev.value += row.value;
    }
  }
  return [...byAccount.values()]
    .sort((a, b) => b.value - a.value)
    .slice(0, limit);
}

export async function addAnalyticsEvent(
  event: Omit<AnalyticsEvent, "id"> & { id?: string },
): Promise<void> {
  const row: AnalyticsEvent = {
    id: event.id ?? newId("evt"),
    type: event.type,
    accountId: event.accountId,
    profileId: event.profileId,
    gameSlug: event.gameSlug,
    label: event.label,
    value: event.value,
    at: event.at,
  };
  await withStore("analytics", "readwrite", (s) => s.put(row));
}

export async function listAnalytics(since?: number): Promise<AnalyticsEvent[]> {
  const all = await getAll<AnalyticsEvent>("analytics");
  return all
    .filter((e) => (since ? e.at >= since : true))
    .sort((a, b) => b.at - a.at);
}

export async function addAudit(entry: Omit<AuditLogEntry, "id">): Promise<void> {
  const row: AuditLogEntry = { ...entry, id: newId("audit") };
  await withStore("audit", "readwrite", (s) => s.put(row));
}

export async function listAudit(): Promise<AuditLogEntry[]> {
  const all = await getAll<AuditLogEntry>("audit");
  return all.sort((a, b) => b.at - a.at);
}

export async function deleteLeaderboardForAccount(
  accountId: string,
): Promise<void> {
  const all = await getAll<LeaderboardEntry>("leaderboards");
  const db = await openDb();
  const tx = db.transaction("leaderboards", "readwrite");
  for (const row of all.filter((r) => r.accountId === accountId)) {
    tx.objectStore("leaderboards").delete(row.id);
  }
  await txDone(tx);
}

export type ProfileExport = {
  version: 1;
  profile: Profile;
  playHistory: PlayHistoryEntry[];
  favorites: FavoriteEntry[];
  achievements: AchievementUnlock[];
};

export async function exportProfileData(
  profileId: string,
): Promise<ProfileExport | null> {
  const profile = await getProfile(profileId);
  if (!profile) return null;
  return {
    version: 1,
    profile,
    playHistory: await getHistoryForProfile(profileId),
    favorites: await getFavorites(profileId),
    achievements: await getUnlocks(profileId),
  };
}

export async function importProfileData(
  data: ProfileExport,
  asNew = true,
): Promise<Profile> {
  const profile: Profile = asNew
    ? {
        ...data.profile,
        id: newId("profile"),
        displayName: `${data.profile.displayName} (import)`,
        createdAt: Date.now(),
      }
    : data.profile;
  await putProfile(profile);
  for (const h of data.playHistory) {
    await withStore("playHistory", "readwrite", (s) =>
      s.put({ ...h, profileId: profile.id }),
    );
  }
  for (const f of data.favorites) {
    await withStore("favorites", "readwrite", (s) =>
      s.put({ ...f, profileId: profile.id }),
    );
  }
  for (const a of data.achievements) {
    await withStore("achievements", "readwrite", (s) =>
      s.put({ ...a, profileId: profile.id }),
    );
  }
  return profile;
}
