import Foundation

struct APIEnvironment {
    let baseURL: URL

    init(baseURL: URL) {
        self.baseURL = baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")
    }
}

enum HermesAPIError: Error {
    case invalidURL(String)
    case invalidResponse
    case transport(Error)
    case unexpectedStatus(Int, Data)
    case decoding(Error)
}

struct HermesAPIClient {
    let environment: APIEnvironment
    let session: URLSession

    private let encoder: JSONEncoder
    private let decoder: JSONDecoder

    init(environment: APIEnvironment, session: URLSession = .shared) {
        self.environment = environment
        self.session = session

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

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

    func startSession(_ payload: HermesSessionStartRequest) async throws -> HermesSessionResponse {
        try await send(path: "api/v1/session/start", method: "POST", body: payload)
    }

    func health() async throws -> HermesHealthResponse {
        try await send(path: "health")
    }

    func endSession() async throws -> HermesSessionResponse {
        try await send(path: "api/v1/session/end", method: "POST")
    }

    func currentSession() async throws -> HermesSessionResponse {
        try await send(path: "api/v1/session/me")
    }

    func nextEvent() async throws -> HermesEvent {
        try await send(path: "api/v1/feed/next")
    }

    func eventManifest(eventID: UUID) async throws -> HermesEventManifest {
        try await send(path: "api/v1/events/\(eventID.uuidString)/manifest")
    }

    func markets(eventID: UUID) async throws -> [HermesMarket] {
        try await send(path: "api/v1/events/\(eventID.uuidString)/markets")
    }

    func currentOdds(marketID: UUID) async throws -> HermesOddsVersion {
        try await send(path: "api/v1/markets/\(marketID.uuidString)/odds/current")
    }

    func submitBetIntent(_ payload: HermesBetIntentRequest) async throws -> HermesBetIntentResponse {
        try await send(path: "api/v1/bets/intent", method: "POST", body: payload)
    }

    func betIntent(id: UUID) async throws -> HermesBetIntentResponse {
        try await send(path: "api/v1/bets/\(id.uuidString)")
    }

    func settlement(eventID: UUID) async throws -> HermesSettlement {
        try await send(path: "api/v1/events/\(eventID.uuidString)/result")
    }

    func experimentConfig() async throws -> HermesExperimentConfig {
        try await send(path: "api/v1/experiments/config")
    }

    func localization(localeCode: String) async throws -> HermesLocalizationBundle {
        try await send(path: "api/v1/localization/\(localeCode)")
    }

    func submitAnalyticsBatch(_ payload: HermesAnalyticsBatchRequest) async throws {
        let encodedBody = try encoder.encode(payload)
        _ = try await perform(path: "api/v1/analytics/batch", method: "POST", body: encodedBody)
    }

    private func send<Response: Decodable>(path: String, method: String = "GET") async throws -> Response {
        let (data, _) = try await perform(path: path, method: method)
        do {
            return try decoder.decode(Response.self, from: data)
        } catch {
            throw HermesAPIError.decoding(error)
        }
    }

    private func send<Response: Decodable, Body: Encodable>(path: String, method: String, body: Body) async throws -> Response {
        let encodedBody = try encoder.encode(body)
        let (data, _) = try await perform(path: path, method: method, body: encodedBody)

        do {
            return try decoder.decode(Response.self, from: data)
        } catch {
            throw HermesAPIError.decoding(error)
        }
    }

    private func perform(path: String, method: String, body: Data? = nil) async throws -> (Data, HTTPURLResponse) {
        let request = try makeRequest(path: path, method: method, body: body)

        do {
            let (data, response) = try await session.data(for: request)
            guard let httpResponse = response as? HTTPURLResponse else {
                throw HermesAPIError.invalidResponse
            }

            guard (200...299).contains(httpResponse.statusCode) else {
                throw HermesAPIError.unexpectedStatus(httpResponse.statusCode, data)
            }

            return (data, httpResponse)
        } catch let error as HermesAPIError {
            throw error
        } catch {
            throw HermesAPIError.transport(error)
        }
    }

    private func makeRequest(path: String, method: String, body: Data? = nil) throws -> URLRequest {
        let normalizedPath = path.hasPrefix("/") ? String(path.dropFirst()) : path
        guard let url = URL(string: normalizedPath, relativeTo: environment.baseURL)?.absoluteURL else {
            throw HermesAPIError.invalidURL(path)
        }

        var request = URLRequest(url: url)
        request.httpMethod = method
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        if let body {
            request.httpBody = body
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        }

        return request
    }
}
