//
//  Network.swift
//  Werkout_ios
//
//  Created by Trey Tartt on 6/19/23.
//

import Foundation

enum FetchableError: Error {
    case apiError(Error)
    case noData
    case decodeError(Error)
    case endOfFileError
    case noPostData
    case noToken
    case statusError(Int, String?)
}

protocol Fetchable {
    associatedtype Response: Codable
    var attachToken: Bool { get }
    var baseURL: String { get }
    var endPoint: String { get }
    func fetch(completion: @escaping (Result<Response, FetchableError>) -> Void)
}

protocol Postable: Fetchable {
    var postableData: [String: Any]? { get }
    var successStatus: Int { get }
}

extension Fetchable {
    var baseURL: String {
        BaseURLs.currentBaseURL
    }
    
    var attachToken: Bool {
        return true
    }
    
    func fetch(completion: @escaping (Result<Response, FetchableError>) -> Void) {
        let url = URL(string: baseURL+endPoint)!
    
        var request = URLRequest(url: url,timeoutInterval: Double.infinity)
        if attachToken {
            guard let token = UserStore.shared.token else {
                completion(.failure(.noPostData))
                return
            }
            request.addValue(token, forHTTPHeaderField: "Authorization")
        }
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        
        let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
            if let error = error {
                completion(.failure(.apiError(error)))
                return
            }
            guard let data = data else {
                completion(.failure(.noData))
                return
            }
            
            do {
                let model = try JSONDecoder().decode(Response.self, from: data)
                completion(.success(model))
                return
            } catch {
                completion(.failure(.decodeError(error)))
                return
            }
        })
        
        task.resume()
    }
}

extension Postable {
   func fetch(completion: @escaping (Result<Response, FetchableError>) -> Void) {
       guard let postableData = postableData else {
           completion(.failure(.noPostData))
            return
        }
       
       let url = URL(string: baseURL+endPoint)!
       
       let postData = try! JSONSerialization.data(withJSONObject:postableData)
       
        var request = URLRequest(url: url,timeoutInterval: Double.infinity)
       if attachToken {
           guard let token = UserStore.shared.token else {
               completion(.failure(.noPostData))
               return
           }
           request.addValue(token, forHTTPHeaderField: "Authorization")
       }
       request.addValue("application/json", forHTTPHeaderField: "Content-Type")
       
       request.httpMethod = "POST"
       request.httpBody = postData
        
        let task = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
            if let error = error {
                completion(.failure(.apiError(error)))
                return
            }
            
            if let httpRespone = response as? HTTPURLResponse {
                if httpRespone.statusCode != successStatus {
                    var returnStr: String?
                    if let data = data {
                        returnStr = String(data: data, encoding: .utf8)
                    }
                    
                    completion(.failure(.statusError(httpRespone.statusCode, returnStr)))
                    return
                }
            }
            
            guard let data = data else {
                completion(.failure(.noData))
                return
            }
            
            do {
                let model = try JSONDecoder().decode(Response.self, from: data)
                completion(.success(model))
                return
            } catch {
                completion(.failure(.decodeError(error)))
                return
            }
        })
        
        task.resume()
    }
}
