import AppKit
import CoreWLAN
import Foundation
import Security

private let keychainService = "NODEDC Mission Core Host Wi-Fi Profiles"
private let credentialMaterialKeychainService =
    "NODEDC Mission Core Device Credential Materials"

private struct HostWifiRequest: Decodable {
    let action: String
    let profileID: String
    let ssid: String?
    let password: String?
    let scanTimeoutSeconds: Double?
    let credentialSourceID: String?

    enum CodingKeys: String, CodingKey {
        case action
        case profileID = "profile_id"
        case ssid
        case password
        case scanTimeoutSeconds = "scan_timeout_seconds"
        case credentialSourceID = "credential_source_id"
    }
}

private struct StoredProfile: Codable {
    let schemaVersion: Int
    let ssid: String
    let password: String
    let credentialSource: String?

    enum CodingKeys: String, CodingKey {
        case schemaVersion = "schema_version"
        case ssid
        case password
        case credentialSource = "credential_source"
    }
}

private struct StoredCredentialMaterial: Codable {
    let schemaVersion: Int
    let password: String

    enum CodingKeys: String, CodingKey {
        case schemaVersion = "schema_version"
        case password
    }
}

private struct HostWifiResponse: Encodable {
    let ok: Bool
    let adapter: String?
    let alreadyAssociated: Bool?
    let stored: Bool?
    let found: Bool?
    let profileAvailable: Bool?
    let profileEnrolled: Bool?
    let scanAttemptCount: Int?
    let scanElapsedMilliseconds: Int?
    let credentialSource: String?
    let reasonCode: String?

    enum CodingKeys: String, CodingKey {
        case ok
        case adapter
        case alreadyAssociated = "already_associated"
        case stored
        case found
        case profileAvailable = "profile_available"
        case profileEnrolled = "profile_enrolled"
        case scanAttemptCount = "scan_attempt_count"
        case scanElapsedMilliseconds = "scan_elapsed_ms"
        case credentialSource = "credential_source"
        case reasonCode = "reason_code"
    }
}

private func emit(
    ok: Bool,
    adapter: String? = nil,
    alreadyAssociated: Bool? = nil,
    stored: Bool? = nil,
    found: Bool? = nil,
    profileAvailable: Bool? = nil,
    profileEnrolled: Bool? = nil,
    scanAttemptCount: Int? = nil,
    scanElapsedMilliseconds: Int? = nil,
    credentialSource: String? = nil,
    reasonCode: String? = nil,
    exitCode: Int32
) -> Never {
    let response = HostWifiResponse(
        ok: ok,
        adapter: adapter,
        alreadyAssociated: alreadyAssociated,
        stored: stored,
        found: found,
        profileAvailable: profileAvailable,
        profileEnrolled: profileEnrolled,
        scanAttemptCount: scanAttemptCount,
        scanElapsedMilliseconds: scanElapsedMilliseconds,
        credentialSource: credentialSource,
        reasonCode: reasonCode
    )
    if let data = try? JSONEncoder().encode(response) {
        FileHandle.standardOutput.write(data)
    }
    exit(exitCode)
}

private func profileIsValid(_ profile: StoredProfile) -> Bool {
    guard profile.schemaVersion == 1,
          let ssidData = profile.ssid.data(using: .utf8),
          (1 ... 32).contains(ssidData.count),
          let passwordData = profile.password.data(using: .utf8),
          (1 ... 64).contains(passwordData.count)
    else {
        return false
    }
    return true
}

private func credentialMaterialIsValid(_ material: StoredCredentialMaterial) -> Bool {
    guard material.schemaVersion == 1,
          let passwordData = material.password.data(using: .utf8),
          (8 ... 63).contains(passwordData.count)
    else {
        return false
    }
    return true
}

private func keychainQuery(service: String, account: String) -> [String: Any] {
    return [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: service,
        kSecAttrAccount as String: account,
    ]
}

private func profileKeychainQuery(profileID: String) -> [String: Any] {
    return keychainQuery(service: keychainService, account: profileID)
}

private func materialKeychainQuery(sourceID: String) -> [String: Any] {
    return keychainQuery(service: credentialMaterialKeychainService, account: sourceID)
}

private func loadProfile(profileID: String) throws -> StoredProfile {
    var query = profileKeychainQuery(profileID: profileID)
    query[kSecReturnData as String] = true
    query[kSecMatchLimit as String] = kSecMatchLimitOne

    var item: CFTypeRef?
    let status = SecItemCopyMatching(query as CFDictionary, &item)
    guard status == errSecSuccess, let data = item as? Data else {
        throw NSError(domain: "HostWifiProfile", code: Int(status))
    }
    let profile = try JSONDecoder().decode(StoredProfile.self, from: data)
    guard profileIsValid(profile) else {
        throw NSError(domain: "HostWifiProfile", code: Int(errSecDecode))
    }
    return profile
}

private func storeProfile(profileID: String, profile: StoredProfile) throws {
    guard profileIsValid(profile) else {
        throw NSError(domain: "HostWifiProfile", code: Int(errSecParam))
    }
    let data = try JSONEncoder().encode(profile)
    let query = profileKeychainQuery(profileID: profileID)
    let attributes = [
        kSecValueData as String: data,
        kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
    ] as [String: Any]

    let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
    if updateStatus == errSecSuccess {
        return
    }
    guard updateStatus == errSecItemNotFound else {
        throw NSError(domain: "HostWifiProfile", code: Int(updateStatus))
    }
    var newItem = query
    attributes.forEach { key, value in newItem[key] = value }
    let addStatus = SecItemAdd(newItem as CFDictionary, nil)
    guard addStatus == errSecSuccess else {
        throw NSError(domain: "HostWifiProfile", code: Int(addStatus))
    }
}

private func loadCredentialMaterial(sourceID: String) throws -> StoredCredentialMaterial {
    var query = materialKeychainQuery(sourceID: sourceID)
    query[kSecReturnData as String] = true
    query[kSecMatchLimit as String] = kSecMatchLimitOne

    var item: CFTypeRef?
    let status = SecItemCopyMatching(query as CFDictionary, &item)
    guard status == errSecSuccess, let data = item as? Data else {
        throw NSError(domain: "HostWifiCredentialMaterial", code: Int(status))
    }
    let material = try JSONDecoder().decode(StoredCredentialMaterial.self, from: data)
    guard credentialMaterialIsValid(material) else {
        throw NSError(domain: "HostWifiCredentialMaterial", code: Int(errSecDecode))
    }
    return material
}

private func storeCredentialMaterial(
    sourceID: String,
    material: StoredCredentialMaterial
) throws {
    guard credentialMaterialIsValid(material) else {
        throw NSError(domain: "HostWifiCredentialMaterial", code: Int(errSecParam))
    }
    let data = try JSONEncoder().encode(material)
    let query = materialKeychainQuery(sourceID: sourceID)
    let attributes = [
        kSecValueData as String: data,
        kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
    ] as [String: Any]

    let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
    if updateStatus == errSecSuccess {
        return
    }
    guard updateStatus == errSecItemNotFound else {
        throw NSError(domain: "HostWifiCredentialMaterial", code: Int(updateStatus))
    }
    var newItem = query
    attributes.forEach { key, value in newItem[key] = value }
    let addStatus = SecItemAdd(newItem as CFDictionary, nil)
    guard addStatus == errSecSuccess else {
        throw NSError(domain: "HostWifiCredentialMaterial", code: Int(addStatus))
    }
}

private func loadSystemWiFiProfile(ssid: String, ssidData: Data) -> StoredProfile? {
    var password: NSString?
    let status = CWKeychainFindWiFiPassword(
        CWKeychainDomain.user,
        ssidData,
        &password
    )
    guard status == errSecSuccess, let password else {
        return nil
    }
    let profile = StoredProfile(
        schemaVersion: 1,
        ssid: ssid,
        password: password as String,
        credentialSource: "system-wifi-keychain"
    )
    return profileIsValid(profile) ? profile : nil
}

private struct TargetedScanResult {
    let network: CWNetwork?
    let attemptCount: Int
    let elapsedMilliseconds: Int
}

private func scanForExpectedNetwork(
    interface: CWInterface,
    ssid: String,
    ssidData: Data,
    timeoutSeconds: Double
) throws -> TargetedScanResult {
    let started = ProcessInfo.processInfo.systemUptime
    var attemptCount = 0

    while true {
        attemptCount += 1
        let networks = try interface.scanForNetworks(withSSID: ssidData)
        if let network = networks.first(where: { $0.ssid == ssid }) {
            return TargetedScanResult(
                network: network,
                attemptCount: attemptCount,
                elapsedMilliseconds: max(
                    0,
                    Int((ProcessInfo.processInfo.systemUptime - started) * 1_000)
                )
            )
        }

        let elapsed = ProcessInfo.processInfo.systemUptime - started
        let remaining = timeoutSeconds - elapsed
        if remaining <= 0 {
            return TargetedScanResult(
                network: nil,
                attemptCount: attemptCount,
                elapsedMilliseconds: max(0, Int(elapsed * 1_000))
            )
        }
        Thread.sleep(forTimeInterval: min(0.75, remaining))
    }
}

private func promptForDevicePassword(ssid: String) -> String? {
    let application = NSApplication.shared
    application.setActivationPolicy(.accessory)

    let passwordField = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: 360, height: 24))
    passwordField.placeholderString = "Пароль точки доступа K1"

    let alert = NSAlert()
    alert.alertStyle = .informational
    alert.messageText = "Первое подключение к \(ssid)"
    alert.informativeText = "macOS не нашла локальный профиль этой точки доступа. Если credential вам неизвестен, нажмите «Отмена» и выполните авторизованный импорт device-профиля LixelGO. Введённое значение будет сохранено только в Keychain этого Mac и не попадёт в браузер, API, журнал или evidence Mission Core."
    alert.accessoryView = passwordField
    alert.addButton(withTitle: "Подключиться")
    alert.addButton(withTitle: "Отмена")

    application.activate(ignoringOtherApps: true)
    guard alert.runModal() == .alertFirstButtonReturn else {
        return nil
    }
    return passwordField.stringValue
}

private let input = FileHandle.standardInput.readDataToEndOfFile()
guard input.count <= 2048 else {
    emit(ok: false, reasonCode: "request-too-large", exitCode: 1)
}

do {
    let request = try JSONDecoder().decode(HostWifiRequest.self, from: input)
    guard (1 ... 128).contains(request.profileID.count),
          request.profileID.allSatisfy({
              $0.isASCII && ($0.isLetter || $0.isNumber || ".-_".contains($0))
          })
    else {
        emit(ok: false, reasonCode: "profile-id-invalid", exitCode: 1)
    }

    if request.action == "store-profile" {
        guard let ssid = request.ssid, let password = request.password else {
            emit(ok: false, reasonCode: "credential-missing", exitCode: 1)
        }
        try storeProfile(
            profileID: request.profileID,
            profile: StoredProfile(
                schemaVersion: 1,
                ssid: ssid,
                password: password,
                credentialSource: nil
            )
        )
        emit(ok: true, adapter: "macOS Keychain", stored: true, exitCode: 0)
    }

    if request.action == "store-credential-material" {
        guard let password = request.password else {
            emit(ok: false, reasonCode: "credential-missing", exitCode: 1)
        }
        try storeCredentialMaterial(
            sourceID: request.profileID,
            material: StoredCredentialMaterial(schemaVersion: 1, password: password)
        )
        emit(ok: true, adapter: "macOS Keychain", stored: true, exitCode: 0)
    }

    if request.action == "check-credential-material" {
        do {
            _ = try loadCredentialMaterial(sourceID: request.profileID)
            emit(
                ok: true,
                adapter: "macOS Keychain",
                profileAvailable: true,
                credentialSource: "exact-firmware-profile",
                exitCode: 0
            )
        } catch {
            emit(
                ok: true,
                adapter: "macOS Keychain",
                profileAvailable: false,
                exitCode: 0
            )
        }
    }

    if request.action == "ensure-profile" {
        guard let ssid = request.ssid,
              let ssidData = ssid.data(using: .utf8),
              (1 ... 32).contains(ssidData.count),
              let sourceID = request.credentialSourceID,
              (1 ... 128).contains(sourceID.count),
              sourceID.allSatisfy({
                  $0.isASCII && ($0.isLetter || $0.isNumber || ".-_".contains($0))
              })
        else {
            emit(ok: false, reasonCode: "credential-source-invalid", exitCode: 1)
        }

        let material: StoredCredentialMaterial
        do {
            material = try loadCredentialMaterial(sourceID: sourceID)
        } catch {
            emit(
                ok: true,
                adapter: "macOS Keychain",
                profileAvailable: false,
                profileEnrolled: false,
                reasonCode: "credential-source-unavailable",
                exitCode: 0
            )
        }

        do {
            let existing = try loadProfile(profileID: request.profileID)
            guard existing.ssid == ssid else {
                emit(
                    ok: false,
                    adapter: "macOS Keychain",
                    profileAvailable: false,
                    profileEnrolled: false,
                    reasonCode: "profile-ssid-mismatch",
                    exitCode: 1
                )
            }
            if existing.password == material.password,
               existing.credentialSource != "exact-firmware-profile" {
                try storeProfile(
                    profileID: request.profileID,
                    profile: StoredProfile(
                        schemaVersion: 1,
                        ssid: ssid,
                        password: existing.password,
                        credentialSource: "exact-firmware-profile"
                    )
                )
            }
            emit(
                ok: true,
                adapter: "macOS Keychain",
                profileAvailable: true,
                profileEnrolled: false,
                credentialSource: existing.password == material.password
                    ? "exact-firmware-profile"
                    : (existing.credentialSource ?? "mission-core-keychain"),
                exitCode: 0
            )
        } catch {
            try storeProfile(
                profileID: request.profileID,
                profile: StoredProfile(
                    schemaVersion: 1,
                    ssid: ssid,
                    password: material.password,
                    credentialSource: "exact-firmware-profile"
                )
            )
            emit(
                ok: true,
                adapter: "macOS Keychain",
                profileAvailable: true,
                profileEnrolled: true,
                credentialSource: "exact-firmware-profile",
                exitCode: 0
            )
        }
    }

    if request.action == "check-profile" {
        let profile: StoredProfile
        do {
            profile = try loadProfile(profileID: request.profileID)
        } catch {
            emit(
                ok: true,
                adapter: "macOS Keychain",
                profileAvailable: false,
                exitCode: 0
            )
        }
        if let expectedSSID = request.ssid, profile.ssid != expectedSSID {
            emit(
                ok: false,
                adapter: "macOS Keychain",
                profileAvailable: false,
                reasonCode: "profile-ssid-mismatch",
                exitCode: 1
            )
        }
        emit(
            ok: true,
            adapter: "macOS Keychain",
            profileAvailable: true,
            exitCode: 0
        )
    }

    guard request.action == "associate" || request.action == "scan-profile" else {
        emit(ok: false, reasonCode: "action-unsupported", exitCode: 1)
    }
    guard let interface = CWWiFiClient.shared().interface() else {
        emit(ok: false, reasonCode: "wifi-interface-unavailable", exitCode: 1)
    }
    let scanTimeoutSeconds = request.scanTimeoutSeconds ?? 0
    guard scanTimeoutSeconds >= 0, scanTimeoutSeconds <= 60 else {
        emit(ok: false, reasonCode: "scan-timeout-invalid", exitCode: 1)
    }

    if request.action == "scan-profile" {
        let profile: StoredProfile
        do {
            profile = try loadProfile(profileID: request.profileID)
        } catch {
            emit(ok: false, reasonCode: "profile-unavailable", exitCode: 1)
        }
        let ssidData = profile.ssid.data(using: .utf8)!
        let scan = try scanForExpectedNetwork(
            interface: interface,
            ssid: profile.ssid,
            ssidData: ssidData,
            timeoutSeconds: scanTimeoutSeconds
        )
        emit(
            ok: true,
            adapter: "CoreWLAN",
            found: scan.network != nil,
            scanAttemptCount: scan.attemptCount,
            scanElapsedMilliseconds: scan.elapsedMilliseconds,
            exitCode: 0
        )
    }

    guard request.action == "associate" else {
        emit(ok: false, reasonCode: "action-unsupported", exitCode: 1)
    }
    guard let expectedSSID = request.ssid,
          let expectedSSIDData = expectedSSID.data(using: .utf8),
          (1 ... 32).contains(expectedSSIDData.count)
    else {
        emit(ok: false, reasonCode: "ssid-invalid", exitCode: 1)
    }

    let scan = try scanForExpectedNetwork(
        interface: interface,
        ssid: expectedSSID,
        ssidData: expectedSSIDData,
        timeoutSeconds: scanTimeoutSeconds
    )
    guard let network = scan.network else {
        emit(
            ok: false,
            scanAttemptCount: scan.attemptCount,
            scanElapsedMilliseconds: scan.elapsedMilliseconds,
            reasonCode: "network-not-found",
            exitCode: 1
        )
    }

    var profileEnrolled = false
    var profileNeedsStore = false
    let credentialSource: String
    let profile: StoredProfile
    do {
        profile = try loadProfile(profileID: request.profileID)
        credentialSource = profile.credentialSource ?? "mission-core-keychain"
    } catch {
        if let systemProfile = loadSystemWiFiProfile(
            ssid: expectedSSID,
            ssidData: expectedSSIDData
        ) {
            profile = systemProfile
            credentialSource = "system-wifi-keychain"
            profileNeedsStore = true
        } else {
            guard let password = promptForDevicePassword(ssid: expectedSSID) else {
                emit(
                    ok: false,
                    scanAttemptCount: scan.attemptCount,
                    scanElapsedMilliseconds: scan.elapsedMilliseconds,
                    reasonCode: "credential-entry-cancelled",
                    exitCode: 1
                )
            }
            guard let passwordData = password.data(using: .utf8),
                  (1 ... 64).contains(passwordData.count)
            else {
                emit(
                    ok: false,
                    scanAttemptCount: scan.attemptCount,
                    scanElapsedMilliseconds: scan.elapsedMilliseconds,
                    reasonCode: "credential-invalid",
                    exitCode: 1
                )
            }
            profile = StoredProfile(
                schemaVersion: 1,
                ssid: expectedSSID,
                password: password,
                credentialSource: "native-secure-prompt"
            )
            credentialSource = "native-secure-prompt"
            profileNeedsStore = true
        }
    }
    guard profile.ssid == expectedSSID else {
        emit(
            ok: false,
            scanAttemptCount: scan.attemptCount,
            scanElapsedMilliseconds: scan.elapsedMilliseconds,
            reasonCode: "profile-ssid-mismatch",
            exitCode: 1
        )
    }

    if interface.ssid() == profile.ssid {
        if profileNeedsStore {
            try storeProfile(profileID: request.profileID, profile: profile)
            profileEnrolled = true
        }
        emit(
            ok: true,
            adapter: "CoreWLAN",
            alreadyAssociated: true,
            profileEnrolled: profileEnrolled,
            scanAttemptCount: scan.attemptCount,
            scanElapsedMilliseconds: scan.elapsedMilliseconds,
            credentialSource: credentialSource,
            exitCode: 0
        )
    }
    try interface.associate(to: network, password: profile.password)
    if profileNeedsStore {
        try storeProfile(profileID: request.profileID, profile: profile)
        profileEnrolled = true
    }
    // CoreWLAN's synchronous association call throws on failure. Reading the
    // current SSID again would require Location authorization on recent macOS
    // versions and could turn a successful association into a false negative.
    emit(
        ok: true,
        adapter: "CoreWLAN",
        alreadyAssociated: false,
        profileEnrolled: profileEnrolled,
        scanAttemptCount: scan.attemptCount,
        scanElapsedMilliseconds: scan.elapsedMilliseconds,
        credentialSource: credentialSource,
        exitCode: 0
    )
} catch {
    emit(ok: false, reasonCode: "corewlan-error", exitCode: 1)
}
