import XCTest
@testable import Rosetta

@MainActor
final class CallDisplayNameTests: XCTestCase {

    private let ownKey = "02-own"
    private let peerKey = "0263e9134d3abeb880bb5fa679954800a80d0c286c5c54c9452a996b0c7608db3"

    override func setUp() {
        super.setUp()
        CallManager.shared.resetForSessionEnd()
        CallManager.shared.bindAccount(publicKey: ownKey)
    }

    override func tearDown() {
        CallManager.shared.resetForSessionEnd()
        super.tearDown()
    }

    // MARK: - CallUiState.displayName fallback chain

    func testDisplayNamePrefersTitle() {
        let state = CallUiState(
            peerPublicKey: peerKey,
            peerTitle: "Alice",
            peerUsername: "alice"
        )
        XCTAssertEqual(state.displayName, "Alice")
    }

    func testDisplayNameFallsBackToUsername() {
        let state = CallUiState(
            peerPublicKey: peerKey,
            peerTitle: "",
            peerUsername: "alice"
        )
        XCTAssertEqual(state.displayName, "@alice")
    }

    func testDisplayNameFallsBackToKeyPrefix() {
        let state = CallUiState(
            peerPublicKey: peerKey,
            peerTitle: "",
            peerUsername: ""
        )
        XCTAssertEqual(state.displayName, String(peerKey.prefix(12)))
    }

    func testDisplayNameReturnsUnknownWhenEmpty() {
        let state = CallUiState()
        XCTAssertEqual(state.displayName, "Unknown")
    }

    // MARK: - Outgoing call sets displayName before CallKit

    func testOutgoingCallSetsDisplayNameFromTitle() {
        let result = CallManager.shared.startOutgoingCall(
            toPublicKey: peerKey,
            title: "Alice",
            username: "alice"
        )
        XCTAssertEqual(result, .started)
        // After startOutgoingCall, uiState should have peerTitle set
        XCTAssertEqual(CallManager.shared.uiState.peerTitle, "Alice")
        XCTAssertEqual(CallManager.shared.uiState.displayName, "Alice")
    }

    func testOutgoingCallWithEmptyTitleUsesUsername() {
        let result = CallManager.shared.startOutgoingCall(
            toPublicKey: peerKey,
            title: "",
            username: "alice"
        )
        XCTAssertEqual(result, .started)
        XCTAssertEqual(CallManager.shared.uiState.displayName, "@alice")
    }

    func testOutgoingCallWithNoIdentityUsesKeyPrefix() {
        let result = CallManager.shared.startOutgoingCall(
            toPublicKey: peerKey,
            title: "",
            username: ""
        )
        XCTAssertEqual(result, .started)
        // Should NOT be the full key — must be truncated prefix
        let displayName = CallManager.shared.uiState.displayName
        XCTAssertEqual(displayName, String(peerKey.prefix(12)))
        XCTAssertNotEqual(displayName, peerKey, "Full public key must NEVER be used as display name")
    }

    // MARK: - Incoming call name hydration

    func testIncomingCallDisplayNameDefaultsToKeyPrefix() {
        // Incoming calls start with empty title (signal packet has no name field)
        let packet = PacketSignalPeer(
            src: peerKey,
            dst: ownKey,
            sharedPublic: "",
            signalType: .call,
            roomId: ""
        )
        CallManager.shared.testHandleSignalPacket(packet)

        XCTAssertEqual(CallManager.shared.uiState.phase, .incoming)
        // Without DialogRepository entry, name falls back to key prefix
        let displayName = CallManager.shared.uiState.displayName
        XCTAssertTrue(displayName.count <= 12 || displayName == "Unknown",
                      "Incoming call display name should be short prefix, not full key: \(displayName)")
    }

    // MARK: - Server parity: signal packet has NO name field

    func testIncomingCallFromServerHasNoNameInPacket() {
        // Server PacketSignalPeer (0x1A) sends only src/dst public keys — no title.
        // Verify that after signal processing, displayName is NOT the full public key.
        let packet = PacketSignalPeer(
            src: peerKey,
            dst: ownKey,
            sharedPublic: "",
            signalType: .call,
            roomId: ""
        )
        CallManager.shared.testHandleSignalPacket(packet)

        let displayName = CallManager.shared.uiState.displayName
        XCTAssertNotEqual(displayName, peerKey,
                          "Server sends no name — displayName must NOT be full 66-char public key")
        XCTAssertTrue(displayName.count < 20,
                      "Display name should be short (prefix or 'Unknown'), got: \(displayName)")
    }

    // MARK: - Outgoing call: displayName available BEFORE CallKit report

    func testDisplayNameSetBeforeCallKitReport() {
        // beginCallSession is called BEFORE startOutgoingCall(peerKey:displayName:)
        // so uiState.displayName must already be resolved when CallKit is invoked.
        let result = CallManager.shared.startOutgoingCall(
            toPublicKey: peerKey,
            title: "Bob",
            username: "bob"
        )
        XCTAssertEqual(result, .started)
        // After startOutgoingCall, peerTitle should be "Bob" (set by beginCallSession)
        XCTAssertEqual(CallManager.shared.uiState.peerTitle, "Bob")
        // CallKit was called with displayName = "Bob" (verified by the flow:
        // beginCallSession sets peerTitle → startOutgoingCall reads uiState.displayName)
    }

    // MARK: - Full key never leaks as display name

    func testFullKeyNeverUsedInAnyScenario() {
        // Scenario 1: outgoing with title
        _ = CallManager.shared.startOutgoingCall(
            toPublicKey: peerKey, title: "Alice", username: ""
        )
        XCTAssertNotEqual(CallManager.shared.uiState.displayName, peerKey)
        CallManager.shared.resetForSessionEnd()
        CallManager.shared.bindAccount(publicKey: ownKey)

        // Scenario 2: outgoing with username only
        _ = CallManager.shared.startOutgoingCall(
            toPublicKey: peerKey, title: "", username: "alice"
        )
        XCTAssertNotEqual(CallManager.shared.uiState.displayName, peerKey)
        CallManager.shared.resetForSessionEnd()
        CallManager.shared.bindAccount(publicKey: ownKey)

        // Scenario 3: outgoing with nothing
        _ = CallManager.shared.startOutgoingCall(
            toPublicKey: peerKey, title: "", username: ""
        )
        XCTAssertNotEqual(CallManager.shared.uiState.displayName, peerKey)
        CallManager.shared.resetForSessionEnd()
        CallManager.shared.bindAccount(publicKey: ownKey)

        // Scenario 4: incoming via signal
        let packet = PacketSignalPeer(
            src: peerKey, dst: ownKey, sharedPublic: "",
            signalType: .call, roomId: ""
        )
        CallManager.shared.testHandleSignalPacket(packet)
        XCTAssertNotEqual(CallManager.shared.uiState.displayName, peerKey)
    }

    // MARK: - updateCallerName (CallKit update after hydration)

    func testUpdateCallerNameIsAvailable() {
        // Verify the method exists and doesn't crash with empty state
        CallKitManager.shared.updateCallerName("")  // should be a no-op
        CallKitManager.shared.updateCallerName("Alice")  // no UUID → no-op, but shouldn't crash
    }
}
