//
//  APIService.swift
//  AppClientBaseSwift
//
//  HTTP client service using URLSession
//  Dịch vụ HTTP client sử dụng URLSession
//

import Foundation

// MARK: - API Error
// Lỗi API

/// API error types
/// Các loại lỗi API
enum APIError: Error, LocalizedError {
    case invalidURL
    case noData
    case decodingError(Error)
    case networkError(Error)
    case serverError(statusCode: Int, message: String?)
    case unauthorized
    case forbidden
    case notFound
    case rateLimited
    case unknown

    var errorDescription: String? {
        switch self {
        case .invalidURL:
            return "Invalid URL / URL không hợp lệ"
        case .noData:
            return "No data received / Không nhận được dữ liệu"
        case .decodingError(let error):
            return "Decoding error: \(error.localizedDescription)"
        case .networkError(let error):
            return "Network error: \(error.localizedDescription)"
        case .serverError(let code, let message):
            return "Server error (\(code)): \(message ?? "Unknown")"
        case .unauthorized:
            return "Unauthorized / Chưa xác thực"
        case .forbidden:
            return "Access forbidden / Truy cập bị từ chối"
        case .notFound:
            return "Resource not found / Không tìm thấy tài nguyên"
        case .rateLimited:
            return "Rate limited / Giới hạn request"
        case .unknown:
            return "Unknown error / Lỗi không xác định"
        }
    }
}

// MARK: - OAuth2 Response Models
// Models response OAuth2

/// OAuth2 token response
/// Response token OAuth2
/// Note: No CodingKeys needed - decoder uses convertFromSnakeCase strategy
/// Lưu ý: Không cần CodingKeys - decoder dùng convertFromSnakeCase tự động
struct OAuthTokenResponse: Decodable {
    let accessToken: String
    let tokenType: String
    let expiresIn: Int
    let refreshToken: String?
    let scope: String?
}

/// OAuth2 error response
/// Response lỗi OAuth2
/// Note: No CodingKeys needed - decoder uses convertFromSnakeCase strategy
/// Lưu ý: Không cần CodingKeys - decoder dùng convertFromSnakeCase tự động
struct OAuthErrorResponse: Decodable {
    let error: String
    let errorDescription: String?
}

// MARK: - HTTP Method
// Phương thức HTTP

/// HTTP request methods
/// Các phương thức request HTTP
enum HTTPMethod: String {
    case get = "GET"
    case post = "POST"
    case put = "PUT"
    case patch = "PATCH"
    case delete = "DELETE"
}

// MARK: - API Service Protocol
// Protocol dịch vụ API

/// API service protocol for dependency injection
/// Protocol dịch vụ API cho dependency injection
protocol APIServiceProtocol {
    func request<T: Decodable>(
        endpoint: String,
        method: HTTPMethod,
        body: Encodable?,
        headers: [String: String]?
    ) async throws -> T
}

// MARK: - API Service
// Dịch vụ API

/// Main API service for network requests
/// Dịch vụ API chính cho các request network
final class APIService: APIServiceProtocol {

    // MARK: - Properties

    /// Shared singleton instance
    /// Instance singleton dùng chung
    static let shared = APIService()

    /// URLSession for requests
    /// URLSession cho các request
    private let session: URLSession

    /// JSON encoder
    /// Bộ mã hóa JSON
    private let encoder: JSONEncoder

    /// JSON decoder
    /// Bộ giải mã JSON
    private let decoder: JSONDecoder

    // MARK: - Init

    /// Initialize API service
    /// Khởi tạo dịch vụ API
    /// - Parameter session: URLSession instance / Instance URLSession
    init(session: URLSession = .shared) {
        self.session = session

        self.encoder = JSONEncoder()
        encoder.keyEncodingStrategy = .convertToSnakeCase
        encoder.dateEncodingStrategy = .iso8601

        self.decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        decoder.dateDecodingStrategy = .iso8601
    }

    // MARK: - Public Methods

    /// Perform network request
    /// Thực hiện request network
    /// - Parameters:
    ///   - endpoint: API endpoint path / Đường dẫn endpoint API
    ///   - method: HTTP method / Phương thức HTTP
    ///   - body: Request body / Body request
    ///   - headers: Additional headers / Headers bổ sung
    /// - Returns: Decoded response / Response đã giải mã
    func request<T: Decodable>(
        endpoint: String,
        method: HTTPMethod = .get,
        body: Encodable? = nil,
        headers: [String: String]? = nil
    ) async throws -> T {

        // Build URL
        // Xây dựng URL
        guard let url = URL(string: APIConfig.baseURL + APIConfig.apiVersion + endpoint) else {
            throw APIError.invalidURL
        }

        // Create request
        // Tạo request
        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue
        request.timeoutInterval = APIConfig.timeout

        // Set default headers
        // Đặt headers mặc định
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("application/json", forHTTPHeaderField: "Accept")

        // Add auth token if available
        // Thêm token xác thực nếu có
        if let token = AuthManager.shared.accessToken {
            request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }

        // Add custom headers
        // Thêm headers tùy chỉnh
        headers?.forEach { key, value in
            request.setValue(value, forHTTPHeaderField: key)
        }

        // Set body
        // Đặt body
        if let body = body {
            request.httpBody = try encoder.encode(body)
        }

        // Log request for debugging
        // Ghi log request để debug
        #if DEBUG
        DebugLogger.logRequest(
            method: method.rawValue,
            url: url.absoluteString,
            headers: request.allHTTPHeaderFields,
            body: request.httpBody
        )
        #endif

        // Perform request
        // Thực hiện request
        let (data, response) = try await session.data(for: request)

        // Handle response
        // Xử lý response
        guard let httpResponse = response as? HTTPURLResponse else {
            throw APIError.unknown
        }

        // Log response for debugging
        // Ghi log response để debug
        #if DEBUG
        DebugLogger.logResponse(statusCode: httpResponse.statusCode, data: data)
        #endif

        // Check status code
        // Kiểm tra status code
        switch httpResponse.statusCode {
        case 200...299:
            // Try to decode the response
            // Thử decode response
            do {
                let result = try decoder.decode(T.self, from: data)
                
                #if DEBUG
                print("✅ Decoded successfully: \(T.self)")
                #endif
                
                return result
            } catch let decodingError as DecodingError {
                // Enhanced decoding error details
                // Chi tiết lỗi decoding được cải thiện
                let errorDetails = formatDecodingError(decodingError, data: data)
                
                #if DEBUG
                print("❌ Decoding Error Details:")
                print(errorDetails)
                print("\n💡 Debugging Tips:")
                print("1. Check if server returns wrapper format: { success, data, error }")
                print("2. Verify all field names match between API and model")
                print("3. Use APITestView to see raw JSON response")
                print("4. Check if snake_case/camelCase conversion is working")
                #endif
                
                throw APIError.decodingError(decodingError)
            } catch {
                #if DEBUG
                print("❌ Unknown decoding error: \(error)")
                #endif
                throw APIError.decodingError(error)
            }
        case 401:
            await AuthManager.shared.handleUnauthorized()
            throw APIError.unauthorized
        case 403:
            throw APIError.forbidden
        case 404:
            throw APIError.notFound
        case 429:
            throw APIError.rateLimited
        default:
            let message = String(data: data, encoding: .utf8)
            #if DEBUG
            print("❌ Server Error (\(httpResponse.statusCode)): \(message ?? "No message")")
            #endif
            throw APIError.serverError(statusCode: httpResponse.statusCode, message: message)
        }
    }

    // MARK: - Convenience Methods
    // Các phương thức tiện ích

    /// GET request
    /// Request GET
    func get<T: Decodable>(endpoint: String) async throws -> T {
        try await request(endpoint: endpoint, method: .get, body: nil as String?, headers: nil)
    }

    /// POST request
    /// Request POST
    func post<T: Decodable, B: Encodable>(endpoint: String, body: B) async throws -> T {
        try await request(endpoint: endpoint, method: .post, body: body, headers: nil)
    }

    /// PUT request
    /// Request PUT
    func put<T: Decodable, B: Encodable>(endpoint: String, body: B) async throws -> T {
        try await request(endpoint: endpoint, method: .put, body: body, headers: nil)
    }

    /// DELETE request
    /// Request DELETE
    func delete<T: Decodable>(endpoint: String) async throws -> T {
        try await request(endpoint: endpoint, method: .delete, body: nil as String?, headers: nil)
    }

    // MARK: - OAuth2 Methods
    // Các phương thức OAuth2

    /// Format decoding error for debugging
    /// Format lỗi decoding cho debugging
    private func formatDecodingError(_ error: DecodingError, data: Data) -> String {
        var details = ""
        
        switch error {
        case .typeMismatch(let type, let context):
            details = """
            🔴 Type Mismatch:
            - Expected type: \(type)
            - Coding path: \(context.codingPath.map { $0.stringValue }.joined(separator: " -> "))
            - Debug description: \(context.debugDescription)
            """
            
        case .valueNotFound(let type, let context):
            details = """
            🔴 Value Not Found:
            - Missing type: \(type)
            - Coding path: \(context.codingPath.map { $0.stringValue }.joined(separator: " -> "))
            - Debug description: \(context.debugDescription)
            """
            
        case .keyNotFound(let key, let context):
            details = """
            🔴 Key Not Found:
            - Missing key: \(key.stringValue)
            - Coding path: \(context.codingPath.map { $0.stringValue }.joined(separator: " -> "))
            - Debug description: \(context.debugDescription)
            """
            
        case .dataCorrupted(let context):
            details = """
            🔴 Data Corrupted:
            - Coding path: \(context.codingPath.map { $0.stringValue }.joined(separator: " -> "))
            - Debug description: \(context.debugDescription)
            """
            
        @unknown default:
            details = "🔴 Unknown decoding error: \(error)"
        }
        
        // Add raw JSON response for debugging
        // Thêm raw JSON response cho debugging
        if let jsonString = String(data: data, encoding: .utf8) {
            details += "\n\n📄 Raw JSON Response:\n\(jsonString)"
        }
        
        return details
    }

    // MARK: - OAuth2 Methods
    // Các phương thức OAuth2

    /// POST form-urlencoded request (for OAuth2 token endpoint)
    /// Request POST dạng form-urlencoded (cho OAuth2 token endpoint)
    /// - Parameters:
    ///   - endpoint: Token endpoint path / Đường dẫn token endpoint
    ///   - formData: Form data dictionary / Dictionary form data
    /// - Returns: Decoded response / Response đã giải mã
    func postForm<T: Decodable>(endpoint: String, formData: [String: String]) async throws -> T {
        guard let url = URL(string: APIConfig.baseURL + endpoint) else {
            throw APIError.invalidURL
        }

        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.timeoutInterval = APIConfig.timeout

        // Encode form data
        // Mã hóa form data
        let formBody = formData.map { key, value in
            let escapedValue = value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
            return "\(key)=\(escapedValue)"
        }.joined(separator: "&")
        request.httpBody = formBody.data(using: .utf8)

        // Perform request
        // Thực hiện request
        let (data, response) = try await session.data(for: request)

        guard let httpResponse = response as? HTTPURLResponse else {
            throw APIError.unknown
        }

        // Handle OAuth2 error responses
        // Xử lý OAuth2 error responses
        if httpResponse.statusCode != 200 {
            if let errorResponse = try? decoder.decode(OAuthErrorResponse.self, from: data) {
                throw APIError.serverError(
                    statusCode: httpResponse.statusCode,
                    message: errorResponse.errorDescription ?? errorResponse.error
                )
            }
            let message = String(data: data, encoding: .utf8)
            throw APIError.serverError(statusCode: httpResponse.statusCode, message: message)
        }

        do {
            return try decoder.decode(T.self, from: data)
        } catch {
            throw APIError.decodingError(error)
        }
    }
}
