import { GetConfiguration } from '@octane/renderer';
import { getAccessToken } from '../auth';

export interface EmuStatsOverview {
    uptimeSeconds: number;
    lastRefreshEpochMs: number;
    guiStatus: string;
    memoryUsedMb: number;
    memoryMaxMb: number;
    memoryAllocatedMb: number;
    memoryUsagePercent: number;
    cpuLoadPercent: number;
    activeOsThreads: number;
    connectedPlayers: number;
    loadedRooms: number;
    wiredTickables: number;
    peakPlayers: number;
    activeWebSocketSessions: number;
    peakWebSocketSessions: number;
    averageRoomCycleMs: number;
    worstRoomCycleMs: number;
    worstRoomCycleRoomId: number;
    worstRoomCycleRoomName: string;
    delayedEventsPending: number;
    overloadedWiredRooms: number;
    heavyWiredRooms: number;
    wiredActivityPerSecond: number;
}

export interface EmuStatsMemoryPoint {
    timestamp: number;
    usedMb: number;
    maxMb: number;
    usagePercent: number;
}

export interface EmuStatsUserRow {
    id: number;
    username: string;
    rank: string;
    /** Sent by servers before the emustats permission; the page no longer shows it. */
    credits?: number;
    roomId: number;
}

export interface EmuStatsRoomRow {
    roomId: number;
    name: string;
    players: number;
    items: number;
    tickables: number;
    cpuMs: number;
    estimatedRamKb: number;
    thread: string;
}

export interface EmuStatsWiredRow {
    roomId: number;
    averageTickMs: number;
    peakTickMs: number;
    usagePercent: number;
    delayedEventsPending: number;
    overloaded: boolean;
    heavy: boolean;
}

export interface EmuStatsWiredTopRoomRow {
    roomId: number;
    name: string;
    usagePercent: number;
    averageTickMs: number;
    peakTickMs: number;
    delayedEventsPending: number;
    activityPerSecond: number;
    heavy: boolean;
}

export interface EmuStatsDatabasePool {
    activeConnections: number;
    idleConnections: number;
    totalConnections: number;
    waitingThreads: number;
    maxConnections: number;
}

export interface EmuStatsScheduler {
    queuedTasks: number;
    activeThreads: number;
    poolSize: number;
    completedTasks: number;
    running: boolean;
}

export interface EmuStatsNetwork {
    incomingPacketsPerSecond: number;
    outgoingPacketsPerSecond: number;
    incomingKilobytesPerSecond: number;
    outgoingKilobytesPerSecond: number;
    totalIncomingPackets: number;
    totalOutgoingPackets: number;
}

export interface EmuStatsGarbageCollector {
    totalCollections: number;
    totalCollectionTimeMs: number;
    collectionsSinceLastSample: number;
    lastObservedPauseMs: number;
    sampledAtEpochMs: number;
}

export interface EmuStatsSnapshot {
    overview: EmuStatsOverview;
    memoryHistory: EmuStatsMemoryPoint[];
    users: EmuStatsUserRow[];
    rooms: EmuStatsRoomRow[];
    wired: EmuStatsWiredRow[];
    wiredTopRooms: EmuStatsWiredTopRoomRow[];
    databasePool: EmuStatsDatabasePool;
    scheduler: EmuStatsScheduler;
    network: EmuStatsNetwork;
    garbageCollector: EmuStatsGarbageCollector;
}

const interpolate = (value: string): string => {
    try {
        return GetConfiguration().interpolate(value);
    } catch {
        return value;
    }
};

const getUrl = (): string => {
    const configured = GetConfiguration().getValue<string>('emustats.endpoint', '${api.url}/api/emustats');

    return interpolate(configured);
};

const buildHeaders = (): Record<string, string> => {
    const headers: Record<string, string> = {
        Accept: 'application/json',
        'X-Requested-With': 'OctaneEmuStats'
    };

    const token = getAccessToken();

    if (token) headers.Authorization = `Bearer ${token}`;

    return headers;
};

let cacheValue: EmuStatsSnapshot = null;
// The snapshot belongs to the session that fetched it: after a logout in the same tab, the next user used to see the
// last staff snapshot without the permission to fetch one.
let cacheOwner: string = null;

const parseJson = async <T>(response: Response): Promise<T> => {
    const text = await response.text();

    if (!text) return {} as T;

    try {
        return JSON.parse(text) as T;
    } catch {
        throw new Error('Invalid emulator stats response.');
    }
};

export const fetchEmuStats = async (force = false): Promise<EmuStatsSnapshot> => {
    const owner = getAccessToken() ?? '';

    if (!force && cacheValue && cacheOwner === owner) return cacheValue;

    const response = await fetch(getUrl(), {
        method: 'GET',
        credentials: 'include',
        headers: buildHeaders()
    });

    const payload = await parseJson<EmuStatsSnapshot & { error?: string }>(response);

    if (!response.ok) {
        throw new Error(payload?.error || `Request failed (${response.status}).`);
    }

    cacheValue = payload;
    cacheOwner = owner;

    return payload;
};

export const getCachedEmuStats = (): EmuStatsSnapshot => {
    return cacheOwner === (getAccessToken() ?? '') ? cacheValue : null;
};
