import ComposableArchitecture
import Foundation

@DependencyClient
public struct OrpheusRealtimeClient {
    public var subscribe: (_ sessionId: String) -> AsyncThrowingStream<OrpheusRealtimeEvent, Error> = { _ in .never }
    public var cancel: () -> Void
}

extension OrpheusRealtimeClient: DependencyKey {
    public static var liveValue: OrpheusRealtimeClient {
        return Self.live()
    }

    /// Implementation w/ Ably
    public static func live() -> Self {
        let orpheusModelParser =  OrpheusModelParser()
        let ablyMessageParser = AblyMessageParser()

        func mapSseEventToAblyEvent(_ sseEvent: SseEvent) -> AblyEvent {
            switch sseEvent {
            case .connected:
                return .connected
            case .disconnected:
                return .disconnected
            case .message(let data, _):
                return ablyMessageParser.parseMessage(data)
            case .error(let error):
                return .error(error)
            }
        }

        func translateAblyEvent(_ ablyEvent: AblyEvent) -> OrpheusRealtimeEvent? {
            switch ablyEvent {
            case .connected:
                return .connected
            case .disconnected:
                return .disconnected
            case .message(let message):
                return parseMessage(message)
            case .error(let error):
                return .error(error)
            }
        }

        func parseMessage(_ message: AblyMessage) -> OrpheusRealtimeEvent? {
            guard let dataString = message.data else {
                print("⚠️ Ignoring message with null data")
                return nil
            }

            let messageName = AblyMessageName.fromValue(message.name)
            switch messageName {
            case .orpheusMessage:
                return orpheusModelParser.parseMessageString(dataString)
            case .orpheusToolCall:
                return orpheusModelParser.parseToolString(dataString)
            case .none:
                print("📡 Ignoring unknown message type: \(message.name ?? "nil")")
                return nil
            }
        }

        return Self(
            /// Subscribes to an Ably channel via SSE
            subscribe: { sessionId in
                let ABLY_SSE_URL = "https://realtime.ably.io/sse"
                let channel = "orpheus-chat:\(sessionId)"
                let config = SseConnectionConfig(
                    url: ABLY_SSE_URL,
                    queryParameters: [
                        "channels": channel,
                        "v": "1.2"
                    ]
                )

                return AsyncThrowingStream { continuation in
                    Task {
                        do {
                            @Dependency(SseClient.self) var sseClient
                            let sseStream = sseClient.subscribe(
                                config: config,
                                authTokenProvider: {
                                    // TODO: Jimmy - Make a services client
                                    let tokenResponse = try await OrpheusService().getAblyAuthToken()
                                    let expiryDate = Date(timeIntervalSince1970: TimeInterval(tokenResponse.expires / 1000))
                                    let formatter = DateFormatter()
                                    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
                                    formatter.locale = Locale(identifier: "en_US_POSIX")
                                    print("📡 Fetched Ably token expiring \(formatter.string(from: expiryDate))")

                                    return SseAuthToken(
                                        authorizationHeader: "Bearer \(tokenResponse.token)",
                                        expiresAtMs: tokenResponse.expires
                                    )
                                }
                            )

                            for try await sseEvent in sseStream {
                                // TODO: Jimmy - This should not be so fucking complicated
                                if let orpheusEvent = translateAblyEvent(mapSseEventToAblyEvent(sseEvent)) {
                                    continuation.yield(orpheusEvent)
                                }
                            }

                            continuation.finish()
                        } catch {
                            continuation.finish(throwing: error)
                        }
                    }
                }
            },
            cancel: { return }
        )
    }

    private enum AblyMessageName: String {
        case orpheusMessage = "orpheus-message"
        case orpheusToolCall = "orpheus-tool-call"

        static func fromValue(_ value: String?) -> AblyMessageName? {
            guard let value = value else { return nil }
            return AblyMessageName(rawValue: value)
        }
    }
}

/// Events received from the real-time messaging service.
public enum OrpheusRealtimeEvent: Equatable {
    case connected
    case disconnected
    case error(Error)
    case messageContent(messageId: String, content: String, userId: String?)
    case messageToolCall(messageId: String, toolCallId: String?, toolCallName: String?, toolCallArguments: String?)
    case messageFinished(messageId: String, reason: String?)
    case messageError(messageId: String, error: String)
    case toolCallResult(toolCallId: String, toolCallName: String, clipIds: [String])
    case toolCallContent(toolCallId: String, toolCallName: String, content: String)
    case toolCallFailure(toolCallId: String, toolCallName: String, errorMessage: String)
    
    // MARK: - Equatable
    
    public static func == (lhs: OrpheusRealtimeEvent, rhs: OrpheusRealtimeEvent) -> Bool {
        switch (lhs, rhs) {
        case (.connected, .connected), (.disconnected, .disconnected):
            return true
        case (.error(let lhsError), .error(let rhsError)):
            return lhsError.localizedDescription == rhsError.localizedDescription
        case (.messageContent(let lhsId, let lhsContent, let lhsUserId),
              .messageContent(let rhsId, let rhsContent, let rhsUserId)):
            return lhsId == rhsId && lhsContent == rhsContent && lhsUserId == rhsUserId
        case (.messageToolCall(let lhsMsgId, let lhsToolId, let lhsToolName, let lhsArgs),
              .messageToolCall(let rhsMsgId, let rhsToolId, let rhsToolName, let rhsArgs)):
            return lhsMsgId == rhsMsgId && lhsToolId == rhsToolId && lhsToolName == rhsToolName && lhsArgs == rhsArgs
        case (.messageFinished(let lhsId, let lhsReason), .messageFinished(let rhsId, let rhsReason)):
            return lhsId == rhsId && lhsReason == rhsReason
        case (.messageError(let lhsId, let lhsError), .messageError(let rhsId, let rhsError)):
            return lhsId == rhsId && lhsError == rhsError
        case (.toolCallResult(let lhsId, let lhsName, let lhsClipIds),
              .toolCallResult(let rhsId, let rhsName, let rhsClipIds)):
            return lhsId == rhsId && lhsName == rhsName && lhsClipIds == rhsClipIds
        case (.toolCallContent(let lhsId, let lhsName, let lhsContent),
              .toolCallContent(let rhsId, let rhsName, let rhsContent)):
            return lhsId == rhsId && lhsName == rhsName && lhsContent == rhsContent
        case (.toolCallFailure(let lhsId, let lhsName, let lhsError),
              .toolCallFailure(let rhsId, let rhsName, let rhsError)):
            return lhsId == rhsId && lhsName == rhsName && lhsError == rhsError
        default:
            return false
        }
    }
}

