import { browser } from '$app/environment';
import { ae_auth_error } from '$lib/stores/ae_stores';
import type { key_val } from '$lib/stores/ae_stores';

/**
 * Performs a PATCH request to the Aether API.
 * Refactored 2026-01-08 to use standard fetch with timeout, custom fetch injection,
 * standardized kebab-case headers, and robust V3 response handling.
 */
export const patch_object = async function patch_object({
    api_cfg = null,
    endpoint = '',
    headers = {},
    params = {},
    data = {},
    timeout = 20000,
    return_meta = false,
    log_lvl = 0,
    retry_count = 5
}: {
    api_cfg: any;
    endpoint: string;
    headers?: any;
    params?: any;
    data?: any;
    timeout?: number;
    return_meta?: boolean;
    log_lvl?: number;
    retry_count?: number;
}) {
    if (log_lvl) {
        console.log(`*** patch_object() *** Endpoint: ${endpoint}`);
        console.log('Params:', params);
        if (log_lvl > 1) {
            console.log('Data:', data);
            console.log(`Base URL: ${api_cfg?.['base_url']}`);
        }
    }

    if (!api_cfg) {
        console.error('No API Config was provided. Returning false.');
        return false;
    }

    // Construct the URL with query parameters
    const url = new URL(endpoint, api_cfg['base_url']);
    if (params) {
        Object.keys(params).forEach((key) =>
            url.searchParams.append(key, params[key])
        );
    }

    // Clean and merge headers without mutating the original api_cfg
    const headers_cleaned: key_val = {};
    const merged_headers = { ...api_cfg['headers'], ...headers };

    // Auto-promote account_id from api_cfg to header if missing
    let account_id = merged_headers['x-account-id'] || api_cfg['account_id'];

    // IMMEDIATE ACCOUNT ID SCAVENGING: Read from localStorage to avoid race conditions
    if (!account_id && typeof localStorage !== 'undefined') {
        try {
            const ae_loc_raw = localStorage.getItem('ae_loc');
            if (ae_loc_raw) {
                const ae_loc_json = JSON.parse(ae_loc_raw);
                if (ae_loc_json.account_id) {
                    account_id = ae_loc_json.account_id;
                }
            }
        } catch (e) {
            // Silently fail on storage read
        }
    }

    if (account_id) {
        merged_headers['x-account-id'] = account_id;
    }

    // Handle "Bootstrap Paradox" for unauthenticated requests
    const bypass_val =
        merged_headers['x-no-account-id'] || merged_headers['x_no_account_id'];
    const is_valid_bypass =
        bypass_val === 'bypass' ||
        bypass_val === 'Nothing to See Here' ||
        bypass_val === 'direct-download';

    if (is_valid_bypass) {
        if (log_lvl > 1)
            console.log(
                'api_patch_object: Valid bypass detected. Stripping account ID context.'
            );
        delete merged_headers['x-account-id'];
        delete merged_headers['x_account_id'];
    } else {
        // If it's a placeholder (like "No_Account_ID_Here"), just remove the bypass header
        // but DO NOT strip the valid Account ID.
        delete merged_headers['x-no-account-id'];
        delete merged_headers['x_no_account_id'];
    }

    for (const prop in merged_headers) {
        const prop_cleaned = prop.replaceAll('_', '-');
        let value = merged_headers[prop];
        if (value === null || value === undefined) continue;

        if (typeof value !== 'string') {
            value = JSON.stringify(value);
        }
        headers_cleaned[prop_cleaned] = value;
    }

    // Auto-inject Authorization header if JWT is present but header is missing
    let jwt =
        headers_cleaned['jwt'] ||
        headers_cleaned['JWT'] ||
        api_cfg['jwt'] ||
        api_cfg['headers']?.['jwt'] ||
        api_cfg['headers']?.['JWT'];

    // Final Fallback: Direct check of primary ae_loc key
    if (!jwt && typeof localStorage !== 'undefined') {
        try {
            const raw = localStorage.getItem('ae_loc');
            if (raw) {
                const json = JSON.parse(raw);
                if (json.jwt) jwt = json.jwt;
            }
        } catch (e) {
            // Silently fail on storage read
        }
    }

    if (
        jwt &&
        !headers_cleaned['Authorization'] &&
        !headers_cleaned['authorization']
    ) {
        headers_cleaned['Authorization'] = `Bearer ${jwt}`;
    }

    headers_cleaned['Content-Type'] = 'application/json';

    if (log_lvl > 1) {
        console.log('Final cleaned headers:', headers_cleaned);
    }

    let fetch_method: any = fetch;
    if (api_cfg.fetch) {
        if (log_lvl > 1) {
            console.log('Using custom fetch function from api_cfg!!!');
        }
        fetch_method = api_cfg.fetch;
    }

    for (let attempt = 1; attempt <= retry_count; attempt++) {
        // Keep timeout handle at attempt scope so catch can always clear it.
        let timeoutId: ReturnType<typeof setTimeout> | null = null;
        try {
            const controller = new AbortController();
            // AbortError alone is ambiguous. Track whether the helper timeout
            // fired so we can retry timeout-aborts but fail fast on caller abort.
            let did_timeout_abort = false;
            timeoutId = setTimeout(() => {
                did_timeout_abort = true;
                console.error(
                    `API PATCH request timed out after ${timeout}ms.`
                );
                controller.abort();
            }, timeout);

            const fetchOptions: RequestInit = {
                method: 'PATCH',
                headers: headers_cleaned,
                body: JSON.stringify(data),
                signal: controller.signal
            };

            const response = await fetch_method(
                url.toString(),
                fetchOptions
            ).catch(function (error: any) {
                // Keep noisy abort/network conditions out of high-level logs.
                if (
                    error?.name === 'AbortError' ||
                    error?.name === 'TypeError' ||
                    error?.message?.includes('aborted')
                ) {
                    if (log_lvl > 1) {
                        console.log(
                            'API PATCH: Request aborted or browser-terminated.',
                            error
                        );
                    }
                    return error;
                }

                console.log(
                    'API PATCH Object *fetch* request was aborted or failed in an unexpected way.',
                    error
                );
                return error;
            });
            if (timeoutId) clearTimeout(timeoutId);

            // Error object was returned from fetch catch block; decide retry class.
            if (
                response instanceof Error ||
                (response &&
                    (response.name === 'AbortError' ||
                        response.name === 'TypeError'))
            ) {
                if (response.name === 'AbortError') {
                    // Retry only helper-timeout aborts. Caller/navigation aborts
                    // should fail fast to avoid duplicate mutation side-effects.
                    if (did_timeout_abort) {
                        throw new Error(
                            `Timeout abort (attempt ${attempt}/${retry_count}) after ${timeout}ms`
                        );
                    }
                    return false;
                }

                // Transient browser/network failure class.
                throw new Error(
                    `Network error (attempt ${attempt}): ${response.message}`
                );
            }

            if (!response) {
                throw new Error(
                    `HTTP fetch request was aborted or failed in an unexpected way! URL = ${url.toString()}`
                );
            }

            if (log_lvl) {
                console.log(
                    `Response: status=${response.status} attempt=${attempt}`
                );
            }

            if (!response.ok) {
                if (response.status === 404) {
                    if (log_lvl) {
                        console.log(
                            'The response was a 404 not found "error". Returning null.'
                        );
                    }
                    return null;
                }

                // FAIL FAST (Section 2D): Do not retry on Auth or Client errors (400, 401, 403, 422)
                if (
                    response.status === 400 ||
                    response.status === 401 ||
                    response.status === 403 ||
                    response.status === 422
                ) {
                    if (log_lvl)
                        console.error(
                            `API Client Failure (${response.status}). Failing fast.`
                        );

                    if (response.status === 401 || response.status === 403) {
                        console.warn(
                            `AUTH DIAGNOSTICS (PATCH): Headers sent for ${endpoint}:`,
                            {
                                has_auth: !!headers_cleaned['Authorization'],
                                has_api_key:
                                    !!headers_cleaned['x-aether-api-key'],
                                has_account_id:
                                    !!headers_cleaned['x-account-id'],
                                jwt_preview: jwt
                                    ? `${jwt.slice(0, 8)}...`
                                    : 'MISSING'
                            }
                        );
                        // Signal the root layout to show the session-expired banner.
                        if (browser)
                            ae_auth_error.set({
                                type: 'expired',
                                ts: Date.now()
                            });
                    }

                    // Structured Error Handling (V3): Attempt to get rich error metadata
                    let error_json: any = null;
                    try {
                        error_json = await response.json();
                    } catch (e) {
                        // Not JSON
                    }

                    if (log_lvl)
                        console.log(
                            'The response was not ok. Structured Error Check:',
                            error_json
                        );

                    if (error_json?.meta?.details) {
                        return error_json;
                    }

                    // Fallback for standard FastAPI "detail" errors
                    if (error_json?.detail) {
                        return {
                            meta: {
                                success: false,
                                status_code: response.status,
                                details: {
                                    category: 'validation',
                                    message:
                                        typeof error_json.detail === 'string'
                                            ? error_json.detail
                                            : JSON.stringify(error_json.detail),
                                    raw: error_json.detail
                                }
                            }
                        };
                    }

                    return false;
                }

                throw new Error(`HTTP error! status: ${response.status}`);
            }

            const json = await response.json();

            if (log_lvl > 1) {
                console.log('Response JSON:', json);
            }

            // Return the response data or metadata
            // Robustly handle V3 response envelopes
            return return_meta
                ? json
                : json.data !== undefined
                  ? json.data
                  : json;
        } catch (error) {
            // Ensure per-attempt timeout is always cleared on failure.
            if (timeoutId) clearTimeout(timeoutId);
            console.error(`API PATCH error on attempt ${attempt}:`, error);

            if (attempt === retry_count) {
                console.error('Max retry attempts reached. Returning false.');
                return false;
            }

            // Backoff before retrying. Caps at 8s to match GET/POST policy.
            const delay_ms = Math.min(2000 * attempt, 8000);
            console.log(
                `API PATCH: Retrying in ${delay_ms}ms... (attempt ${attempt}/${retry_count})`
            );
            await new Promise<void>((resolve) => setTimeout(resolve, delay_ms));
        }
    }
};
