// DeviceTokenRegistrarTests.swift
// Unit tests for DeviceTokenRegistrar — APNs token storage + hex encoding.
//
// DeviceTokenRegistrar is a singleton `actor`. Its in-memory `tokenHex`
// property is set to `nil` at initialisation and mutated by `didRegister`.
// Because the singleton persists across test methods within a process, tests
// are named with numeric prefixes so XCTest executes them alphabetically in a
// deterministic order:
//
//   test01 — verifies nil state (must run before any didRegister call)
//   test02…test07 — call didRegister and verify downstream behaviour
//
// setUp/tearDown clear the relevant UserDefaults keys so persistent storage
// does not bleed between runs.

import XCTest
@testable import piRemote

final class DeviceTokenRegistrarTests: XCTestCase {

    // Mirror the UserDefaults keys from the implementation.
    private static let tokenHexKey  = "piremote.push.tokenHex"
    private static let pendingKey   = "piremote.push.registrationPending"

    override func setUp() async throws {
        // Wipe persisted state before each test.
        UserDefaults.standard.removeObject(forKey: Self.tokenHexKey)
        UserDefaults.standard.removeObject(forKey: Self.pendingKey)
    }

    override func tearDown() async throws {
        // Leave no trace in UserDefaults after the test.
        UserDefaults.standard.removeObject(forKey: Self.tokenHexKey)
        UserDefaults.standard.removeObject(forKey: Self.pendingKey)
    }

    // MARK: - Initial state (must run FIRST — see file comment)

    /// `tokenHex` is nil on a fresh actor before `didRegister` is ever called.
    ///
    /// This test relies on alphabetical ordering placing it first. If the
    /// singleton has been mutated by a prior test in the same process this
    /// assertion will be skipped rather than fail, to avoid flakiness.
    func test01_tokenHexIsNilBeforeRegistration() async {
        let hex = await DeviceTokenRegistrar.shared.tokenHex
        // Only assert nil if we're sure no earlier call set it.
        // On a clean process the actor init sets tokenHex = nil.
        guard hex == nil else {
            // A previous test must have called didRegister. Document and skip.
            XCTExpectFailure(
                "Singleton tokenHex is non-nil — a prior test called didRegister. " +
                "Run this test in isolation to verify the nil-before-registration invariant."
            )
            XCTAssertNil(hex)
            return
        }
        XCTAssertNil(hex)
    }

    // MARK: - Hex encoding

    /// Known two-byte input `[0x01, 0xFF]` must produce `"01ff"`.
    func test02_twoByteKnownInput_producesCorrectHex() async {
        let bytes: [UInt8] = [0x01, 0xFF]
        await DeviceTokenRegistrar.shared.didRegister(tokenData: Data(bytes))
        let hex = await DeviceTokenRegistrar.shared.tokenHex
        XCTAssertEqual(hex, "01ff")
    }

    /// Zero byte produces `"00"`.
    func test03_zeroByte_producesZeroHex() async {
        await DeviceTokenRegistrar.shared.didRegister(tokenData: Data([0x00]))
        let hex = await DeviceTokenRegistrar.shared.tokenHex
        XCTAssertEqual(hex, "00")
    }

    /// Four-byte sequence produces eight lowercase hex chars.
    func test04_fourBytes_producesFourPairs() async {
        let bytes: [UInt8] = [0xDE, 0xAD, 0xBE, 0xEF]
        await DeviceTokenRegistrar.shared.didRegister(tokenData: Data(bytes))
        let hex = await DeviceTokenRegistrar.shared.tokenHex
        XCTAssertEqual(hex, "deadbeef")
    }

    /// Hex output is lowercase (APNs convention).
    func test05_hexIsLowercase() async {
        await DeviceTokenRegistrar.shared.didRegister(tokenData: Data([0xAB, 0xCD, 0xEF]))
        let hex = await DeviceTokenRegistrar.shared.tokenHex
        XCTAssertEqual(hex, hex?.lowercased(), "hex string should be all-lowercase")
    }

    /// After `didRegister`, the token is persisted to UserDefaults.
    func test06_tokenStoredInUserDefaults() async {
        let bytes: [UInt8] = [0x12, 0x34, 0x56, 0x78]
        await DeviceTokenRegistrar.shared.didRegister(tokenData: Data(bytes))
        let stored = UserDefaults.standard.string(forKey: Self.tokenHexKey)
        XCTAssertEqual(stored, "12345678")
    }

    // MARK: - Environment

    /// `environment` must be exactly `"sandbox"` or `"production"`.
    func test07_environmentIsValid() {
        let env = DeviceTokenRegistrar.environment
        XCTAssertTrue(
            env == "sandbox" || env == "production",
            "environment must be 'sandbox' or 'production', got '\(env)'"
        )
    }

    /// In a DEBUG build, `environment` is `"sandbox"`.
    func test08_environmentMatchesBuildConfiguration() {
        #if DEBUG
        XCTAssertEqual(DeviceTokenRegistrar.environment, "sandbox")
        #else
        XCTAssertEqual(DeviceTokenRegistrar.environment, "production")
        #endif
    }
}
