import Foundation

public struct SwiftDevRant {
    let request: Request
    let backend = DevRantBackend()
    
    public init(requestLogger: Logger) {
        self.request = Request(encoder: .devRant, decoder: .devRant, logger: requestLogger)
    }
    
    func makeConfig(_ method: Request.Method, path: String, urlParameters: [String: String] = [:], headers: [String: String] = [:], token: AuthToken? = nil) -> Request.Config {
        var urlParameters = urlParameters
        urlParameters["app"] = "3"
        if let token {
            urlParameters["token_id"] = String(token.id)
            urlParameters["token_key"] = token.key
            urlParameters["user_id"] = String(token.userId)
        }
        
        var headers = headers
        headers["Content-Type"] = "application/x-www-form-urlencoded"
        
        return .init(method: method, backend: backend, path: path, urlParameters: urlParameters, headers: headers)
    }
    
    public func logIn(username: String, password: String) async throws -> AuthToken {
        var parameters: [String: String] = [:]
        parameters["app"] = "3"
        parameters["username"] = username
        parameters["password"] = password
        
        let config = makeConfig(.post, path: "users/auth-token")
        
        // For the log in request the url encoded parameters are passed as a string in the http body instead of in the URL.
        let body = String(Request.urlEncodedQueryString(from: parameters).dropFirst()) // dropping the first character "?"
        
        let response: AuthToken.CodingData.Container = try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self)
        
        return response.auth_token.decoded
    }
    
    /// Gets a personalized feed of rants.
    ///
    /// - Parameters:
    ///    - token: The token from the `logIn` call response.
    ///    - limit: The number of rants to get for pagination.
    ///    - skip: How many rants to skip for pagination.
    ///    - sessionHash: Pass the session hash value from the last rant feed response or `nil` if calling for the first time.
    public func getRantFeed(token: AuthToken, sort: RantFeed.Sort = .algorithm, limit: Int = 20, skip: Int, sessionHash: String?) async throws -> RantFeed {
        var parameters: [String: String] = [:]
        
        parameters["sort"] = switch sort {
        case .algorithm: "algo"
        case .recent: "recent"
        case .top: "top"
        }
        
        switch sort {
        case .top(range: let range):
            parameters["range"] = switch range {
            case .day: "day"
            case .week: "week"
            case .month: "month"
            case .all: "all"
            }
        default:
            break
        }
        
        parameters["limit"] = String(limit)
        parameters["skip"] = String(skip)
        parameters["prev_set"] = sessionHash
        
        parameters["plat"] = "1" // I don't know wtf that is.
        parameters["nari"] = "1" // I don't know wtf that is.
        
        let config = makeConfig(.get, path: "devrant/rants", urlParameters: parameters)
        
        let response: RantFeed.CodingData = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)
        
        return response.decoded
    }
    
    /// Gets all weeklies as a list.
    ///
    /// - Parameters:
    ///    - token: The token from the `logIn` call response.
    public func getWeeklies(token: AuthToken) async throws -> [Weekly] {
        let config = makeConfig(.get, path: "devrant/weekly-list")
        
        let response: Weekly.CodingData.List = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)
        
        return response.weeks.map(\.decoded)
    }
    
    /// Gets a specific week's weekly rants.
    ///
    /// - Parameters:
    ///    - token: The token from the `logIn` call response.
    ///    - week: The number of the week. Pass `nil` to get the latest week's rants.
    ///    - limit: The number of rants for pagination.
    ///    - skip: How many rants to skip for pagination.
    public func getWeeklyRants(token: AuthToken, week: Int?, limit: Int = 20, skip: Int) async throws -> RantFeed {
        var parameters: [String: String] = [:]
        
        parameters["week"] = week.flatMap { String($0) }
        parameters["limit"] = String(limit)
        parameters["skip"] = String(skip)
        
        //parameters["sort"] = "algo" //TODO: This seems wrong. Check if this is needed or not.
        
        let config = makeConfig(.get, path: "devrant/weekly-rants", urlParameters: parameters)
        
        let response: RantFeed.CodingData = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)
        
        return response.decoded
    }
    
    /// Gets the list of notifications and numbers for each notification type.
    ///
    /// - Parameters:
    ///    - token: The token from the `logIn` call response.
    ///    - lastChecked: Pass the value from the last response or `nil`.
    public func getNotificationFeed(token: AuthToken, lastChecked: Date?, category: NotificationFeed.Category) async throws -> NotificationFeed {
        var parameters: [String: String] = [:]
        
        parameters["last_time"] = lastChecked.flatMap { String(Int($0.timeIntervalSince1970)) } ?? "0"
        parameters["ext_prof"] = "1" // I don't know wtf that is.
        
        let config = makeConfig(.get, path: "users/me/notif-feed\(category.rawValue)", urlParameters: parameters)
        
        let response: NotificationFeed.CodingData.Container = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)
        
        return response.data.decoded
    }
}