//
//  KBAuthManager.m
//
//  关键点：
//  - 使用固定的 service/account 将 KBAuthSession 序列化后保存到钥匙串；
//  - 通过 kSecAttrAccessGroup 指定 Keychain Sharing 访问组，实现 App 与扩展共享；
//  - 保存/清除时发送 Darwin 跨进程通知，便于对端刷新缓存；
//

#import "KBAuthManager.h"
#import <Security/Security.h>
#import "KBConfig.h"  // 需要共享钥匙串访问组常量，见 KBConfig.h 中的说明

NSString * const kKBDarwinAuthChanged = @"com.loveKey.nyx.auth.changed";
NSNotificationName const KBAuthChangedNotification = @"KBAuthChangedNotification";

static NSString * const kKBKCService = @"com.loveKey.nyx.auth";  // 钥匙串 service 名
static NSString * const kKBKCAccount = @"session";              // 钥匙串 account 键

// 用于 Keychain Sharing 的访问组；必须与两个 target 的 entitlements 配置一致。
// 示例（Capabilities 中勾选 Keychain Sharing 后的值）：
//   $(AppIdentifierPrefix)com.loveKey.nyx.shared
// 运行时会被展开为：TN6HHV45BB.com.loveKey.nyx.shared
#ifndef KB_KEYCHAIN_ACCESS_GROUP
#define KB_KEYCHAIN_ACCESS_GROUP @"TN6HHV45BB.com.loveKey.nyx.shared"
#endif

// 过期宽限：若过期时间距离当前 <= 该阈值，则视为已过期。
static const NSTimeInterval kKBExpiryGrace = 5.0; // 秒

@implementation KBAuthSession

+ (BOOL)supportsSecureCoding { return YES; }

- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.accessToken forKey:@"accessToken"];
    [coder encodeObject:self.refreshToken forKey:@"refreshToken"];
    [coder encodeObject:self.expiryDate   forKey:@"expiryDate"];
    [coder encodeObject:self.userIdentifier forKey:@"userIdentifier"];
}

- (instancetype)initWithCoder:(NSCoder *)coder {
    if (self = [super init]) {
        _accessToken = [coder decodeObjectOfClass:NSString.class forKey:@"accessToken"];
        _refreshToken = [coder decodeObjectOfClass:NSString.class forKey:@"refreshToken"];
        _expiryDate   = [coder decodeObjectOfClass:NSDate.class forKey:@"expiryDate"];
        _userIdentifier = [coder decodeObjectOfClass:NSString.class forKey:@"userIdentifier"];
    }
    return self;
}

@end

@interface KBAuthManager ()
@property (atomic, strong, readwrite, nullable) KBAuthSession *current;
@end

@implementation KBAuthManager

+ (instancetype)shared {
    static KBAuthManager *m; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ m = [KBAuthManager new]; });
    return m;
}

#if DEBUG
static inline void KBLog(NSString *fmt, ...) {
    va_list args; va_start(args, fmt);
    NSString *msg = [[NSString alloc] initWithFormat:fmt arguments:args];
    va_end(args);
    NSLog(@"[KBAuth] %@", msg);
}
#endif

- (instancetype)init {
    if (self = [super init]) {
        [self reloadFromKeychain];
        // 监听 Darwin 跨进程通知（App 与扩展之间）
        CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
                                        (__bridge const void *)(self),
                                        KBAuthDarwinCallback,
                                        (__bridge CFStringRef)kKBDarwinAuthChanged,
                                        NULL,
                                        CFNotificationSuspensionBehaviorDeliverImmediately);
    }
    return self;
}

static void KBAuthDarwinCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
    KBAuthManager *self = (__bridge KBAuthManager *)observer;
    [self reloadFromKeychain];
}

- (void)dealloc {
    CFNotificationCenterRemoveObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), (__bridge CFStringRef)kKBDarwinAuthChanged, NULL);
}

- (BOOL)isLoggedIn {
    KBAuthSession *s = self.current;
    if (s.accessToken.length == 0) return NO;
    if (!s.expiryDate) return YES; // 未设置过期时间时，只要有 token 即视为已登录
    return ([s.expiryDate timeIntervalSinceNow] > kKBExpiryGrace);
}

#pragma mark - Public

- (void)reloadFromKeychain {
    NSData *data = [self keychainRead];
    KBAuthSession *session = nil;
    if (data.length > 0) {
        @try {
            session = [NSKeyedUnarchiver unarchivedObjectOfClass:KBAuthSession.class fromData:data error:NULL];
        } @catch (__unused NSException *e) { session = nil; }
    }
    self.current = session;
    [[NSNotificationCenter defaultCenter] postNotificationName:KBAuthChangedNotification object:nil]; // 进程内通知
}

- (BOOL)saveAccessToken:(NSString *)accessToken
          refreshToken:(NSString *)refreshToken
            expiryDate:(NSDate *)expiryDate
        userIdentifier:(NSString *)userIdentifier {
    KBAuthSession *s = [KBAuthSession new];
    s.accessToken = accessToken ?: @"";
    s.refreshToken = refreshToken;
    s.expiryDate = expiryDate;
    s.userIdentifier = userIdentifier;

    NSError *err = nil;
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:s requiringSecureCoding:YES error:&err];
    if (err || data.length == 0) return NO;

    BOOL ok = [self keychainWrite:data];
    if (ok) {
        self.current = s;
        // 进程内通知
        [[NSNotificationCenter defaultCenter] postNotificationName:KBAuthChangedNotification object:nil];
        // 跨进程通知（App <-> 扩展）
        CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge CFStringRef)kKBDarwinAuthChanged, NULL, NULL, true);
    }
    return ok;
}

- (void)signOut {
    [self keychainDelete];
    self.current = nil;
    [[NSNotificationCenter defaultCenter] postNotificationName:KBAuthChangedNotification object:nil];
    CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge CFStringRef)kKBDarwinAuthChanged, NULL, NULL, true);
}

- (NSDictionary<NSString *,NSString *> *)authorizationHeader {
    NSString *t = self.current.accessToken;
    if (t.length == 0) return @{}; // 未登录返回空头部
    return @{ @"Authorization": [@"Bearer " stringByAppendingString:t] };
}

#pragma mark - Keychain (shared)

- (NSMutableDictionary *)baseKCQuery {
    NSMutableDictionary *q = [@{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
                                 (__bridge id)kSecAttrService: kKBKCService,
                                 (__bridge id)kSecAttrAccount: kKBKCAccount } mutableCopy];
    // 指定共享访问组（App 与扩展共用同一组）
    q[(__bridge id)kSecAttrAccessGroup] = KB_KEYCHAIN_ACCESS_GROUP;
    return q;
}

- (BOOL)keychainWrite:(NSData *)data {
    if (!data) return NO;
    NSMutableDictionary *query = [self baseKCQuery];
    SecItemDelete((__bridge CFDictionaryRef)query);

    // 设置属性
    query[(__bridge id)kSecValueData] = data;
    // 访问控制：设备首次解锁后可读，不随备份迁移
    query[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;

    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
#if DEBUG
    if (status != errSecSuccess) {
        KBLog(@"SecItemAdd failed status=%ld group=%@", (long)status, KB_KEYCHAIN_ACCESS_GROUP);
    } else {
        KBLog(@"SecItemAdd ok group=%@", KB_KEYCHAIN_ACCESS_GROUP);
    }
#endif
    return (status == errSecSuccess);
}

- (NSData *)keychainRead {
    NSMutableDictionary *query = [self baseKCQuery];
    query[(__bridge id)kSecReturnData] = @YES;
    query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;

    CFTypeRef dataRef = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataRef);
#if DEBUG
    if (status != errSecSuccess) {
        KBLog(@"SecItemCopyMatching status=%ld group=%@ (item not found or no entitlement)", (long)status, KB_KEYCHAIN_ACCESS_GROUP);
    } else {
        KBLog(@"SecItemCopyMatching ok group=%@", KB_KEYCHAIN_ACCESS_GROUP);
    }
#endif
    if (status != errSecSuccess || !dataRef) return nil;
    return (__bridge_transfer NSData *)dataRef;
}

- (void)keychainDelete {
    NSDictionary *query = [self baseKCQuery];
    SecItemDelete((__bridge CFDictionaryRef)query);
}

@end
