import ClerkClient
import Dependencies
import Foundation
import HTTPTypes
import OpenAPIRuntime
import OpenAPIURLSession

struct SunoClientTransport: ClientTransport {
    var inner: any ClientTransport
    var invalidToken: @Sendable () -> Void

    @Dependency(ClerkClient.self) private var clerk

    func send(
        _ request: HTTPTypes.HTTPRequest, body: OpenAPIRuntime.HTTPBody?, baseURL: URL, operationID: String
    ) async throws -> (HTTPTypes.HTTPResponse, OpenAPIRuntime.HTTPBody?) {
        let (response, responseBody) = try await inner.send(request, body: body, baseURL: baseURL, operationID: operationID)

        do {
            return try await handle(response: response, responseBody: responseBody)
        } catch is InvalidTokenError {
            // We hit a 401 unauthorized: refresh the token and try again
            _ = try await clerk.jwt(skipCache: true)

            do {
                let (response, responseBody) = try await inner.send(request, body: body, baseURL: baseURL, operationID: operationID)
                return try await handle(response: response, responseBody: responseBody)
            } catch is InvalidTokenError {
                // We hit the same error again. This time we log the user out.
                invalidToken()
                throw InvalidTokenError()
            }
        }
    }

    private func handle(response: HTTPResponse, responseBody: HTTPBody?) async throws -> (HTTPResponse, HTTPBody?) {
        // Check for errors we want to catch and explicitly handle
        switch response.status.code {
        case 401: // Unauthorized
            throw InvalidTokenError()

        case 402:
            throw APIError.insufficientCredits()

        case 403:
            throw APIError.forbidden()

        case 422:
            throw APIError.invalidHCaptchaToken

        case 429:
            throw APIError.tooManyRunningJobs

        case 400 ... 499:
            throw APIError.clientError(response.status.code)

        case 500 ... 599:
            throw APIError.serverError(response.status.code)

        default:
            // Anything else we send through "as-is"
            return (response, responseBody)
        }
    }
}
