import Foundation

enum HTTPClientError: LocalizedError {
    case invalidURL
    case badStatus(Int, String)

    var errorDescription: String? {
        switch self {
        case .invalidURL:
            return "无效的服务地址"
        case .badStatus(let code, let body):
            return "HTTP \(code): \(body)"
        }
    }
}

struct HTTPResult {
    let statusCode: Int
    let body: Data
    let effectiveBaseURL: String
}

enum HTTPClient {
    static func getJSON<T: Decodable>(
        _ type: T.Type,
        baseURL: String,
        path: String,
        timeout: TimeInterval
    ) async throws -> (value: T, effectiveBaseURL: String) {
        let result = try await execute(
            baseURL: baseURL,
            path: path,
            method: "GET",
            timeout: timeout,
            headers: ["Accept": "application/json"]
        )
        guard (200...299).contains(result.statusCode) else {
            let body = String(data: result.body, encoding: .utf8) ?? ""
            throw HTTPClientError.badStatus(result.statusCode, body)
        }
        let value = try JSONDecoder().decode(T.self, from: result.body)
        return (value, result.effectiveBaseURL)
    }

    static func execute(
        baseURL: String,
        path: String,
        method: String,
        timeout: TimeInterval,
        headers: [String: String] = [:],
        body: Data? = nil
    ) async throws -> HTTPResult {
        let trimmedBase = baseURL.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
        guard let base = URL(string: trimmedBase), base.scheme != nil, base.host != nil else {
            throw HTTPClientError.invalidURL
        }
        let endpoint = URL(string: path, relativeTo: base) ?? base
        do {
            return try await perform(
                url: endpoint,
                method: method,
                timeout: timeout,
                headers: headers,
                body: body,
                effectiveBaseURL: trimmedBase
            )
        } catch {
            // 与安卓端一致：HTTPS 因 TLS 问题失败时，降级用同一请求重试 HTTP。
            guard AppConfig.allowHTTPFallback,
                  endpoint.scheme == "https",
                  isTLSError(error) else {
                throw error
            }
            var baseComponents = URLComponents(string: trimmedBase)
            baseComponents?.scheme = "http"
            guard let httpBase = baseComponents?.string,
                  let httpURL = URL(string: path, relativeTo: URL(string: httpBase)) else {
                throw error
            }
            return try await perform(
                url: httpURL,
                method: method,
                timeout: timeout,
                headers: headers,
                body: body,
                effectiveBaseURL: httpBase
            )
        }
    }

    private static func perform(
        url: URL,
        method: String,
        timeout: TimeInterval,
        headers: [String: String],
        body: Data?,
        effectiveBaseURL: String
    ) async throws -> HTTPResult {
        var request = URLRequest(url: url)
        request.httpMethod = method
        request.timeoutInterval = timeout
        request.setValue(AppConfig.userAgent, forHTTPHeaderField: "User-Agent")
        for (key, value) in headers {
            request.setValue(value, forHTTPHeaderField: key)
        }
        request.httpBody = body
        let (data, response) = try await URLSession.shared.data(for: request)
        guard let http = response as? HTTPURLResponse else {
            throw HTTPClientError.invalidURL
        }
        return HTTPResult(statusCode: http.statusCode, body: data, effectiveBaseURL: effectiveBaseURL)
    }

    private static func isTLSError(_ error: Error) -> Bool {
        let nsError = error as NSError
        let tlsCodes: [Int] = [
            NSURLErrorSecureConnectionFailed,
            NSURLErrorServerCertificateHasBadDate,
            NSURLErrorServerCertificateUntrusted,
            NSURLErrorServerCertificateHasUnknownRoot,
            NSURLErrorServerCertificateNotYetValid,
            NSURLErrorClientCertificateRejected,
            NSURLErrorClientCertificateRequired,
            NSURLErrorCannotLoadFromNetwork,
        ]
        if tlsCodes.contains(nsError.code) {
            return true
        }
        let message = (nsError.localizedDescription + " " + (nsError.localizedFailureReason ?? ""))
            .lowercased()
        return message.contains("ssl") || message.contains("tls") || message.contains("certificate")
    }
}
