import CryptoKit
import Foundation

private struct StoredPushRelayRegistrationState: Codable {
    var relayHandle: String
    var sendGrant: String
    var relayOrigin: String?
    var gatewayDeviceId: String
    var relayHandleExpiresAtMs: Int64?
    var tokenDebugSuffix: String?
    var lastAPNsTokenHashHex: String
    var installationId: String
    var lastTransport: String
    var apnsEnvironment: String?
    var relayProfile: String?
    var proofPolicy: String?
}

enum PushRelayRegistrationStore {
    private static let service = "ai.openclawfoundation.app.pushrelay"
    private static let registrationStateAccount = "registration-state"
    private static let appAttestKeyIDAccount = "app-attest-key-id"
    private static let appAttestedKeyIDAccount = "app-attested-key-id"

    struct AppAttestScope {
        var relayOrigin: String
        var apnsEnvironment: String
        var relayProfile: String
        var proofPolicy: String
    }

    struct RegistrationState: Codable {
        var relayHandle: String
        var sendGrant: String
        var relayOrigin: String?
        var gatewayDeviceId: String
        var relayHandleExpiresAtMs: Int64?
        var tokenDebugSuffix: String?
        var lastAPNsTokenHashHex: String
        var installationId: String
        var lastTransport: String
        var apnsEnvironment: String
        var relayProfile: String
        var proofPolicy: String
    }

    static func loadRegistrationState() -> RegistrationState? {
        guard let raw = KeychainStore.loadString(
            service: self.service,
            account: self.registrationStateAccount),
            let data = raw.data(using: .utf8),
            let decoded = try? JSONDecoder().decode(StoredPushRelayRegistrationState.self, from: data)
        else {
            return nil
        }
        return RegistrationState(
            relayHandle: decoded.relayHandle,
            sendGrant: decoded.sendGrant,
            relayOrigin: decoded.relayOrigin,
            gatewayDeviceId: decoded.gatewayDeviceId,
            relayHandleExpiresAtMs: decoded.relayHandleExpiresAtMs,
            tokenDebugSuffix: decoded.tokenDebugSuffix,
            lastAPNsTokenHashHex: decoded.lastAPNsTokenHashHex,
            installationId: decoded.installationId,
            lastTransport: decoded.lastTransport,
            apnsEnvironment: decoded.apnsEnvironment ?? "production",
            relayProfile: decoded.relayProfile ?? "production",
            proofPolicy: decoded.proofPolicy ?? "appleStrict")
    }

    @discardableResult
    static func saveRegistrationState(_ state: RegistrationState) -> Bool {
        let stored = StoredPushRelayRegistrationState(
            relayHandle: state.relayHandle,
            sendGrant: state.sendGrant,
            relayOrigin: state.relayOrigin,
            gatewayDeviceId: state.gatewayDeviceId,
            relayHandleExpiresAtMs: state.relayHandleExpiresAtMs,
            tokenDebugSuffix: state.tokenDebugSuffix,
            lastAPNsTokenHashHex: state.lastAPNsTokenHashHex,
            installationId: state.installationId,
            lastTransport: state.lastTransport,
            apnsEnvironment: state.apnsEnvironment,
            relayProfile: state.relayProfile,
            proofPolicy: state.proofPolicy)
        guard let data = try? JSONEncoder().encode(stored),
              let raw = String(data: data, encoding: .utf8)
        else {
            return false
        }
        return KeychainStore.saveString(raw, service: self.service, account: self.registrationStateAccount)
    }

    static func loadAppAttestKeyID(scope: AppAttestScope) -> String? {
        let value = KeychainStore.loadString(
            service: self.service,
            account: self.scopedAccount(self.appAttestKeyIDAccount, scope: scope))?
            .trimmingCharacters(in: .whitespacesAndNewlines)
        if value?.isEmpty == false { return value }
        return nil
    }

    @discardableResult
    static func saveAppAttestKeyID(_ keyID: String, scope: AppAttestScope) -> Bool {
        KeychainStore.saveString(
            keyID,
            service: self.service,
            account: self.scopedAccount(self.appAttestKeyIDAccount, scope: scope))
    }

    @discardableResult
    static func clearAppAttestKeyID(scope: AppAttestScope) -> Bool {
        KeychainStore.delete(
            service: self.service,
            account: self.scopedAccount(self.appAttestKeyIDAccount, scope: scope))
    }

    static func loadAttestedKeyID(scope: AppAttestScope) -> String? {
        let value = KeychainStore.loadString(
            service: self.service,
            account: self.scopedAccount(self.appAttestedKeyIDAccount, scope: scope))?
            .trimmingCharacters(in: .whitespacesAndNewlines)
        if value?.isEmpty == false { return value }
        return nil
    }

    @discardableResult
    static func saveAttestedKeyID(_ keyID: String, scope: AppAttestScope) -> Bool {
        KeychainStore.saveString(
            keyID,
            service: self.service,
            account: self.scopedAccount(self.appAttestedKeyIDAccount, scope: scope))
    }

    @discardableResult
    static func clearAttestedKeyID(scope: AppAttestScope) -> Bool {
        KeychainStore.delete(
            service: self.service,
            account: self.scopedAccount(self.appAttestedKeyIDAccount, scope: scope))
    }

    private static func scopedAccount(_ baseAccount: String, scope: AppAttestScope) -> String {
        let raw = [
            scope.relayOrigin,
            scope.apnsEnvironment,
            scope.relayProfile,
            scope.proofPolicy,
        ].joined(separator: "\n")
        let digest = SHA256.hash(data: Data(raw.utf8))
            .map { String(format: "%02x", $0) }
            .joined()
        // A relay sees an App Attest key as attested only after receiving that
        // key's attestation object, so keep key state isolated per relay context.
        return "\(baseAccount)-\(digest)"
    }
}
