import { notify } from './toast';

const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:4100';

class AuthError extends Error {
    constructor(message = 'Authentication required') {
        super(message);
        this.name = 'AuthError';
    }
}

const getStoredToken = (): string | null => localStorage.getItem('helix_access_token');
const getRefreshToken = (): string | null => localStorage.getItem('helix_refresh_token');

const storeTokens = (accessToken: string, refreshToken: string) => {
    localStorage.setItem('helix_access_token', accessToken);
    localStorage.setItem('helix_refresh_token', refreshToken);
};

const clearTokens = () => {
    localStorage.removeItem('helix_access_token');
    localStorage.removeItem('helix_refresh_token');
};

// Shared auth headers
const authHeaders = (): Record<string, string> => {
    const token = getStoredToken();
    return {
        'Content-Type': 'application/json',
        ...(token ? { Authorization: `Bearer ${token}` } : {}),
    };
};

// Token refresh — attempts to get a new access token using the refresh token
let refreshPromise: Promise<boolean> | null = null;

const tryRefreshToken = async (): Promise<boolean> => {
    // Deduplicate concurrent refresh attempts
    if (refreshPromise) return refreshPromise;

    refreshPromise = (async () => {
        const refreshToken = getRefreshToken();
        if (!refreshToken) return false;

        try {
            const response = await fetch(`${API_URL}/auth/refresh`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ refreshToken }),
            });

            if (!response.ok) return false;

            const data = await response.json();
            if (data.accessToken && data.refreshToken) {
                storeTokens(data.accessToken, data.refreshToken);
                return true;
            }
            return false;
        } catch {
            return false;
        } finally {
            refreshPromise = null;
        }
    })();

    return refreshPromise;
};

// Shared response handler — extracts error message, handles 401 with auto-refresh, toasts on failure
const handleResponse = async <T>(response: Response, silent = false, retryFn?: () => Promise<Response>): Promise<T> => {
    if (response.status === 401 && retryFn) {
        const refreshed = await tryRefreshToken();
        if (refreshed) {
            const retryResponse = await retryFn();
            return handleResponse<T>(retryResponse, silent);
        }
        clearTokens();
        if (!silent) notify.error('Session expired. Please log in again.');
        throw new AuthError();
    }

    if (response.status === 401) {
        clearTokens();
        if (!silent) notify.error('Session expired. Please log in again.');
        throw new AuthError();
    }

    const json = await response.json().catch(() => null);

    // Sidecar may return 400 when the underlying platform token expired — retry with refreshed token
    if (!response.ok && retryFn) {
        const msg = (json?.message ?? '').toLowerCase();
        if (msg.includes('agent identity') || msg.includes('token') || msg.includes('unauthenticated')) {
            const refreshed = await tryRefreshToken();
            if (refreshed) {
                const retryResponse = await retryFn();
                return handleResponse<T>(retryResponse, silent);
            }
            clearTokens();
            if (!silent) notify.error('Session expired. Please log in again.');
            throw new AuthError();
        }
    }

    if (!response.ok) {
        const message = json?.message ?? json?.error ?? `Request failed (${response.status})`;
        if (!silent) notify.error(message);
        throw new Error(message);
    }

    return json as T;
};

export const apiClient = {
    // Auth — no token needed, no toast on failure (login page shows inline errors)
    async login(email: string, password: string) {
        const response = await fetch(`${API_URL}/auth/login`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ email, password }),
        });

        if (!response.ok) {
            const data = await response.json().catch(() => ({}));
            throw new Error(data.message ?? 'Login failed');
        }

        const data = await response.json();
        storeTokens(data.accessToken, data.refreshToken);
        return data as {
            accessToken: string;
            refreshToken: string;
            user?: {
                id?: string;
                email?: string;
                firstName?: string;
                lastName?: string;
                avatarUrl?: string;
                role?: string;
                platformRoles?: string[];
            };
        };
    },

    // GraphQL — all platform data queries go through this
    async graphql<T>(query: string, variables?: Record<string, unknown>, options?: { silent?: boolean }): Promise<T> {
        const token = getStoredToken();
        if (!token) throw new AuthError();

        const doFetch = () => fetch(`${API_URL}/graphql`, {
            method: 'POST',
            headers: authHeaders(),
            body: JSON.stringify({ query, variables }),
        });

        let response = await doFetch();

        if (response.status === 401) {
            const refreshed = await tryRefreshToken();
            if (refreshed) {
                response = await doFetch();
            } else {
                clearTokens();
                if (!options?.silent) notify.error('Session expired', 'Please log in again.');
                throw new AuthError();
            }
        }

        let json = await response.json();

        // Platform returns 200 with UNAUTHENTICATED error when token expires — retry with refresh
        const authError = json.errors?.find((e: any) => e.extensions?.code === 'UNAUTHENTICATED');
        if (authError) {
            const refreshed = await tryRefreshToken();
            if (refreshed) {
                const retryResponse = await doFetch();
                json = await retryResponse.json();
            } else {
                clearTokens();
                if (!options?.silent) notify.error('Session expired', 'Please log in again.');
                throw new AuthError();
            }
        }

        if (json.errors) {
            const message = json.errors[0]?.message ?? 'GraphQL error';
            if (!options?.silent) notify.error('Query failed', message);
            throw new Error(message);
        }

        return json.data;
    },

    // REST — all sidecar API calls go through these
    async post<T>(path: string, body?: Record<string, unknown>, options?: { silent?: boolean }): Promise<T> {
        const doFetch = () => fetch(`${API_URL}${path}`, {
            method: 'POST',
            headers: authHeaders(),
            body: body ? JSON.stringify(body) : undefined,
        });
        const response = await doFetch();
        return handleResponse<T>(response, options?.silent, doFetch);
    },

    async get<T>(path: string, options?: { silent?: boolean }): Promise<T> {
        const doFetch = () => fetch(`${API_URL}${path}`, {
            method: 'GET',
            headers: authHeaders(),
        });
        const response = await doFetch();
        return handleResponse<T>(response, options?.silent, doFetch);
    },

    async put<T>(path: string, body?: Record<string, unknown>, options?: { silent?: boolean }): Promise<T> {
        const doFetch = () => fetch(`${API_URL}${path}`, {
            method: 'PUT',
            headers: authHeaders(),
            body: body ? JSON.stringify(body) : undefined,
        });
        const response = await doFetch();
        return handleResponse<T>(response, options?.silent, doFetch);
    },

    // Health check — silent, no toasts
    async healthCheck(): Promise<{ status: string; platform: { reachable: boolean } }> {
        try {
            const response = await fetch(`${API_URL}/api/health`, { signal: AbortSignal.timeout(3000) });
            if (!response.ok) return { status: 'down', platform: { reachable: false } };
            return response.json();
        } catch {
            return { status: 'down', platform: { reachable: false } };
        }
    },

    getStoredToken,
    getRefreshToken,
    storeTokens,
    clearTokens,
    isAuthenticated: () => !!getStoredToken(),
};
