import Foundation
import Security

// Watch-local credential persistence. The watch's WCSession receiver
// writes here on every successful handoff from the phone so the
// credentials survive app force-quits / cold launches without
// requiring the phone to be reachable. Plan §5: tokens live in the
// watch's Keychain (NOT iCloud Keychain — single-device handoff).
//
// One slot — the watch only ever knows ONE account at a time (the
// phone's currently-active one). Account switches on the phone
// overwrite the slot via a fresh `updateApplicationContext` push.
//
// Accessibility: `kSecAttrAccessibleAfterFirstUnlock` so background
// refresh tasks (Phase C — complication TimelineProvider, etc.) can
// read the token without requiring a foregrounded app. Slightly more
// permissive than the phone's WHEN_PASSCODE_SET_THIS_DEVICE_ONLY, but
// the watch has no "passcode-cleared" failure mode the phone defends
// against, and the background-refresh constraint is hard.

enum WatchKeychain {
    private static let service = "ht.halflig.app.watch.creds"
    private static let account = "current"

    struct Persisted: Codable, Equatable {
        let instance: String
        let accessToken: String
        let accountId: String
        let username: String
        let accountType: String
        let issuedAt: Date
        let supportsTapestry: Bool
        let avatarUrl: String?
        let accentVariant: Int

        init(
            instance: String,
            accessToken: String,
            accountId: String,
            username: String,
            accountType: String,
            issuedAt: Date,
            supportsTapestry: Bool,
            avatarUrl: String?,
            accentVariant: Int
        ) {
            self.instance = instance
            self.accessToken = accessToken
            self.accountId = accountId
            self.username = username
            self.accountType = accountType
            self.issuedAt = issuedAt
            self.supportsTapestry = supportsTapestry
            self.avatarUrl = avatarUrl
            self.accentVariant = accentVariant
        }

        // Manual decoder so blobs persisted by older builds (missing
        // `avatarUrl` / `accentVariant` keys) decode with sensible
        // defaults rather than throwing. The alternative is one launch
        // of EmptyAuthView until the phone re-pushes, and §3's
        // persisted-signature dedupe means a cold launch may not
        // re-push if the (accountId, token-prefix, instance) tuple
        // hasn't changed since the prior build was running.
        init(from decoder: Decoder) throws {
            let c = try decoder.container(keyedBy: CodingKeys.self)
            instance = try c.decode(String.self, forKey: .instance)
            accessToken = try c.decode(String.self, forKey: .accessToken)
            accountId = try c.decode(String.self, forKey: .accountId)
            username = try c.decode(String.self, forKey: .username)
            accountType = try c.decode(String.self, forKey: .accountType)
            issuedAt = try c.decode(Date.self, forKey: .issuedAt)
            supportsTapestry = try c.decode(Bool.self, forKey: .supportsTapestry)
            avatarUrl = try c.decodeIfPresent(String.self, forKey: .avatarUrl)
            accentVariant =
                try c.decodeIfPresent(Int.self, forKey: .accentVariant) ?? 1
        }
    }

    static func read() -> Persisted? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne,
        ]
        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        guard status == errSecSuccess, let data = item as? Data else { return nil }
        return try? JSONDecoder().decode(Persisted.self, from: data)
    }

    static func save(_ p: Persisted) {
        guard let data = try? JSONEncoder().encode(p) else { return }
        let baseQuery: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
        ]
        let updateAttrs: [String: Any] = [
            kSecValueData as String: data
        ]
        let status = SecItemUpdate(baseQuery as CFDictionary, updateAttrs as CFDictionary)
        if status == errSecItemNotFound {
            var addQuery = baseQuery
            addQuery[kSecValueData as String] = data
            addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
            SecItemAdd(addQuery as CFDictionary, nil)
        }
    }

    static func clear() {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
        ]
        SecItemDelete(query as CFDictionary)
    }
}
