import Foundation
import UIKit

/// Manages the random per-device identifier used in handshake/device approval flows.
/// The identifier is stored encrypted in the app container, keyed by local device traits,
/// mirroring the desktop model more closely than sending `identifierForVendor` directly.
final class DeviceIdentityManager: @unchecked Sendable {

    static let shared = DeviceIdentityManager()

    private enum LegacyKeychainKey {
        static let randomDeviceId = "randomDeviceId"
    }

    private let crypto = CryptoManager.shared
    private let keychain = KeychainManager.shared
    private let fileManager = FileManager.default
    private let alphabet = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
    private let salt = "rosetta-device-salt"

    private lazy var deviceFileURL: URL = {
        let baseURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
            ?? fileManager.urls(for: .documentDirectory, in: .userDomainMask).first
            ?? URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
        let directory = baseURL
            .appendingPathComponent("Rosetta", isDirectory: true)
            .appendingPathComponent("System", isDirectory: true)
        try? fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
        return directory.appendingPathComponent("device_id", isDirectory: false)
    }()

    private init() {}

    func currentDeviceId() -> String {
        if let encrypted = try? String(contentsOf: deviceFileURL, encoding: .utf8),
           let decrypted = try? crypto.decryptWithPassword(encrypted, password: localSecret()),
           let deviceId = String(data: decrypted, encoding: .utf8),
           !deviceId.isEmpty {
            return deviceId
        }

        if let legacy = try? keychain.loadString(forKey: LegacyKeychainKey.randomDeviceId),
           !legacy.isEmpty {
            persist(deviceId: legacy)
            try? keychain.delete(forKey: LegacyKeychainKey.randomDeviceId)
            return legacy
        }

        let generated = generateRandomDeviceId(length: 128)
        persist(deviceId: generated)
        return generated
    }
}

private extension DeviceIdentityManager {
    func persist(deviceId: String) {
        guard let raw = deviceId.data(using: .utf8),
              let encrypted = try? crypto.encryptWithPassword(raw, password: localSecret()),
              let data = encrypted.data(using: .utf8) else {
            return
        }
        try? data.write(
            to: deviceFileURL,
            options: [.atomic, .completeFileProtectionUntilFirstUserAuthentication]
        )
    }

    func localSecret() -> String {
        let vendorId = UIDevice.current.identifierForVendor?.uuidString ?? "unknown"
        return vendorId + UIDevice.current.name + salt
    }

    func generateRandomDeviceId(length: Int) -> String {
        guard let randomBytes = try? CryptoPrimitives.randomBytes(count: max(length, 1)) else {
            return UUID().uuidString.replacingOccurrences(of: "-", with: "")
        }
        return String(randomBytes.prefix(length).map { alphabet[Int($0) % alphabet.count] })
    }
}
