import type { RequestHandler } from '@sveltejs/kit';
import { createBeatLeaderAPI } from '$lib/server/beatleader';
import { getBeatLeaderSessionCookieHeader, getValidAccessToken } from '$lib/server/beatleaderAuth';
import { getSteamIdFromCookies } from '$lib/server/steam';

import { promises as fsp } from 'fs';
import path from 'path';

type BeatLeaderScore = {
  timeset?: string | number;
  leaderboard?: {
    id?: string | number | null;
    leaderboardId?: string | number | null;
    song?: { hash?: string | null };
    difficulty?: { value?: number | string | null; modeName?: string | null };
  };
};

function normalizeDifficultyName(value: number | string | null | undefined): string {
  if (value === null || value === undefined) return 'ExpertPlus';
  if (typeof value === 'string') {
    const v = value.toLowerCase();
    if (v.includes('expertplus') || v === 'expertplus' || v === 'ex+' || v.includes('ex+')) return 'ExpertPlus';
    if (v.includes('expert')) return 'Expert';
    if (v.includes('hard')) return 'Hard';
    if (v.includes('normal')) return 'Normal';
    if (v.includes('easy')) return 'Easy';
    return value;
  }
  switch (value) {
    case 1:
      return 'Easy';
    case 3:
      return 'Normal';
    case 5:
      return 'Hard';
    case 7:
      return 'Expert';
    case 9:
      return 'ExpertPlus';
    default:
      return 'ExpertPlus';
  }
}

function parseTimeset(ts: string | number | undefined): number {
  if (ts === undefined) return 0;
  if (typeof ts === 'number') return ts;
  const n = Number(ts);
  return Number.isFinite(n) ? n : 0;
}

const DATA_DIR = path.join(process.cwd(), '.data', 'beatleader-cache', 'player-scores');

async function ensureDir(dir: string): Promise<void> {
  await fsp.mkdir(dir, { recursive: true });
}

async function readJson<T>(filePath: string): Promise<T | null> {
  try {
    const buf = await fsp.readFile(filePath, 'utf8');
    return JSON.parse(buf) as T;
  } catch {
    return null;
  }
}

async function writeJson(filePath: string, data: unknown): Promise<void> {
  const dir = path.dirname(filePath);
  await ensureDir(dir);
  const tmp = filePath + '.tmp';
  await fsp.writeFile(tmp, JSON.stringify(data));
  await fsp.rename(tmp, filePath);
}

function getCacheFilePath(playerId: string, diff: string): string {
  const safeId = encodeURIComponent(playerId);
  const safeDiff = encodeURIComponent(diff);
  return path.join(DATA_DIR, `${safeId}__${safeDiff}.json`);
}

function scoreKey(score: BeatLeaderScore): string | null {
  const lb: any = score?.leaderboard ?? {};
  const lbId = lb?.id ?? lb?.leaderboardId;
  if (lbId != null) return `lb:${String(lbId)}`;
  const hash = lb?.song?.hash ? String(lb.song.hash).toLowerCase() : undefined;
  const diff = normalizeDifficultyName(lb?.difficulty?.value ?? undefined);
  const mode = lb?.difficulty?.modeName ?? 'Standard';
  if (hash) return `hdm:${hash}|${diff}|${mode}`;
  const ts = parseTimeset(score?.timeset);
  return ts ? `ts:${ts}` : null;
}

export const GET: RequestHandler<{ id: string }> = async ({ fetch, params, url, cookies }) => {
  type AuthMode = 'steam' | 'oauth' | 'session' | 'auto' | 'none';
  const authMode = (url.searchParams.get('auth') as AuthMode | null) ?? 'steam';

  let token: string | undefined;
  let websiteCookieHeader: string | undefined;

  if (authMode === 'oauth') {
    const maybeToken = await getValidAccessToken(cookies);
    token = maybeToken ?? undefined;
  } else if (authMode === 'session') {
    websiteCookieHeader = getBeatLeaderSessionCookieHeader(cookies) ?? undefined;
  } else if (authMode === 'steam') {
    websiteCookieHeader = getBeatLeaderSessionCookieHeader(cookies) ?? undefined;
    if (!websiteCookieHeader) {
      const steamId = getSteamIdFromCookies(cookies);
      if (steamId) {
        try {
          const probe = await fetch('https://api.beatleader.com/user', { credentials: 'include' as RequestCredentials });
          const setCookieHeaders = (probe.headers as any).getSetCookie?.() ?? probe.headers.get('set-cookie')?.split(',') ?? [];
          const { setBeatLeaderSessionFromSetCookieHeaders } = await import('$lib/server/beatleaderAuth');
          setBeatLeaderSessionFromSetCookieHeaders(cookies, Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders]);
          websiteCookieHeader = getBeatLeaderSessionCookieHeader(cookies) ?? undefined;
        } catch {}
      }
    }
  } else if (authMode === 'auto') {
    const maybeToken = await getValidAccessToken(cookies);
    if (maybeToken) {
      token = maybeToken;
    } else {
      websiteCookieHeader = getBeatLeaderSessionCookieHeader(cookies) ?? undefined;
      if (!websiteCookieHeader) {
        const steamId = getSteamIdFromCookies(cookies);
        if (steamId) {
          try {
            const probe = await fetch('https://api.beatleader.com/user', { credentials: 'include' as RequestCredentials });
            const setCookieHeaders = (probe.headers as any).getSetCookie?.() ?? probe.headers.get('set-cookie')?.split(',') ?? [];
            const { setBeatLeaderSessionFromSetCookieHeaders } = await import('$lib/server/beatleaderAuth');
            setBeatLeaderSessionFromSetCookieHeaders(cookies, Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders]);
            websiteCookieHeader = getBeatLeaderSessionCookieHeader(cookies) ?? undefined;
          } catch {}
        }
      }
    }
  }

  const api = createBeatLeaderAPI(fetch, token, websiteCookieHeader);
  const playerId = params.id as string;
  const diff = (url.searchParams.get('diff') ?? 'ExpertPlus');
  const cutoffEpochParam = url.searchParams.get('cutoffEpoch');
  const cutoffEpoch = cutoffEpochParam ? Number(cutoffEpochParam) : undefined;
  const pageSize = 100;
  const maxPages = url.searchParams.get('maxPages') ? Number(url.searchParams.get('maxPages')) : 100;

  try {
    const filePath = getCacheFilePath(playerId, diff);
    const existing = await readJson<{ scores: BeatLeaderScore[]; updatedAt: number }>(filePath);
    const existingScores = Array.isArray(existing?.scores) ? existing!.scores : [];
    const existingNewest = existingScores.length > 0 ? Math.max(...existingScores.map((s) => parseTimeset(s.timeset))) : 0;

    // Fetch new pages until we reach an older-or-equal score than the newest cached
    let page = 1;
    const newScores: BeatLeaderScore[] = [];
    while (page <= maxPages) {
      const data = (await api.getPlayerScores(playerId, { page, count: pageSize, sortBy: 'date', order: 'desc' })) as any;
      const batch: BeatLeaderScore[] = Array.isArray(data?.data) ? data.data : [];
      if (batch.length === 0) break;
      // Filter by requested diff immediately
      const filtered = batch.filter((s) => normalizeDifficultyName(s?.leaderboard?.difficulty?.value ?? undefined) === diff);
      newScores.push(...filtered);
      const last = batch[batch.length - 1];
      const lastTs = parseTimeset(last?.timeset);
      if (existingNewest && lastTs <= existingNewest) break;
      if (cutoffEpoch !== undefined && lastTs < cutoffEpoch) break;
      page += 1;
    }

    // Merge new with existing, dedupe by scoreboard key
    const mergedMap = new Map<string, BeatLeaderScore>();
    for (const s of existingScores) {
      const k = scoreKey(s);
      if (k) mergedMap.set(k, s);
    }
    for (const s of newScores) {
      const k = scoreKey(s);
      if (k) mergedMap.set(k, s);
    }
    const merged = Array.from(mergedMap.values());
    // Keep only requested diff in the stored file to reduce size
    const stored = merged.filter((s) => normalizeDifficultyName(s?.leaderboard?.difficulty?.value ?? undefined) === diff);

    // Persist
    await writeJson(filePath, { scores: stored, updatedAt: Date.now() });

    // Prepare response: filter by cutoffEpoch if provided and sort desc by timeset
    const responseScores = stored
      .filter((s) => (cutoffEpoch !== undefined ? parseTimeset(s.timeset) >= cutoffEpoch : true))
      .sort((a, b) => parseTimeset(b.timeset) - parseTimeset(a.timeset));

    return new Response(JSON.stringify({ data: responseScores }), { headers: { 'content-type': 'application/json' } });
  } catch (err) {
    const message = err instanceof Error ? err.message : 'Unknown error';
    return new Response(JSON.stringify({ error: message }), { status: 500, headers: { 'content-type': 'application/json' } });
  }
};


