import AuthenticationServices
import CryptoKit
import Foundation
import UIKit

// 应用内 Broker 登录（device-authorization + PKCE，对齐桌面 desktop/platform/oauth.go）。iOS 无法
// 跑 loopback 监听，故用 ASWebAuthenticationSession + 自定义 scheme 回调（cdrop://auth-callback）：
//   1. GET  {broker}/device/authorize?app=cdrop&redirect_uri=cdrop://auth-callback&state&code_challenge&S256
//   2. 用户在 broker SSO 授权 → 回调 cdrop://auth-callback?code=..&state=..
//   3. POST {broker}/device/token {code, code_verifier, redirect_uri} → bootstrap 机器令牌
//   4. POST {api}/api/auth/device-session（Bearer bootstrap）→ cdrop 设备会话（带真名 / device_id）
// 这样手机上直接输账号登录，无需另一台设备扫码批准。
// 前置：broker 须为 cdrop 应用注册 redirect_uri cdrop://auth-callback（同桌面 loopback 须注册）。

enum BrokerLoginError: Error
{
    case incompleteConfig
    case cannotStart
    case noCallback
    case stateMismatch
    case missingCode
    case tokenFailed(Int)
    case deviceSessionFailed(Int)
    case badResponse
}

// ASWebAuthenticationSession 的 async 封装 + 展示锚点提供者。须被持有至完成（session 的
// presentationContextProvider 是 weak，且 session 自身也须保活），故由 AuthManager 在登录期间强持。
@MainActor
final class BrokerAuthFlow: NSObject, ASWebAuthenticationPresentationContextProviding
{
    private var session: ASWebAuthenticationSession?

    // 打开授权页，等回调 URL（cdrop://auth-callback?code=..&state=..）。用户取消 / 失败抛错。
    func run(url: URL, callbackScheme: String) async throws -> URL
    {
        try await withCheckedThrowingContinuation
        { cont in
            let s = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackScheme)
            { [weak self] callback, error in
                self?.session = nil
                if let error { cont.resume(throwing: error) }
                else if let callback { cont.resume(returning: callback) }
                else { cont.resume(throwing: BrokerLoginError.noCallback) }
            }
            s.presentationContextProvider = self
            // 共享浏览会话：若用户已在 Safari 登录 broker SSO，则免再输密码（无缝 SSO）。
            s.prefersEphemeralWebBrowserSession = false
            self.session = s
            if !s.start() { cont.resume(throwing: BrokerLoginError.cannotStart) }
        }
    }

    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor
    {
        let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
        return scenes.flatMap { $0.windows }.first { $0.isKeyWindow }
            ?? scenes.first?.windows.first
            ?? ASPresentationAnchor()
    }
}

// 跨登录持久的稳定 device_id（broker meta / 会话↔设备连接键）。存 UserDefaults，登出不清——
// 故重登复用同一 device_id，broker R2 幂等轮换同一会话、设备表 UPSERT 同一行，杜绝重复设备项
// （对齐 web 的 localStorage cdrop.device_id）。首次为空，由后端铸发后回存。
enum DeviceIDStore
{
    private static let key = "cdrop.device_id"
    static var value: String
    {
        get { UserDefaults.standard.string(forKey: key) ?? "" }
        set { UserDefaults.standard.set(newValue, forKey: key) }
    }
}

// PKCE / 随机串（对齐桌面 randString：n 字节 base64url 取前 n 字符，落在 PKCE unreserved 集内）。
enum PKCE
{
    static func randomToken(_ n: Int) -> String
    {
        var bytes = [UInt8](repeating: 0, count: n)
        _ = SecRandomCopyBytes(kSecRandomDefault, n, &bytes)
        return String(Data(bytes).base64URLEncoded().prefix(n))
    }

    static func challenge(for verifier: String) -> String
    {
        let digest = SHA256.hash(data: Data(verifier.utf8))
        return Data(digest).base64URLEncoded()
    }
}

extension Data
{
    // base64url（无填充，- / _ 替 + /），用于 PKCE challenge / 随机串。
    func base64URLEncoded() -> String
    {
        base64EncodedString()
            .replacingOccurrences(of: "+", with: "-")
            .replacingOccurrences(of: "/", with: "_")
            .replacingOccurrences(of: "=", with: "")
    }
}

extension String
{
    func trimmingTrailingSlash() -> String
    {
        hasSuffix("/") ? String(dropLast()) : self
    }
}
