import { Capacitor } from "@capacitor/core";
import type { HunterSave, SaveSlotId } from "./types";
import type { BossId } from "../game/types";

export interface OnlineAccount {
  id: number;
  username: string;
}

export interface OnlineSaveSlot {
  slotId: SaveSlotId;
  save: HunterSave;
  updatedAt: string;
}

export interface LeaderboardEntry {
  rank: number;
  username: string;
  hunterName: string;
  slotId: SaveSlotId;
  value: number;
}

export interface LeaderboardResult {
  kind: "boss" | "roguelike" | "rogue-trials-endless";
  bossId?: BossId;
  top: LeaderboardEntry[];
  current: LeaderboardEntry | null;
}

interface TokenStorage {
  getItem(key: string): string | null;
  setItem(key: string, value: string): void;
  removeItem(key: string): void;
}

type Requester = typeof fetch;

const TOKEN_KEY = "i-want-to-heal:auth-token:v1";
const PRODUCTION_API_URL = "https://iwanttoheal.phenomrom.com";

function defaultApiBaseUrl() {
  const configured = String(import.meta.env.VITE_API_BASE_URL ?? "");
  if (configured) return configured;
  return Capacitor.isNativePlatform() ? PRODUCTION_API_URL : "";
}

function browserStorage(): TokenStorage {
  if (typeof localStorage !== "undefined") return localStorage;
  const memory = new Map<string, string>();
  return {
    getItem: (key) => memory.get(key) ?? null,
    setItem: (key, value) => { memory.set(key, value); },
    removeItem: (key) => { memory.delete(key); },
  };
}

export class OnlineApiError extends Error {
  constructor(message: string, readonly status: number) {
    super(message);
  }
}

export class OnlineRepository {
  private readonly baseUrl: string;

  constructor(
    private readonly requester: Requester = (...args) => fetch(...args),
    private readonly storage: TokenStorage = browserStorage(),
    baseUrl: string = defaultApiBaseUrl(),
  ) {
    this.baseUrl = baseUrl.replace(/\/$/, "");
  }

  private async request<T>(path: string, init: RequestInit = {}): Promise<T> {
    const headers = new Headers(init.headers);
    const token = this.storage.getItem(TOKEN_KEY);
    if (token) headers.set("Authorization", `Bearer ${token}`);
    let response: Response;
    try {
      response = await this.requester(`${this.baseUrl}${path}`, { ...init, headers });
    } catch {
      throw new OnlineApiError("Online server is unreachable.", 0);
    }
    const body = await response.json().catch(() => ({})) as { error?: string } & T;
    if (!response.ok) throw new OnlineApiError(body.error ?? "Online request failed.", response.status);
    return body;
  }

  private rememberAuth(result: { account: OnlineAccount; token: string }) {
    this.storage.setItem(TOKEN_KEY, result.token);
    return result.account;
  }

  async register(username: string, password: string): Promise<OnlineAccount> {
    return this.rememberAuth(await this.request<{ account: OnlineAccount; token: string }>("/api/auth/register", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ username, password }),
    }));
  }

  async login(username: string, password: string): Promise<OnlineAccount> {
    return this.rememberAuth(await this.request<{ account: OnlineAccount; token: string }>("/api/auth/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ username, password }),
    }));
  }

  async session(): Promise<OnlineAccount | null> {
    if (!this.storage.getItem(TOKEN_KEY)) return null;
    try {
      return (await this.request<{ account: OnlineAccount }>("/api/auth/session")).account;
    } catch (error) {
      if (error instanceof OnlineApiError && error.status === 401) this.storage.removeItem(TOKEN_KEY);
      if (error instanceof OnlineApiError && error.status === 401) return null;
      throw error;
    }
  }

  async logout(): Promise<void> {
    try { await this.request("/api/auth/logout", { method: "POST" }); }
    finally { this.storage.removeItem(TOKEN_KEY); }
  }

  async listSaves(): Promise<OnlineSaveSlot[]> {
    return (await this.request<{ slots: OnlineSaveSlot[] }>("/api/saves")).slots;
  }

  async writeSave(save: HunterSave): Promise<HunterSave> {
    return (await this.request<{ save: HunterSave }>(`/api/saves/${save.slotId}`, {
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ save }),
    })).save;
  }

  async readSave(slotId: SaveSlotId): Promise<HunterSave | null> {
    return (await this.request<{ save: HunterSave | null }>(`/api/saves/${slotId}`)).save;
  }

  bossLeaderboard(bossId: BossId, slotId: SaveSlotId): Promise<LeaderboardResult> {
    return this.request(`/api/leaderboards/boss/${encodeURIComponent(bossId)}?slot=${slotId}`);
  }

  roguelikeLeaderboard(slotId: SaveSlotId): Promise<LeaderboardResult> {
    return this.request(`/api/leaderboards/roguelike?slot=${slotId}`);
  }

  rogueTrialsEndlessLeaderboard(slotId: SaveSlotId): Promise<LeaderboardResult> {
    return this.request(`/api/leaderboards/rogue-trials-endless?slot=${slotId}`);
  }
}

export const onlineRepository = new OnlineRepository();
