import CryptoKit
import Dependencies
import Foundation
import KeychainClient
import UIKit
import Utilities

public extension DependencyValues {
    var instanceIdClient: InstanceIDClient {
        get { self[InstanceIDClient.self] }
        set { self[InstanceIDClient.self] = newValue }
    }
}

public struct InstanceIDClient {
    public var instanceId: @Sendable () -> String
}

// MARK: - DependencyKey

private let log = Logger(category: "InstanceIDClient")

extension InstanceIDClient: DependencyKey {
    enum Constants {
        static let instanceIdKey = Bundle.main.infoDictionary?["INSTANCE_ID_KEY"] as? String ?? "ai.suno.ios.InstanceIDClient.instanceId"
    }

    public static let liveValue: Self = {
        @Dependency(\.keychainClient) var keychainClient

        let fallbackCache = LockIsolated<String?>(nil)

        let generateInstanceId = {
            // Generate a stable instance ID by hashing the device's identifierForVendor
            // SHA256 hashing provides privacy (can't reverse to original IDFV) and consistent 64-char hex output.
            // Example output: "d1e4b74f2f1b9fdcf42dae779bf2633d0e960a8579dec1aaef6840df1b8390ca"
            if let idfv = UIDevice.current.identifierForVendor?.uuidString {
                let hashed = SHA256.hash(data: Data(idfv.utf8))
                return hashed.compactMap { String(format: "%02x", $0) }.joined()
            } else {
                // Should never happen, but if it does, we'll use a random UUID.
                log.telemetry.assertionFailure("Failed to get identifierForVendor, using random UUID")
                return UUID().uuidString
            }
        }

        return Self(
            instanceId: {
                if let existingId = try? keychainClient.getString(Constants.instanceIdKey) {
                    return existingId
                }

                if let cachedId = fallbackCache.value {
                    try? keychainClient.setString(cachedId, Constants.instanceIdKey)
                    return cachedId
                }

                let newId = generateInstanceId()

                do {
                    try keychainClient.setString(newId, Constants.instanceIdKey)
                } catch {
                    fallbackCache.setValue(newId)
                }

                return newId
            }
        )
    }()
}

// MARK: - TestDependencyKey

extension InstanceIDClient: TestDependencyKey {
    public static let mockInstanceIdString = UUID().uuidString

    public static let testValue: Self = Self(
        instanceId: { Self.mockInstanceIdString }
    )
}
