//
//  APIClient.swift - 通用 HTTP 客户端
//

import Foundation

actor APIClient {
    static let shared = APIClient()

    private let session: URLSession
    private var token: String?

    private init() {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = APIConfig.timeout
        config.timeoutIntervalForResource = 60
        session = URLSession(configuration: config)
    }

    func setToken(_ token: String?) {
        self.token = token
    }

    // MARK: - Generic request

    func request<T: Decodable>(
        _ path: String,
        method: String = "GET",
        body: Encodable? = nil,
        queryItems: [URLQueryItem]? = nil
    ) async throws -> T {
        try await performRequest(path, method: method, body: body, queryItems: queryItems, isRetry: false)
    }

    private func performRequest<T: Decodable>(
        _ path: String,
        method: String,
        body: Encodable?,
        queryItems: [URLQueryItem]?,
        isRetry: Bool
    ) async throws -> T {
        var components = URLComponents(url: APIConfig.url(path), resolvingAgainstBaseURL: true)!
        if let queryItems { components.queryItems = queryItems }

        var request = URLRequest(url: components.url!)
        request.httpMethod = method
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("application/json", forHTTPHeaderField: "Accept")

        if let token {
            request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }

        if let body {
            request.httpBody = try JSONEncoder().encode(AnyEncodable(body))
        }

        let (data, response) = try await session.data(for: request)

        guard let httpResponse = response as? HTTPURLResponse else {
            throw APIError.networkError(NSError(domain: "", code: -1))
        }

        switch httpResponse.statusCode {
        case 200, 201:
            return try decodeResponse(data)
        case 401 where !isRetry:
            if let newToken = await refreshAccessToken() {
                self.token = newToken
                return try await performRequest(path, method: method, body: body, queryItems: queryItems, isRetry: true)
            }
            let info = extractErrorInfo(data)
            await notifyTokenExpired(errorCode: info.errorCode)
            throw decodeServerError(data, fallback: APIError.unauthorized)
        case 401:
            let info = extractErrorInfo(data)
            throw APIError.serverError(info.message ?? "未授权", code: info.errorCode)
        case 400..<500:
            let msg = String(data: data, encoding: .utf8) ?? ""
            throw APIError.serverError(msg)
        default:
            throw APIError.requestFailed(httpResponse.statusCode)
        }
    }

    private func refreshAccessToken() async -> String? {
        guard let refreshToken = KeychainHelper.getRefreshToken() else { return nil }
        do {
            let body = RefreshRequest(refreshToken: refreshToken)
            let resp: AuthResponse = try await performRequest(
                "/auth/refresh", method: "POST", body: body, queryItems: nil, isRetry: true
            )
            KeychainHelper.save(
                accessToken: resp.accessToken,
                refreshToken: resp.refreshToken,
                userId: resp.user?.id ?? ""
            )
            return resp.accessToken
        } catch {
            return nil
        }
    }

    private func notifyTokenExpired(errorCode: String? = nil) async {
        var userInfo: [AnyHashable: Any]? = nil
        if let code = errorCode {
            userInfo = ["errorCode": code]
        }
        await MainActor.run {
            NotificationCenter.default.post(name: .tokenExpired, object: nil, userInfo: userInfo)
        }
    }

    // MARK: - Server error decoding

    /// 从服务端错误响应中提取 message 和 errorCode
    func extractErrorInfo(_ data: Data) -> (message: String?, errorCode: String?) {
        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
            return (nil, nil)
        }
        return (
            message: json["message"] as? String,
            errorCode: json["errorCode"] as? String
        )
    }

    private func decodeServerError(_ data: Data, fallback: APIError) -> APIError {
        let info = extractErrorInfo(data)
        if let serverMsg = info.message {
            return APIError.serverError(serverMsg, code: info.errorCode)
        }
        return fallback
    }
}

// MARK: - Server error info (public for AuthManager)

struct ServerErrorInfo {
    let message: String
    let errorCode: String?
}

extension APIClient {
    nonisolated func parseErrorInfo(from data: Data) -> ServerErrorInfo? {
        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let msg = json["message"] as? String else {
            return nil
        }
        return ServerErrorInfo(message: msg, errorCode: json["errorCode"] as? String)
    }

    // MARK: - Decoding

    private func decodeResponse<T: Decodable>(_ data: Data) throws -> T {
        let decoder = JSONDecoder()
        // Try unwrapped response first (no envelope), then wrapped
        if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
           json["data"] == nil && json["success"] == nil {
            return try decoder.decode(T.self, from: data)
        }
        // Has envelope wrapper
        do {
            let envelope = try decoder.decode(APIEnvelope<T>.self, from: data)
            return envelope.data
        } catch {
            // Fallback: try decoding T directly (e.g. server returns unwrapped on some endpoints)
            return try decoder.decode(T.self, from: data)
        }
    }
}

// MARK: - Helper for encoding arbitrary Encodable

struct AnyEncodable: Encodable {
    let value: Encodable
    init(_ value: Encodable) { self.value = value }
    func encode(to encoder: Encoder) throws { try value.encode(to: encoder) }
}
