import CryptoKit
import Dependencies
@testable import InstanceIDClient
import KeychainClient
import Testing
import UIKit

@Suite("InstanceIDClientTests")
struct InstanceIDClientTests {
    let instanceIdKeychainKey = InstanceIDClient.Constants.instanceIdKey

    @Test("Instance ID is generated and stored when keychain is empty")
    func testGenerateAndStoreInstanceId() async throws {
        withDependencies {
            $0.keychainClient = .isolatedTestValue()
            $0.instanceIdClient = .liveValue
        } operation: {
            @Dependency(\.instanceIdClient) var instanceIdClient
            @Dependency(\.keychainClient) var keychainClient

            let instanceId = instanceIdClient.instanceId()

            #expect(!instanceId.isEmpty)

            let storedId = try? keychainClient.getString(instanceIdKeychainKey)
            #expect(storedId == instanceId)
        }
    }

    @Test("Instance ID is retrieved from keychain when already exists")
    func testRetrieveExistingInstanceId() async throws {
        let existingId = "existing-test-instance-id"

        withDependencies {
            $0.keychainClient = .isolatedTestValue()
            $0.instanceIdClient = .liveValue
        } operation: {
            @Dependency(\.keychainClient) var keychainClient
            try? keychainClient.setString(existingId, instanceIdKeychainKey)

            @Dependency(\.instanceIdClient) var instanceIdClient
            let instanceId = instanceIdClient.instanceId()

            #expect(instanceId == existingId)
        }
    }

    @Test("Instance ID consistency - multiple calls return same value")
    func testInstanceIdConsistency() async throws {
        withDependencies {
            $0.keychainClient = .isolatedTestValue()
        } operation: {
            @Dependency(\.instanceIdClient) var instanceIdClient

            let firstCall = instanceIdClient.instanceId()
            let secondCall = instanceIdClient.instanceId()
            let thirdCall = instanceIdClient.instanceId()

            // All calls should return the same instance ID
            #expect(firstCall == secondCall)
            #expect(secondCall == thirdCall)
            #expect(!firstCall.isEmpty)
        }
    }

    @Test("Instance ID uses fallback cache when keychain fails")
    func testFallbackCacheWhenKeychainFails() async throws {
        withDependencies {
            $0.keychainClient = .failingValue
            $0.instanceIdClient = .liveValue
        } operation: {
            @Dependency(\.instanceIdClient) var instanceIdClient

            let firstCall = instanceIdClient.instanceId()
            let secondCall = instanceIdClient.instanceId()

            // Should generate and cache instance ID even when keychain fails
            #expect(!firstCall.isEmpty)
            #expect(firstCall == secondCall)
        }
    }

    @Test("Different keychain states produce different behaviors")
    func testDifferentKeychainStates() async throws {
        // Test 1: Empty keychain should generate new ID
        withDependencies {
            $0.keychainClient = .isolatedTestValue()
            $0.instanceIdClient = .liveValue
        } operation: {
            @Dependency(\.instanceIdClient) var instanceIdClient

            let newId = instanceIdClient.instanceId()
            #expect(!newId.isEmpty)
        }

        // Test 2: Keychain with existing value should return that value
        let presetId = "preset-test-id-12345"

        withDependencies {
            $0.keychainClient = .isolatedTestValue()
            $0.instanceIdClient = .liveValue
        } operation: {
            @Dependency(\.keychainClient) var keychainClient
            try? keychainClient.setString(presetId, instanceIdKeychainKey)

            @Dependency(\.instanceIdClient) var instanceIdClient
            let retrievedId = instanceIdClient.instanceId()
            #expect(retrievedId == presetId)
        }
    }

    @Test("Keychain key constant is correct")
    func testKeychainKeyConstant() async throws {
        withDependencies {
            $0.keychainClient = .isolatedTestValue()
            $0.instanceIdClient = .liveValue
        } operation: {
            @Dependency(\.instanceIdClient) var instanceIdClient
            @Dependency(\.keychainClient) var keychainClient

            let instanceId = instanceIdClient.instanceId()

            // Verify the ID is stored with the correct key
            let storedValue = try? keychainClient.getString(instanceIdKeychainKey)
            #expect(storedValue == instanceId)
        }
    }
}
