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

export const temp_post_blob_percent_completed = 0;
export const post_blob_percent_completed = temp_post_blob_percent_completed;
export const temp_post_object_percent_completed = 0;
export const post_object_percent_completed = temp_post_object_percent_completed;

// Updated 2026-01-26 (Abort Silence)
export const post_object = async function post_object({
    api_cfg = null,
    endpoint = '',
    headers = {},
    params = {},
    data = {},
    form_data = null,
    timeout = 20000,
    return_meta = false,
    return_blob = false,
    filename = '',
    auto_download = false,
    // The task_id value should be a random string that is unique to the task. This is used to identify the task in the message event.
    task_id = crypto.randomUUID(),
    log_lvl = 0,
    retry_count = 5,
    // When true: use XHR instead of fetch so xhr.upload.onprogress can fire
    // progress postMessages into api_upload_kv. Only meaningful for form_data uploads.
    track_progress = false
}: {
    api_cfg: any;
    endpoint: string;
    headers?: any;
    params?: any;
    data?: any;
    form_data?: any;
    timeout?: number;
    return_meta?: boolean;
    return_blob?: boolean;
    filename?: string;
    auto_download?: boolean;
    task_id?: string;
    log_lvl?: number;
    retry_count?: number;
    track_progress?: boolean;
}) {
    if (log_lvl) {
        console.log(
            `*** post_object() *** Endpoint: ${endpoint} Task ID: ${task_id}`
        );
        console.log('Params:', params);
        if (log_lvl > 1) {
            console.log('Data:', data);
            console.log(typeof data);
            console.log(`Base URL: ${api_cfg['base_url']}`);
            console.log('API Config:', api_cfg);
        }
        if (log_lvl > 2) {
            console.log(`Return Meta: ${return_meta}`);
            console.log(`Return Blob: ${return_blob}`);
            console.log(`Filename: ${filename}`);
            console.log(`Auto Download: ${auto_download}`);
        }
    }

    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
    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_post_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}`;
    }

    if (form_data) {
        delete headers_cleaned['content-type'];
        delete headers_cleaned['Content-Type'];
        if (log_lvl > 1) console.log('Form Data:', form_data);
    } else {
        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;
    }

    // XHR path — only for form_data uploads with progress tracking requested.
    // fetch() has no upload progress events; XHR.upload.onprogress does.
    if (track_progress && form_data) {
        return _post_with_xhr({
            url_str: url.toString(),
            headers_cleaned,
            form_data,
            task_id,
            endpoint,
            timeout,
            return_meta,
            log_lvl
        });
    }

    for (let attempt = 1; attempt <= retry_count; attempt++) {
        // Declared at loop scope (not inside try) so the catch block can clearTimeout.
        // Fresh controller per attempt — same rationale as api_get_object.ts.
        const controller = new AbortController();
        // AbortError is not specific enough by itself. Distinguish timeout-aborts
        // (retryable transient class) from intentional caller aborts (fail-fast).
        let did_timeout_abort = false;
        const timeoutId = setTimeout(() => {
            did_timeout_abort = true;
            console.warn(`API POST: Request timed out after ${timeout}ms (attempt ${attempt}/${retry_count}).`);
            controller.abort();
        }, timeout);

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

            if (log_lvl > 1) {
                console.log('Fetch Options:', fetchOptions);
            }

            const response = await fetch_method(
                url.toString(),
                fetchOptions
            ).catch(function (error: any) {
                // SILENCE NOISE: Aborted requests shouldn't spam logs at log_lvl 0
                if (
                    error.name === 'AbortError' ||
                    error.message?.includes('aborted') ||
                    error.name === 'TypeError'
                ) {
                    if (log_lvl > 1) {
                        console.log(
                            'API POST: Request was aborted or terminated by browser. Expected during navigation.',
                            error
                        );
                    }
                    return error;
                }

                console.log(
                    'API POST Object *fetch* request failed in an unexpected way.',
                    error
                );
                return error;
            });
            clearTimeout(timeoutId);

            // Check if we should stop due to abort or network failure.
            if (
                response instanceof Error ||
                (response &&
                    (response.name === 'TypeError' ||
                        response.name === 'AbortError'))
            ) {
                // Retry timeout-aborts from this helper; do not retry caller aborts
                // (route change/unmount/manual cancellation).
                if (response.name === 'AbortError') {
                    if (did_timeout_abort) {
                        throw new Error(
                            `Timeout abort (attempt ${attempt}/${retry_count}) after ${timeout}ms`
                        );
                    }
                    return false;
                }

                // TypeError = transient network failure. Throw into the retry loop
                // so backoff-and-retry applies. Same fix as api_get_object.ts — see
                // comment there for the full history of why this was broken.
                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 (POST): 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}`);
            }

            if (!return_blob) {
                const json = await response.json();

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

                // Post a message to the window indicating the upload is complete
                try {
                    if (typeof window !== 'undefined') {
                        window.postMessage(
                            {
                                type: 'api_post_json_form',
                                status: 'complete',
                                task_id: task_id,
                                endpoint: endpoint,
                                size_total: 0,
                                size_loaded: 0,
                                percent_completed: 100,
                                progress: 100,
                                rate: 0
                            },
                            '*'
                        );
                    }
                } catch (error) {
                    console.error('Error posting message to window:', error);
                }

                // Return the response data or metadata
                // Robustly handle V3 response envelopes
                return return_meta
                    ? json
                    : json.data !== undefined
                      ? json.data
                      : json;
            } else {
                const blob = await response.blob();

                if (auto_download) {
                    const downloadUrl = window.URL.createObjectURL(blob);
                    const link = document.createElement('a');
                    link.href = downloadUrl;
                    link.setAttribute('download', filename || 'download');
                    document.body.appendChild(link);
                    link.click();
                    link.remove();
                    return true;
                } else {
                    return blob;
                }
            }
        } catch (error) {
            // Ensure the per-attempt timeout timer is always cancelled on failure.
            clearTimeout(timeoutId);
            console.error(`API POST error on attempt ${attempt}:`, error);

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

            // Backoff before retrying — same rationale as api_get_object.ts.
            const delay_ms = Math.min(2000 * attempt, 8000);
            console.log(`API POST: Retrying in ${delay_ms}ms... (attempt ${attempt}/${retry_count})`);
            await new Promise<void>((resolve) => setTimeout(resolve, delay_ms));
        }
    }
};

function _post_with_xhr({
    url_str,
    headers_cleaned,
    form_data,
    task_id,
    endpoint,
    timeout,
    return_meta,
    log_lvl
}: {
    url_str: string;
    headers_cleaned: key_val;
    form_data: FormData;
    task_id: string;
    endpoint: string;
    timeout: number;
    return_meta: boolean;
    log_lvl: number;
}): Promise<any> {
    return new Promise((resolve) => {
        const xhr = new XMLHttpRequest();
        xhr.open('POST', url_str);
        xhr.timeout = timeout;

        // Apply auth/custom headers. Content-Type is intentionally omitted —
        // the browser sets the multipart boundary automatically for FormData.
        for (const [key, value] of Object.entries(headers_cleaned)) {
            xhr.setRequestHeader(key, String(value));
        }

        xhr.upload.onprogress = (event) => {
            if (event.lengthComputable && typeof window !== 'undefined') {
                const pct = Math.round((event.loaded / event.total) * 100);
                try {
                    window.postMessage(
                        {
                            type: 'api_post_json_form',
                            status: 'uploading',
                            task_id,
                            endpoint,
                            size_total: event.total,
                            size_loaded: event.loaded,
                            percent_completed: pct,
                            progress: pct,
                            rate: 0
                        },
                        '*'
                    );
                } catch (_) {}
            }
        };

        xhr.ontimeout = () => {
            console.error(`XHR upload timed out after ${timeout}ms. Endpoint: ${endpoint}`);
            resolve(false);
        };

        xhr.onerror = () => {
            console.error(`XHR upload network error. Endpoint: ${endpoint}`);
            resolve(false);
        };

        xhr.onload = () => {
            if (log_lvl)
                console.log(`XHR response: status=${xhr.status} endpoint=${endpoint}`);

            if (xhr.status === 401 || xhr.status === 403) {
                console.warn(`XHR AUTH FAILURE (${xhr.status}): ${endpoint}`);
                if (browser) ae_auth_error.set({ type: 'expired', ts: Date.now() });
                resolve(false);
                return;
            }

            if (xhr.status === 404) {
                resolve(null);
                return;
            }

            if (xhr.status < 200 || xhr.status >= 300) {
                console.error(`XHR upload failed: HTTP ${xhr.status}. Endpoint: ${endpoint}`);
                resolve(false);
                return;
            }

            // Fire completion postMessage (matches the fetch path shape)
            try {
                if (typeof window !== 'undefined') {
                    window.postMessage(
                        {
                            type: 'api_post_json_form',
                            status: 'complete',
                            task_id,
                            endpoint,
                            size_total: 0,
                            size_loaded: 0,
                            percent_completed: 100,
                            progress: 100,
                            rate: 0
                        },
                        '*'
                    );
                }
            } catch (_) {}

            try {
                const json = JSON.parse(xhr.responseText);
                if (log_lvl > 1) console.log('XHR Response JSON:', json);
                resolve(
                    return_meta
                        ? json
                        : json.data !== undefined
                          ? json.data
                          : json
                );
            } catch (e) {
                console.error('XHR: Failed to parse response JSON.', e);
                resolve(false);
            }
        };

        xhr.send(form_data);
    });
}
