/**
 * 通过 RunForge-ProxyServer 的 /Proxy/Forward 发起出站 HTTP 请求。
 */
const axios = require('axios')
const { getRuntimeConfig } = require('./RuntimeConfig')

async function getServerConfig() {
    let cfg = null
    try {
        cfg = await getRuntimeConfig('proxyForwardServer', { required: false, defaultValue: null })
    } catch (_) {
        return { url: '', enabled: false }
    }
    if (!cfg || typeof cfg !== 'object') return { url: '', enabled: false }
    return {
        url: String(cfg.url || '').trim().replace(/\/+$/, ''),
        enabled: cfg.enabled !== false,
        defaultTimeout: Number(cfg.timeout) || 120000
    }
}

async function isProxyForwardEnabled() {
    const { url, enabled } = await getServerConfig()
    return enabled && url.length > 0
}

function debugProxyEnabled() {
    return String(process.env.LEPAO_DEBUG_PROXY || '').trim() === '1'
}

function debugProxyAxiosFragment() {
    const host = process.env.LEPAO_DEBUG_PROXY_HOST || '127.0.0.1'
    const port = Number(process.env.LEPAO_DEBUG_PROXY_PORT || 9000)
    return {
        proxy: {
            host,
            port,
            protocol: 'http'
        }
    }
}

function briefUrlPath(fullUrl) {
    try {
        const u = new URL(fullUrl)
        return `${u.pathname}${u.search}`
    } catch {
        return fullUrl
    }
}

/** 转为可 JSON 序列化的纯对象，避免 AxiosHeaders 等类型丢失字段 */
function normalizeForwardHeaders(headers) {
    if (headers == null) return {}
    let source = headers
    if (typeof headers === 'string') {
        try {
            source = JSON.parse(headers)
        } catch {
            return {}
        }
    }
    if (typeof source !== 'object' || Array.isArray(source)) return {}
    if (typeof source.toJSON === 'function') source = source.toJSON()
    const out = {}
    for (const [key, value] of Object.entries(source)) {
        if (value == null) continue
        out[String(key)] = Array.isArray(value) ? value.join(', ') : String(value)
    }
    return out
}

/**
 * URLSearchParams / Buffer 等无法被 JSON.stringify 正确序列化，需先转换再发给代理服务。
 */
function serializeForwardBody(data) {
    if (data == null || data === '') return null
    if (typeof data === 'string') return data
    if (Buffer.isBuffer(data)) {
        return { __encoding: 'base64', data: data.toString('base64') }
    }
    if (typeof URLSearchParams !== 'undefined' && data instanceof URLSearchParams) {
        return data.toString()
    }
    if (typeof data === 'object' && !Array.isArray(data)) return data
    return String(data)
}

function buildDirectAxiosConfig(extra = {}) {
    if (debugProxyEnabled()) {
        const { HttpsProxyAgent } = require('https-proxy-agent')
        const dbg = debugProxyAxiosFragment()
        const { host, port } = dbg.proxy
        const agent = new HttpsProxyAgent(`http://${host}:${port}`)
        return { proxy: false, httpAgent: agent, httpsAgent: agent, ...extra }
    }
    return { proxy: false, ...extra }
}

async function execDirect({ method, url, data, headers, timeout, validateStatus, responseType, transformResponse }) {
    const m = String(method || 'get').toLowerCase()
    const merged = {
        headers: headers || {},
        timeout,
        ...buildDirectAxiosConfig(),
        ...(validateStatus ? { validateStatus } : {}),
        ...(responseType ? { responseType } : {}),
        ...(transformResponse ? { transformResponse } : {})
    }
    return axios.request({ ...merged, method: m, url, data })
}

function assertProxyModeOk(outboundMode, meta) {
    if (outboundMode !== 'proxy') return
    if (meta?.fallback_direct) {
        const err = new Error('代理模式发生直连回退，任务中止')
        err.code = 'PROXY_REQUIRED_FALLBACK_DIRECT'
        err.retryable = true
        throw err
    }
    if (!meta?.used_proxy) {
        const err = new Error('代理模式未使用代理节点')
        err.code = 'PROXY_REQUIRED_NO_PROXY_USED'
        err.retryable = true
        throw err
    }
}

function toAxiosResponse(forwardData, responseType) {
    if (!forwardData || typeof forwardData !== 'object') return null
    const status = forwardData.status ?? forwardData.statusCode
    if (status == null) return null

    let data = forwardData.body
    if (data && typeof data === 'object' && data.__encoding === 'base64' && data.data) {
        data = Buffer.from(String(data.data), 'base64')
    }

    return {
        status,
        statusText: String(status),
        headers: forwardData.headers || {},
        data,
        config: {}
    }
}

function isValidForwardPayload(json) {
    return json && json.code === 0 && json.data != null && (json.data.status ?? json.data.statusCode) != null
}

/**
 * @param {{
 *   method?: string
 *   url: string
 *   data?: any
 *   headers?: object
 *   timeout?: number
 *   outboundMode?: 'auto'|'direct'|'proxy'
 *   validateStatus?: Function
 *   responseType?: string
 *   transformResponse?: Function[]
 *   logger?: object
 *   logPrefix?: string
 *   scene?: string
 * }} opts
 */
async function forwardRequest(opts) {
    const {
        method = 'get',
        url,
        data,
        headers = {},
        timeout = 15000,
        outboundMode = 'auto',
        validateStatus,
        responseType,
        transformResponse,
        logger,
        logPrefix = '[ProxyForwardClient]',
        scene = 'outbound'
    } = opts

    const m = String(method).toLowerCase()
    const path = briefUrlPath(url)

    if (outboundMode === 'direct') {
        logger?.info?.(`${logPrefix} (${scene}) 直连 ${m.toUpperCase()} ${path}`)
        return execDirect({ method: m, url, data, headers, timeout, validateStatus, responseType, transformResponse })
    }

    if (!await isProxyForwardEnabled()) {
        logger?.info?.(`${logPrefix} (${scene}) 未配置代理服务，直连 ${path}`)
        if (outboundMode === 'proxy') {
            const err = new Error('未配置 proxyForwardServer.url')
            err.code = 'PROXY_REQUIRED_NOT_CONFIGURED'
            err.retryable = false
            throw err
        }
        return execDirect({ method: m, url, data, headers, timeout, validateStatus, responseType, transformResponse })
    }

    const { url: serverUrl, defaultTimeout } = await getServerConfig()
    logger?.info?.(`${logPrefix} (${scene}) 经代理服务 ${m.toUpperCase()} ${path}`)

    const reqHeaders = normalizeForwardHeaders(headers)
    const reqBody = serializeForwardBody(data)

    let forwardResp
    try {
        forwardResp = await axios.post(
            `${serverUrl}/Proxy/Forward`,
            {
                url,
                method: m.toUpperCase(),
                headers: reqHeaders,
                body: reqBody,
                timeout,
                ...(responseType ? { responseType } : {})
            },
            {
                timeout: Math.min(defaultTimeout, timeout + 10000),
                validateStatus: () => true,
                headers: { 'Content-Type': 'application/json' }
            }
        )
    } catch (e) {
        if (outboundMode === 'proxy') {
            const err = new Error(`代理服务不可达: ${e.message || e}`)
            err.code = 'PROXY_SERVER_UNREACHABLE'
            err.retryable = true
            throw err
        }
        logger?.warn?.(`${logPrefix} (${scene}) 代理服务请求失败，改直连: ${e.message || e}`)
        return execDirect({ method: m, url, data, headers, timeout, validateStatus, responseType, transformResponse })
    }

    const json = forwardResp.data || {}
    if (!isValidForwardPayload(json)) {
        const reason = json.code !== 0 ? (json.msg || 'unknown') : '代理响应缺少 status'
        if (outboundMode === 'proxy') {
            const err = new Error(json.msg || reason || '代理转发失败')
            err.code = 'PROXY_FORWARD_FAILED'
            err.retryable = true
            throw err
        }
        logger?.warn?.(`${logPrefix} (${scene}) 代理响应无效，改直连: ${reason}`)
        return execDirect({ method: m, url, data, headers, timeout, validateStatus, responseType, transformResponse })
    }

    const meta = json.data?.meta || {}
    assertProxyModeOk(outboundMode, meta)

    logger?.info?.(
        `${logPrefix} (${scene}) 完成 status=${json.data?.status ?? json.data?.statusCode} used_proxy=${meta.used_proxy} fallback=${meta.fallback_direct} ${meta.duration_ms}ms`
    )

    const axiosResp = toAxiosResponse(json.data, responseType)
    if (!axiosResp) {
        if (outboundMode === 'proxy') {
            const err = new Error('代理响应缺少 HTTP status')
            err.code = 'PROXY_FORWARD_BAD_RESPONSE'
            err.retryable = true
            throw err
        }
        logger?.warn?.(`${logPrefix} (${scene}) 代理响应缺少 status，改直连`)
        return execDirect({ method: m, url, data, headers, timeout, validateStatus, responseType, transformResponse })
    }

    if (validateStatus && !validateStatus(axiosResp.status)) {
        const err = new Error(`Request failed with status code ${axiosResp.status}`)
        err.response = axiosResp
        err.isAxiosError = true
        throw err
    }

    return axiosResp
}

module.exports = {
    forwardRequest,
    isProxyForwardEnabled,
    debugProxyEnabled,
    debugProxyAxiosFragment,
    briefUrlPath,
    buildDirectAxiosConfig,
    execDirect,
    getServerConfig,
    normalizeForwardHeaders,
    serializeForwardBody
}
