import saveAs from 'file-saver';

import { ErrorShowType } from '@/defines';

import { request, RequestConfig } from './request';
import { toLoginPage } from './routerUtils';

type RequestMethods =
    | 'get'
    | 'GET'
    | 'post'
    | 'POST'
    | 'put'
    | 'PUT'
    | 'delete'
    | 'DELETE'
    | 'patch'
    | 'PATCH';

function handleDownloadError(
    errorMessage: string,
    errorCode: string | number,
    showType: ErrorShowType,
    config?: RequestConfig,
    data?: any
) {
    if (!(config as any)?.skipErrorHandler) {
        if (errorCode === 401) {
            toLoginPage();
            return;
        } else {
            switch (showType) {
                case ErrorShowType.SILENT:
                    // TODO: 静默处理，不显示任何提示
                    break;
                case ErrorShowType.WARN_MESSAGE:
                    // TODO: 显示消息弹窗（警告）
                    break;
                case ErrorShowType.ERROR_MESSAGE:
                    // TODO: 显示消息弹窗（错误）
                    break;
                case ErrorShowType.NOTIFICATION:
                    // TODO: 显示一个通知
                    break;
                case ErrorShowType.REDIRECT:
                    // TODO: 将错误重定向到指定页面
                    break;
                default:
                    // TODO: 显示消息弹窗（错误）
                    break;
            }
        }
    }

    const error: any = new Error(errorMessage);
    error.name = 'BizError';
    error.info = { errorCode, errorMessage, showType, data };
    return Promise.reject(error);
}

async function handleBlobErrorResponse(blob: Blob, config?: RequestConfig) {
    try {
        const text = await blob.text();
        if (!text.trim()) {
            return handleDownloadError(
                'Empty error response',
                '500',
                ErrorShowType.ERROR_MESSAGE,
                config
            );
        }

        const errorData: API.Result = JSON.parse(text);
        const errorMessage = errorData.errorMessage || 'Unknown error';
        const errorCode = errorData.errorCode || 500;
        const showType = errorData.showType ?? ErrorShowType.ERROR_MESSAGE;

        return handleDownloadError(errorMessage, errorCode, showType, config, errorData.data);
    } catch {
        return handleDownloadError(
            'Failed to parse error response',
            500,
            ErrorShowType.ERROR_MESSAGE,
            config
        );
    }
}

function extractFileName(contentDisposition: string | undefined, defaultFileName: string): string {
    if (!contentDisposition || typeof contentDisposition !== 'string') {
        return defaultFileName;
    }

    // 支持标准的 filename 和 filename* 格式
    const patterns = [
        /filename\*=UTF-8''([^;]+)/i, // RFC 5987 格式
        /filename\*=['"]?([^'";]+)['"]?/i,
        /filename=['"]?([^'";]+)['"]?/i,
    ];

    for (const pattern of patterns) {
        const matches = contentDisposition.match(pattern);
        if (matches?.[1]) {
            try {
                return decodeURIComponent(matches[1].trim());
            } catch {
                return matches[1].trim();
            }
        }
    }

    return defaultFileName;
}

/**
 * 发起文件下载请求：以 Blob 方式请求并保存为本地文件。超时 10 分钟；4xx/5xx 或业务错误 Blob 会按 ErrorShowType 处理（401 跳转登录），并 reject BizError。
 * @param method HTTP 方法
 * @param uri 请求地址
 * @param params 请求配置（如 data、params），会与 config 合并
 * @param opts 下载选项：mime 保存时的 MIME，defaultFileName 未从 Content-Disposition 解析到文件名时的默认名；不传时默认 txt
 * @param config 额外请求配置，可与 params 合并；含 skipErrorHandler 时跳过统一错误处理
 * @returns Promise，成功时无返回值（触发浏览器保存）；失败时 reject 带 info 的 BizError
 */
export const downloadFile = <D = any>(
    method: RequestMethods,
    uri: string,
    params?: RequestConfig<D>,
    opts?: { mime: string; defaultFileName: string },
    config?: RequestConfig
) => {
    const { defaultFileName = 'default.txt', mime = 'text/plain;charset=utf-8' } = opts ?? {};
    return request(uri, {
        method,
        ...(params || {}),
        ...(config || {}),
        responseType: 'blob',
        getResponse: true,
        timeout: 10 * 60 * 1000,
    }).then(async (response) => {
        if (!response.data || !(response.data instanceof Blob)) {
            return handleDownloadError(
                'Invalid response data',
                '500',
                ErrorShowType.ERROR_MESSAGE,
                config
            );
        }

        if (response.status >= 400) {
            if (response.data.type === 'application/json') {
                return handleBlobErrorResponse(response.data, config);
            } else {
                return handleDownloadError(
                    `HTTP Error: ${response.status}`,
                    response.status,
                    ErrorShowType.ERROR_MESSAGE,
                    config
                );
            }
        }

        const contentDisposition = response.headers['content-disposition'];
        const isFileDownload = contentDisposition && contentDisposition.includes('attachment');
        if (response.data.type === 'application/json' && !isFileDownload) {
            return handleBlobErrorResponse(response.data, config);
        }

        const fileName = extractFileName(contentDisposition, defaultFileName);
        const downloadBlob = new Blob([response.data], { type: mime });
        saveAs(downloadBlob, fileName);
    });
};

/**
 * 使用 POST 请求下载文本文件并保存为本地文件；等价于 downloadFile('post', url, params, undefined, config)。
 * @param url 请求地址
 * @param params 请求配置（如 data）
 * @param config 额外请求配置
 * @returns 同 downloadFile
 */
export const downloadText = <D = any>(
    url: string,
    params: RequestConfig<D>,
    config?: RequestConfig
) => {
    return downloadFile('post', url, params, void 0, config);
};

/**
 * 使用 POST 请求下载 Excel（xlsx）并保存为本地文件；MIME 与默认文件名已固定为 xlsx。
 * @param url 请求地址
 * @param params 请求配置（如 data）
 * @param config 额外请求配置
 * @returns 同 downloadFile
 */
export const downloadExcel = <D = any>(
    url: string,
    params: RequestConfig<D>,
    config?: RequestConfig
) => {
    return downloadFile(
        'post',
        url,
        params,
        {
            mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            defaultFileName: 'default.xlsx',
        },
        config
    );
};
