//
//  NetworkManager.swift
//  WOKA
//
//  Created by MacBook Pro on 06/05/24.
//

import Alamofire

class NetworkManager{
    
    static let shareInstance = NetworkManager()
    private let alamofireLogger = AlamofireLogger()

    private init() {}
    
    enum APIError: Error, LocalizedError {
        case networkError
        case noNetwork(message:String)
        case invalidURL
        case parameterEncodingFailed
        case responseValidationFailed
        case unknown(message: String)
        case custom(message: String)
        
        var errorDescription: String? {
            switch self {
            case .networkError:
                return "A network error occurred."
            case .noNetwork(let message):
                return "No network: \(message)"
            case .invalidURL:
                return "The URL provided is invalid."
            case .parameterEncodingFailed:
                return "Failed to encode the parameters."
            case .responseValidationFailed:
                return "Response validation failed."
            case .unknown(let message):
                return "Unknown error: \(message)"
            case .custom(let message):
                return "Custom error: \(message)"
            }
        }
    }
    
    /// This function will do the network call for HTTPMethod & Encoding is URLEncoding with contentType ["application/json"]
    ///
    ///
    /// - Warning: The returned string is not localized.
    ///
    /// Usage:
    ///
    ///     Alamofire network call Generic (.get , .post, .put.).
    ///
    /// - Parameter (header : HTTPHeaders , Params :[String : Any] , URL , Dedocable Generic T Struct. , queue : dispatchQueue decide)
    /// - NOTE Oncompletion will be called on main thread so no need to specify the main thread for UI updates
    /// - Returns: This function returns a GENERIC response base on the T Model & APIError .
    func apiRequest<T: Codable & ResponseProtocol>(
        url: URLConvertible,
        method: HTTPMethod,
        parameters: Parameters? = nil,
        encoding: ParameterEncoding = URLEncoding.default,
        headers: HTTPHeaders? = nil,
        queue: DispatchQueue = QueueHelper.background,
        completionHandler: @escaping (Result<T, APIError>) -> Void
    ) {
        // Stop monitoring network reachability
        NetworkReachibility.shared.stopMonitoring()
        let loginCred = getLoginIDPass()
        // Execute the request on the specified queue
        queue.async {
            AF.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers, requestModifier: { $0.timeoutInterval = 20 })
                .authenticate(username: loginCred.0, password: loginCred.1)
                .validate(statusCode: 200..<300)
                .responseDecodable(of: T.self) { response in
//                    alamofireLogger.request(response.da, didParseResponse: response)
                    switch response.result {
                    case .success(let value):
                        /* 
                         if Sucess == 4 menas user has logined in some other device, logout that user by saying session timeout
                         */
                        if value.success == 4{
                            if let topController = UIApplication.topViewController() {
                                Utilities.dismissProgressHUD()
                                let sb = UIStoryboard(name: K.StoryBoard.customAlerts, bundle: nil)
                                let vcPush = sb.instantiateViewController(withIdentifier: K.StoryBoardID.CustomAlerts.alertCustomVC) as! AlertCustomVC
                                vcPush.contentLabel = "Please Login Again"
                                vcPush.mainTitleText = "Session Expired."
                                vcPush.yesBtnText = "Login"
                                vcPush.outsideViewTapGestureEnabled = false
                                vcPush.onDoneBlock = { isDone in
                                    AuthFunc.shareInstance.logout()
                                    UIApplication.setRootView(LoginNavVC.instantiate(from: .AuthenticationSB))
                                }
                                vcPush.modalPresentationStyle = .overCurrentContext
                                vcPush.modalTransitionStyle = .crossDissolve
                                topController.present(vcPush, animated: true)
                                
//                                Utilities.alertWithBtnCompletion(title: "Session Expired", msgBody: "Please Login Again", okBtnStr: "OK", vc: topController) { isDone in
//                                    AuthFunc.shareInstance.logout()
//                                    UIApplication.setRootView(LoginNavVC.instantiate(from: .AuthenticationSB))
//                                }
                            }
                            return
                        }
                        
                        // Handle successful response on the main thread
                        DispatchQueue.main.async {
                            completionHandler(.success(value))
                        }
                    case .failure(let error):
                        // Handle failure cases
                        if let afError = error as? AFError {
                            switch afError {
                            case .sessionTaskFailed(let urlError):
                                if urlError._code == -1020 || urlError._code == -1009 {
                                    // Handle network error on the main thread
                                    DispatchQueue.main.async {
                                        completionHandler(.failure(.noNetwork(message: K.ConstantString.noInternet)))
                                    }
                                } else {
                                    // Handle other network errors on the main thread
                                    DispatchQueue.main.async {
                                        completionHandler(.failure(.networkError))
                                    }
                                }
                            case .invalidURL:
                                // Handle invalid URL error on the main thread
                                DispatchQueue.main.async {
                                    completionHandler(.failure(.invalidURL))
                                }
                            case .parameterEncodingFailed:
                                // Handle parameter encoding failure on the main thread
                                DispatchQueue.main.async {
                                    completionHandler(.failure(.parameterEncodingFailed))
                                }
                            case .responseValidationFailed:
                                // Handle response validation failure on the main thread
                                DispatchQueue.main.async {
                                    completionHandler(.failure(.responseValidationFailed))
                                }
                            default:
                                // Handle other network errors on the main thread
                                DispatchQueue.main.async {
                                    completionHandler(.failure(.networkError))
                                }
                            }
                        } else {
                            // Handle unrecognized errors on the main thread
                            DispatchQueue.main.async {
                                completionHandler(.failure(.unknown(message: K.ConstantString.unRecognised)))
                            }
                        }
                    }
                }
        }
    }
    
    func getLoginIDPass()-> (String, String){
        if let loginID = Bundle.main.infoDictionary?["API_KEY_ID"] as? String , let loginPaass = Bundle.main.infoDictionary?["API_KEY_PASS"] as? String{
            let cleanedLoginID = loginID.trimmingCharacters(in: CharacterSet(charactersIn: "\""))
            let cleanedLoginPass = loginPaass.trimmingCharacters(in: CharacterSet(charactersIn: "\""))
            return (cleanedLoginID, cleanedLoginPass)
        }
        return ("","")
    }
    
    /// This function will do the network call for POST With RAWJSON & Encoding is URLEncoding with contentType ["application/json"]
    ///
    ///
    /// - Warning: The returned string is not localized.
    ///
    /// Usage:
    ///
    ///     Alamofire network call POST(RawJSON).
    ///
    /// - Parameter (header : HTTPHeaders , Params :[String : Any] , URL , Dedocable Generic T Struct.)
    ///
    /// - Returns: This function returns a GENERIC response base on the T Model & ERROR .
    func nwCallRawJSON(clicksData : [ClicksAnalytics], onCompletion : @escaping (Bool) -> Void){
        let loginCred = getLoginIDPass()
        let encoder = JSONEncoder()
        encoder.keyEncodingStrategy = .convertToSnakeCase
        guard let jsonData = try? encoder.encode(clicksData),
              let jsonArray = try? JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) as? [[String: Any]] else {
            print("Failed to encode totalClicks array to JSON")
            return
        }
        
        let url = APIEndPoints.Analytics.user_clicks
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.headers = ["device-id" : AuthFunc.shareInstance.getDeviceUUID(),
                           "access-token" : AuthFunc.shareInstance.getAccessToken()]
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        do {
            // Set the HTTP body with the JSON data
            request.httpBody = try JSONSerialization.data(withJSONObject: jsonArray)
        } catch let error {
            print("Error: \(error.localizedDescription)")
            return
        }
        
        AF.request(request).authenticate(username: loginCred.0, password: loginCred.1)
            .validate(statusCode: 200..<300)
            .responseDecodable(of: CommonResponseModel.self) { response in
                switch response.result {
                case .success(let data):
                    onCompletion(true)
                case .failure(let error):
                    onCompletion(false)
                }
            }
    }
    
    func nwCallRawJSONAds(adsData : [AdsClickImpressionsData], onCompletion : @escaping (Bool) -> Void){
        let loginCred = getLoginIDPass()
        let encoder = JSONEncoder()
        encoder.keyEncodingStrategy = .convertToSnakeCase
        guard let jsonData = try? encoder.encode(adsData),
              let jsonArray = try? JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) as? [[String: Any]] else {
            print("Failed to encode totalClicks array to JSON")
            return
        }
        
        let url = APIEndPoints.Analytics.update_ad_count
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.headers = ["device-id" : AuthFunc.shareInstance.getDeviceUUID(),
                           "access-token" : AuthFunc.shareInstance.getAccessToken()]
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        do {
            // Set the HTTP body with the JSON data
            request.httpBody = try JSONSerialization.data(withJSONObject: jsonArray)
        } catch let error {
            print("Error: \(error.localizedDescription)")
            return
        }
        
        AF.request(request).authenticate(username: loginCred.0, password: loginCred.1)
            .validate(statusCode: 200..<300)
            .responseDecodable(of: CommonResponseModel.self) { response in
                switch response.result {
                case .success(let data):
                    onCompletion(true)
                case .failure(let error):
                    onCompletion(false)
                }
            }
    }
}


final class AlamofireLogger: EventMonitor {

    func requestDidResume(_ request: Request) {

        let allHeaders = request.request.flatMap { $0.allHTTPHeaderFields.map { $0.description } } ?? "None"
        let headers = """
        ⚡️⚡️⚡️⚡️ Request Started: \(request)
        ⚡️⚡️⚡️⚡️ Headers: \(allHeaders)
        """
        NSLog(headers)


        let body = request.request.flatMap { $0.httpBody.map { String(decoding: $0, as: UTF8.self) } } ?? "None"
        let message = """
        ⚡️⚡️⚡️⚡️ Request Started: \(request)
        ⚡️⚡️⚡️⚡️ Body Data: \(body)
        """
        NSLog(message)
    }

    func request<Value>(_ request: DataRequest, didParseResponse response: AFDataResponse<Value>) {

        NSLog("⚡️⚡️⚡️⚡️ Response Received: \(response.debugDescription)")
        NSLog("⚡️⚡️⚡️⚡️ Response All Headers: \(String(describing: response.response?.allHeaderFields))")
    }
}


extension UIApplication {
    class func topViewController(_ base: UIViewController? = UIApplication.shared.mainKeyWindow?.rootViewController) -> UIViewController? {
        if let nav = base as? UINavigationController {
            return topViewController(nav.visibleViewController)
        }
        if let tab = base as? UITabBarController {
            if let selected = tab.selectedViewController {
                return topViewController(selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topViewController(presented)
        }
        return base
    }
}
