import Foundation
import CallKit

@objcMembers public class UUIDStorage: NSObject, CXCallObserverDelegate {
    /// Primary storage: cid -> CallingxCall
    private var callsByCid: [String: CallingxCall] = [:]
    /// Reverse lookup: lowercased UUID string -> CallingxCall
    private var callsByUUID: [String: CallingxCall] = [:]
    private let queue = DispatchQueue(label: "com.stream.uuidstorage", attributes: [])

    /// Warm, long-lived observer of CallKit's call state — a cold observer can report empty even for a
    /// call that's been live for a while.
    private let callObserver = CXCallObserver()
    /// CallKit's live view, maintained from observer callbacks. NOTE: contains UUIDs of ALL
    /// system calls. Only ever intersect it with our own UUIDs — never treat it as "our calls".
    private var liveCallKitUUIDs: Set<UUID> = []

    public override init() {
        super.init()
        callObserver.setDelegate(self, queue: queue)
        queue.async { [weak self] in
            guard let self = self else { return }
            self.liveCallKitUUIDs = Set(
                self.callObserver.calls.filter { !$0.hasEnded }.map { $0.uuid }
            )
        }
    }

    // MARK: - CXCallObserverDelegate

    /// IMPORTANT: delivered on `queue`, NEVER call the `queue.sync` helpers below —
    /// re-entering the serial queue would deadlock.
    public func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
        if call.hasEnded {
            liveCallKitUUIDs.remove(call.uuid)
        } else {
            liveCallKitUUIDs.insert(call.uuid)
        }
    }

    // MARK: - CallKit liveness queries

    public func hasRegisteredCall() -> Bool {
        return queue.sync {
            guard !callsByCid.isEmpty else { return false }
            let ours = Set(callsByCid.values.map { $0.uuid })
            return !ours.isDisjoint(with: liveCallKitUUIDs)
        }
    }

    public func isCallTracked(forCid cid: String) -> Bool {
        return queue.sync {
            guard let call = callsByCid[cid] else { return false }
            return liveCallKitUUIDs.contains(call.uuid)
        }
    }

    // MARK: - CallingxCall-based API (new)

    /// Returns the existing call for the given cid, or creates a new one.
    public func getOrCreateCall(forCid cid: String, isOutgoing: Bool = false) -> CallingxCall {
        return queue.sync {
            if let existing = callsByCid[cid] {
                CallingxLog.uuid.debugPublic("getOrCreateCall: found existing \(existing)")
                return existing
            }

            let uuid = UUID()
            let call = CallingxCall(uuid: uuid, cid: cid, isOutgoing: isOutgoing)
            let uuidString = uuid.uuidString.lowercased()
            callsByCid[cid] = call
            callsByUUID[uuidString] = call
            CallingxLog.uuid.debugPublic("getOrCreateCall: created \(call)")
            return call
        }
    }

    /// Returns the call for the given cid, or nil if not found.
    public func getCall(forCid cid: String) -> CallingxCall? {
        return queue.sync {
            return callsByCid[cid]
        }
    }

    /// Returns the call for the given UUID, or nil if not found.
    public func getCallByUUID(_ uuid: UUID) -> CallingxCall? {
        return queue.sync {
            let uuidString = uuid.uuidString.lowercased()
            return callsByUUID[uuidString]
        }
    }

    public func allCids() -> [String] {
        return queue.sync {
            return Array(callsByCid.keys)
        }
    }
    
    // MARK: - Legacy API (preserved for backward compatibility)

    public func allUUIDs() -> [UUID] {
        return queue.sync {
            return callsByCid.values.map { $0.uuid }
        }
    }

    /// Returns the existing UUID for the given cid, or creates a new CallingxCall and returns its UUID.
    public func getOrCreateUUID(forCid cid: String) -> UUID {
        return queue.sync {
            if let existing = callsByCid[cid] {
                CallingxLog.uuid.debugPublic("getUUIDForCid: found existing UUID \(existing.uuid.uuidString.lowercased()) for cid \(cid)")
                return existing.uuid
            }

            let uuid = UUID()
            let call = CallingxCall(uuid: uuid, cid: cid, isOutgoing: false)
            let uuidString = uuid.uuidString.lowercased()
            callsByCid[cid] = call
            callsByUUID[uuidString] = call
            CallingxLog.uuid.debugPublic("getUUIDForCid: created new UUID \(uuidString) for cid \(cid)")
            return uuid
        }
    }

    public func getUUID(forCid cid: String) -> UUID? {
        return queue.sync {
            return callsByCid[cid]?.uuid
        }
    }

    public func getCid(forUUID uuid: UUID) -> String? {
        return queue.sync {
            let uuidString = uuid.uuidString.lowercased()
            let cid = callsByUUID[uuidString]?.cid
            CallingxLog.uuid.debugPublic("getCidForUUID: UUID \(uuidString) -> cid \(cid ?? "(not found)")")
            return cid
        }
    }

    public func removeCid(forUUID uuid: UUID) {
        queue.sync {
            let uuidString = uuid.uuidString.lowercased()
            if let call = callsByUUID[uuidString] {
                callsByCid.removeValue(forKey: call.cid)
                callsByUUID.removeValue(forKey: uuidString)
                CallingxLog.uuid.debugPublic("removeCidForUUID: removed cid \(call.cid) for UUID \(uuidString)")
            } else {
                CallingxLog.uuid.debugPublic("removeCidForUUID: no cid found for UUID \(uuidString)")
            }
        }
    }

    public func removeCid(_ cid: String) {
        queue.sync {
            if let call = callsByCid[cid] {
                let uuidString = call.uuid.uuidString.lowercased()
                callsByUUID.removeValue(forKey: uuidString)
                callsByCid.removeValue(forKey: cid)
                CallingxLog.uuid.debugPublic("removeCid: removed cid \(cid) with UUID \(uuidString)")
            } else {
                CallingxLog.uuid.debugPublic("removeCid: no UUID found for cid \(cid)")
            }
        }
    }

    public func removeAllObjects() {
        queue.sync {
            let count = callsByCid.count
            callsByCid.removeAll()
            callsByUUID.removeAll()
            CallingxLog.uuid.debugPublic("removeAllObjects: cleared \(count) entries")
        }
    }

    public func count() -> Int {
        return queue.sync {
            return callsByCid.count
        }
    }

    public func containsCid(_ cid: String) -> Bool {
        return queue.sync {
            return callsByCid[cid] != nil
        }
    }

    public func containsUUID(_ uuid: UUID) -> Bool {
        return queue.sync {
            return callsByUUID[uuid.uuidString.lowercased()] != nil
        }
    }

    public override var description: String {
        return queue.sync {
            let entries = callsByCid.map { "\($0.key): \($0.value)" }.joined(separator: ", ")
            return "UUIDStorage: [\(entries)]"
        }
    }
}
