import Foundation

struct APIClient {
    enum APIError: LocalizedError {
        case invalidURL
        case server(String)
        case unexpectedStatusCode(Int)

        var errorDescription: String? {
            switch self {
            case .invalidURL:
                return "The API URL is invalid."
            case .server(let message):
                return message
            case .unexpectedStatusCode(let statusCode):
                return "The server returned status code \(statusCode)."
            }
        }
    }

    private let decoder: JSONDecoder = {
        let decoder = JSONDecoder()
        return decoder
    }()

    func signUp(
        name: String,
        email: String,
        password: String,
        age: Int,
        city: String,
        bio: String,
        occupation: String,
        gender: String,
        interestedIn: String,
        prompt: String,
        interests: [String]
    ) async throws -> SessionResponse {
        try await request(
            path: "/api/auth/signup",
            method: "POST",
            body: [
                "name": name,
                "email": email,
                "password": password,
                "age": age,
                "city": city,
                "bio": bio,
                "occupation": occupation,
                "gender": gender,
                "interestedIn": interestedIn,
                "prompt": prompt,
                "interests": interests,
            ]
        )
    }

    func login(email: String, password: String) async throws -> SessionResponse {
        try await request(
            path: "/api/auth/login",
            method: "POST",
            body: [
                "email": email,
                "password": password,
            ]
        )
    }

    func fetchCurrentUser(token: String) async throws -> MeResponse {
        try await request(path: "/api/me", token: token)
    }

    func updateProfile(token: String, user: User) async throws -> MeResponse {
        try await request(
            path: "/api/me",
            method: "PUT",
            token: token,
            body: [
                "name": user.name,
                "age": user.age,
                "bio": user.bio,
                "city": user.city,
                "occupation": user.occupation,
                "gender": user.gender,
                "interestedIn": user.interestedIn,
                "prompt": user.prompt,
                "interests": user.interests,
                "photos": user.photos,
            ]
        )
    }

    func fetchDiscover(token: String) async throws -> DiscoverResponse {
        try await request(path: "/api/discover", token: token)
    }

    func sendSwipe(targetUserId: Int, direction: SwipeDirection, token: String) async throws -> SwipeResponse {
        try await request(
            path: "/api/swipes",
            method: "POST",
            token: token,
            body: [
                "targetUserId": targetUserId,
                "direction": direction.rawValue,
            ]
        )
    }

    func fetchMatches(token: String) async throws -> MatchesResponse {
        try await request(path: "/api/matches", token: token)
    }

    func fetchChats(token: String) async throws -> ChatsResponse {
        try await request(path: "/api/chats", token: token)
    }

    func fetchMessages(matchId: Int, token: String) async throws -> MessagesResponse {
        try await request(path: "/api/matches/\(matchId)/messages", token: token)
    }

    func sendMessage(matchId: Int, text: String, token: String) async throws -> MessageEnvelope {
        try await request(
            path: "/api/matches/\(matchId)/messages",
            method: "POST",
            token: token,
            body: ["text": text]
        )
    }

    private func request<T: Decodable>(
        path: String,
        method: String = "GET",
        token: String? = nil,
        body: [String: Any]? = nil
    ) async throws -> T {
        guard let baseURL = URL(string: AppConfig.baseURLString) else {
            throw APIError.invalidURL
        }

        let url = baseURL.appending(path: path)
        var request = URLRequest(url: url)
        request.httpMethod = method
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

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

        if let body {
            request.httpBody = try JSONSerialization.data(withJSONObject: body)
        }

        let (data, response) = try await URLSession.shared.data(for: request)
        guard let httpResponse = response as? HTTPURLResponse else {
            throw APIError.server("The server response was invalid.")
        }

        if (200..<300).contains(httpResponse.statusCode) {
            return try decoder.decode(T.self, from: data)
        }

        if
            let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
            let errorMessage = object["error"] as? String
        {
            throw APIError.server(errorMessage)
        }

        throw APIError.unexpectedStatusCode(httpResponse.statusCode)
    }
}
