import { config } from '@/config';

/**
 * Der einzige Weg nach draussen.
 *
 * `credentials: 'include'` sorgt dafuer, dass das Sitzungscookie mitgeht. Es gibt
 * bewusst keinen Authorization-Header und kein Token im Browser: die Sitzung
 * liegt vollstaendig im Backend, der Browser haelt nur die undurchsichtige Kennung.
 */

export class ApiError extends Error {
    constructor(
        message: string,
        readonly status: number,
    ) {
        super(message);
        this.name = 'ApiError';
    }

    /** Die Sitzung ist abgelaufen oder wurde beendet. */
    get isUnauthorized(): boolean {
        return this.status === 401;
    }

    /** Angemeldet, aber ohne das noetige Recht. */
    get isForbidden(): boolean {
        return this.status === 403;
    }
}

type RequestOptions = {
    method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
    body?: unknown;
    signal?: AbortSignal;
};

/** Wird ausgeloest, sobald das Backend 401 meldet, damit die Anmeldemaske erscheint. */
type UnauthorizedListener = () => void;
const unauthorizedListeners = new Set<UnauthorizedListener>();

export function onUnauthorized(listener: UnauthorizedListener): () => void {
    unauthorizedListeners.add(listener);
    return () => unauthorizedListeners.delete(listener);
}

async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
    const { method = 'GET', body, signal } = options;

    const response = await fetch(`${config.api.baseUrl}${path}`, {
        method,
        credentials: 'include',
        cache: 'no-store',
        signal,
        headers: body === undefined ? { Accept: 'application/json' } : { Accept: 'application/json', 'Content-Type': 'application/json' },
        body: body === undefined ? undefined : JSON.stringify(body),
    }).catch(() => {
        throw new ApiError('Das Backend ist nicht erreichbar.', 0);
    });

    if (response.status === 204) return undefined as T;

    const payload = await response.json().catch(() => ({}));

    if (!response.ok) {
        // 401 auf der Auth-Sonde ist eine Antwort, kein Fehler: dort steht,
        // dass niemand angemeldet ist. Ueberall sonst heisst es, dass die
        // Sitzung gerade abgelaufen ist.
        if (response.status === 401 && path !== '/api/auth/me') {
            unauthorizedListeners.forEach((listener) => listener());
        }

        const message = typeof payload?.error === 'string' ? payload.error : `Das Backend antwortet mit HTTP ${response.status}.`;
        throw new ApiError(message, response.status);
    }

    return payload as T;
}

export const api = {
    get: <T,>(path: string, signal?: AbortSignal) => request<T>(path, { signal }),
    post: <T,>(path: string, body?: unknown) => request<T>(path, { method: 'POST', body }),
    put: <T,>(path: string, body?: unknown) => request<T>(path, { method: 'PUT', body }),
    patch: <T,>(path: string, body?: unknown) => request<T>(path, { method: 'PATCH', body }),
    delete: <T,>(path: string, body?: unknown) => request<T>(path, { method: 'DELETE', body }),
};

/** Adresse fuer eine PDF-Anzeige im Browser. Die Sitzung traegt ueber das Cookie. */
export function fileUrl(path: string): string {
    return `${config.api.baseUrl}${path}`;
}

/**
 * Übergangsbrücke für Dashboard.tsx.
 *
 * Die große Dashboard-Komponente ruft noch direkt `fetch("/api/…")` auf, geht
 * also nicht durch `api` oben. Ohne diesen Aufsatz bliebe eine abgelaufene
 * Sitzung dort unbemerkt und die Ansichten zeigten Fehlermeldungen, statt zur
 * Anmeldung zurückzufallen.
 *
 * Diese Funktion darf ersatzlos entfallen, sobald alle Ansichten den Client
 * oben verwenden.
 */
let intercepted = false;

export function watchLegacyFetchForExpiry(): void {
    if (intercepted) return;
    intercepted = true;

    const original = window.fetch.bind(window);

    window.fetch = async (input, init) => {
        const response = await original(input, init);
        const url = typeof input === 'string' ? input : input instanceof Request ? input.url : String(input);

        // Nur eigene API-Aufrufe, und die Auth-Sonde bleibt außen vor: dort ist
        // 401 die reguläre Antwort für "niemand angemeldet".
        if (response.status === 401 && url.includes('/api/') && !url.includes('/api/auth/me')) {
            unauthorizedListeners.forEach((listener) => listener());
        }

        return response;
    };
}
