import APIClient
import BackendEnvironmentClient
import Dependencies
import ClerkClient
import ComposableArchitecture
import Foundation
import GenAPI

/// Implementation of OrpheusServiceProtocol using URLSession
/// Note: The HTTP POST only triggers backend processing; actual messages arrive via Ably subscription
public struct OrpheusService: OrpheusServiceProtocol {
    private let baseURL: URL
    @Dependency(ClerkClient.self) var clerkClient: ClerkClient
    @Dependency(\.apiClientV2) var apiClient

    public init() {
        @Dependency(BackendEnvironmentClient.self) var backendEnvironment
        let environmentConfiguration = backendEnvironment.configuration()
        let endpointHost = environmentConfiguration.orpheusEndpointHost
        guard let url = URL(string: "https://\(endpointHost)") else {
            fatalError("ORPHEUS_BASE_URL is not defined for this configuration")
        }
        self.baseURL = url
    }

    public func streamChat(_ request: OrpheusChatSpec) async throws -> GenAPI.StreamingResponse {
        // Get auth token
        let jwt = try await clerkClient.jwt(skipCache: false)

        // Get user ID from Clerk
        guard let user = try await clerkClient.user() else {
            throw NSError(domain: "OrpheusService", code: -1, userInfo: [NSLocalizedDescriptionKey: "User not authenticated"])
        }
        let userId = user.id

        // Build URL - Android endpoint is just "chat" relative to Orpheus base URL
        let url = baseURL.appendingPathComponent("chat")

        // Create request
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "POST"
        urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
        urlRequest.setValue("Bearer \(jwt)", forHTTPHeaderField: "Authorization")

        // Encode request body with userId field
        // OrpheusChatSpec doesn't include userId, so we need to manually construct the JSON
        var requestDict: [String: Any] = [
            "message": request.message,
            "message_id": request.messageId,
            "session_id": request.sessionId,
            "user_id": userId
        ]

        if let toolCallId = request.toolCallId {
            requestDict["tool_call_id"] = toolCallId
        }

        urlRequest.httpBody = try JSONSerialization.data(withJSONObject: requestDict)

        // Send request (we don't consume the streaming response - it's just a trigger)
        let (data, response) = try await URLSession.shared.data(for: urlRequest)

        guard let httpResponse = response as? HTTPURLResponse else {
            throw NSError(domain: "OrpheusService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid response"])
        }

        guard 200...299 ~= httpResponse.statusCode else {
            // Log response body for debugging
            let responseBody = String(data: data, encoding: .utf8) ?? "Unable to decode response"
            print("❌ OrpheusService HTTP \(httpResponse.statusCode) error. Response body: \(responseBody)")
            print("❌ Request body was: \(String(data: urlRequest.httpBody ?? Data(), encoding: .utf8) ?? "nil")")
            throw NSError(
                domain: "OrpheusService",
                code: httpResponse.statusCode,
                userInfo: [
                    NSLocalizedDescriptionKey: "HTTP \(httpResponse.statusCode)",
                    "responseBody": responseBody
                ]
            )
        }

        // Return a dummy StreamingResponse since we don't actually consume it
        // The real messages come through Ably
        return GenAPI.StreamingResponse(message: "")
    }

    public func getAblyAuthToken() async throws -> AblyTokenResponse {
        // Get auth token
        let jwt = try await clerkClient.jwt(skipCache: false)

        // Build URL - Android endpoint is "ably-auth" relative to Orpheus base URL
        let url = baseURL.appendingPathComponent("ably-auth")

        // Create request
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "GET"
        urlRequest.setValue("Bearer \(jwt)", forHTTPHeaderField: "Authorization")

        // Send request
        let (data, response) = try await URLSession.shared.data(for: urlRequest)

        guard let httpResponse = response as? HTTPURLResponse else {
            throw NSError(domain: "OrpheusService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid response"])
        }

        guard 200...299 ~= httpResponse.statusCode else {
            let responseBody = String(data: data, encoding: .utf8) ?? "Unable to decode response"
            print("❌ OrpheusService getAblyAuthToken HTTP \(httpResponse.statusCode) error. Response body: \(responseBody)")
            throw NSError(
                domain: "OrpheusService",
                code: httpResponse.statusCode,
                userInfo: [
                    NSLocalizedDescriptionKey: "HTTP \(httpResponse.statusCode)",
                    "responseBody": responseBody
                ]
            )
        }

        // Decode response
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        return try decoder.decode(AblyTokenResponse.self, from: data)
    }

    public func registerContextState(_ request: ContextStateRequest) async throws {
        // Get auth token
        let jwt = try await clerkClient.jwt(skipCache: false)

        // Build URL - Android endpoint is "context-state" relative to Orpheus base URL
        let url = baseURL.appendingPathComponent("context-state")

        // Create request
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "POST"
        urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
        urlRequest.setValue("Bearer \(jwt)", forHTTPHeaderField: "Authorization")

        // Encode request body
        let encoder = JSONEncoder()
        encoder.keyEncodingStrategy = .convertToSnakeCase
        urlRequest.httpBody = try encoder.encode(request)

        // Send request
        let (data, response) = try await URLSession.shared.data(for: urlRequest)

        guard let httpResponse = response as? HTTPURLResponse else {
            throw NSError(domain: "OrpheusService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid response"])
        }

        guard 200...299 ~= httpResponse.statusCode else {
            let responseBody = String(data: data, encoding: .utf8) ?? "Unable to decode response"
            print("❌ OrpheusService registerContextState HTTP \(httpResponse.statusCode) error. Response body: \(responseBody)")
            print("❌ Request body was: \(String(data: urlRequest.httpBody ?? Data(), encoding: .utf8) ?? "nil")")
            throw NSError(
                domain: "OrpheusService",
                code: httpResponse.statusCode,
                userInfo: [
                    NSLocalizedDescriptionKey: "HTTP \(httpResponse.statusCode)",
                    "responseBody": responseBody
                ]
            )
        }

        // No response body to decode (returns Unit in Android)
        print("✅ Successfully registered context state: type=\(request.type.rawValue)")
    }

    public func registerSession(_ request: RegisterSessionRequest) async throws {
        // Get auth token
        let jwt = try await clerkClient.jwt(skipCache: false)

        // Build URL - Android endpoint is "register-session" relative to Orpheus base URL
        let url = baseURL.appendingPathComponent("register-session")

        // Create request
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "POST"
        urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
        urlRequest.setValue("Bearer \(jwt)", forHTTPHeaderField: "Authorization")

        // Encode request body
        let encoder = JSONEncoder()
        encoder.keyEncodingStrategy = .convertToSnakeCase
        urlRequest.httpBody = try encoder.encode(request)

        // Send request
        let (data, response) = try await URLSession.shared.data(for: urlRequest)

        guard let httpResponse = response as? HTTPURLResponse else {
            throw NSError(domain: "OrpheusService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid response"])
        }

        guard 200...299 ~= httpResponse.statusCode else {
            let responseBody = String(data: data, encoding: .utf8) ?? "Unable to decode response"
            print("❌ OrpheusService registerSession HTTP \(httpResponse.statusCode) error. Response body: \(responseBody)")
            print("❌ Request body was: \(String(data: urlRequest.httpBody ?? Data(), encoding: .utf8) ?? "nil")")
            throw NSError(
                domain: "OrpheusService",
                code: httpResponse.statusCode,
                userInfo: [
                    NSLocalizedDescriptionKey: "HTTP \(httpResponse.statusCode)",
                    "responseBody": responseBody
                ]
            )
        }

        // No response body to decode (returns Unit in Android)
        print("✅ Successfully registered session: sessionId=\(request.sessionId), workspaceId=\(request.workspaceId)")
    }
}
