import Capacitor
import GameKit
import UIKit

/// Wraps `GKLocalPlayer.authenticateHandler` (callback-driven; fires on every
/// auth state change). `silent=true` only inspects `isAuthenticated`;
/// `silent=false` presents the system sign-in UI when GameKit offers it.
internal class SignInModule: PgsModule, UIAdaptivePresentationControllerDelegate {
    private var handlerInstalled = false
    private var handlerAnswered = false
    private var lastAuthError: Error?
    private var interactiveCalls: [CAPPluginCall] = []
    private var interactiveDeadline: DispatchWorkItem?

    /// Idempotent. NOT installed in load(): setting the handler can auto-present
    /// GameKit's "Welcome back" banner, which would suppress an ATT prompt racing
    /// boot, so it is held until the app has requested ATT and called initialize().
    func installAuthHandlerIfNeeded() {
        if !Thread.isMainThread {
            DispatchQueue.main.async { self.installAuthHandlerIfNeeded() }
            return
        }
        if handlerInstalled { return }
        handlerInstalled = true

        GKLocalPlayer.local.authenticateHandler = { [weak self] viewController, error in
            DispatchQueue.main.async {
                guard let self = self else { return }
                self.handlerAnswered = true
                self.lastAuthError = error
                self.pendingSignInVC = viewController
                if viewController != nil {
                    if !self.interactiveCalls.isEmpty { self.presentSignIn() }
                } else {
                    self.emitState()
                    self.finishInteractive(error: error)
                }
            }
        }
    }

    private var pendingSignInVC: UIViewController?
    private weak var presentedSignInVC: UIViewController?

    func signIn(_ call: CAPPluginCall) {
        if !Thread.isMainThread {
            DispatchQueue.main.async { self.signIn(call) }
            return
        }
        let silent = call.getBool("silent", true)
        installAuthHandlerIfNeeded()

        if GKLocalPlayer.local.isAuthenticated {
            resolveSignedIn(call)
            return
        }

        if silent {
            resolveSignedOut(call)
            return
        }

        interactiveCalls.append(call)
        if interactiveCalls.count > 1 { return }
        // Presentation completion only means the UI appeared. Authentication
        // completes through authenticateHandler, or its dismissal delegate.
        let deadline = DispatchWorkItem { [weak self] in
            self?.finishInteractive(error: NSError(
                domain: "PlayGames", code: 1,
                userInfo: [NSLocalizedDescriptionKey: "Game Center sign-in did not finish"]
            ))
        }
        interactiveDeadline = deadline
        DispatchQueue.main.asyncAfter(deadline: .now() + 120, execute: deadline)
        if presentedSignInVC?.presentingViewController != nil { return }
        if pendingSignInVC != nil {
            presentSignIn()
        } else if handlerAnswered {
            finishInteractive(error: lastAuthError, reason: "unavailable")
        }
    }

    private func presentSignIn() {
        guard let controller = pendingSignInVC else { return }
        guard let presenter = topViewController(), presenter.view.window != nil else {
            finishInteractive(reason: "unavailable")
            return
        }
        pendingSignInVC = nil
        presentedSignInVC = controller
        controller.presentationController?.delegate = self
        presenter.present(controller, animated: true) {
            controller.presentationController?.delegate = self
        }
    }

    func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
        finishInteractive(reason: "cancelled")
    }

    private func finishInteractive(error: Error? = nil, reason: String = "not_authenticated") {
        interactiveDeadline?.cancel()
        interactiveDeadline = nil
        let calls = interactiveCalls
        interactiveCalls.removeAll()
        for call in calls {
            if GKLocalPlayer.local.isAuthenticated {
                resolveSignedIn(call)
            } else if let error = error {
                let nativeError = error as NSError
                if nativeError.domain == GKErrorDomain && nativeError.code == GKError.cancelled.rawValue {
                    resolveSignedOut(call, reason: "cancelled")
                } else {
                    call.reject(error.localizedDescription, "\(nativeError.domain):\(nativeError.code)", error)
                }
            } else {
                resolveSignedOut(call, reason: reason)
            }
        }
    }

    func isSignedIn(_ call: CAPPluginCall) {
        call.resolve(["signedIn": GKLocalPlayer.local.isAuthenticated])
    }

    func getPlayer(_ call: CAPPluginCall) {
        guard GKLocalPlayer.local.isAuthenticated else {
            call.reject("not signed in")
            return
        }
        call.resolve(playerInfo(from: GKLocalPlayer.local))
    }

    func fetchIdentityVerificationSignature(_ call: CAPPluginCall) {
        let localPlayer = GKLocalPlayer.local
        guard localPlayer.isAuthenticated else {
            call.reject("not signed in")
            return
        }
        localPlayer.fetchItems(forIdentityVerificationSignature: { publicKeyURL, signature, salt, timestamp, error in
            if let error = error {
                call.reject(error.localizedDescription, nil, error)
                return
            }
            guard let publicKeyURL = publicKeyURL,
                let signature = signature,
                let salt = salt
            else {
                call.reject("identity verification signature unavailable")
                return
            }
            call.resolve([
                "publicKeyUrl": publicKeyURL.absoluteString,
                "signature": signature.base64EncodedString(),
                "salt": salt.base64EncodedString(),
                "timestamp": timestamp,
                "playerId": localPlayer.gamePlayerID,
                "bundleId": Bundle.main.bundleIdentifier ?? "",
                "teamPlayerId": localPlayer.teamPlayerID,
                "gamePlayerId": localPlayer.gamePlayerID,
            ])
        })
    }

    private func resolveSignedIn(_ call: CAPPluginCall) {
        let body: [String: Any] = [
            "signedIn": true,
            "player": playerInfo(from: GKLocalPlayer.local),
        ]
        plugin.emit(event: "signInStateChanged", data: body)
        call.resolve(body)
    }

    private func resolveSignedOut(_ call: CAPPluginCall, reason: String = "not_authenticated") {
        let body: [String: Any] = ["signedIn": false, "reason": reason]
        plugin.emit(event: "signInStateChanged", data: body)
        call.resolve(body)
    }

    /// Re-emit current auth state on a handler-driven change (e.g. signed out via Settings).
    private func emitState() {
        let body: [String: Any]
        if GKLocalPlayer.local.isAuthenticated {
            body = [
                "signedIn": true,
                "player": playerInfo(from: GKLocalPlayer.local),
            ]
        } else {
            body = ["signedIn": false]
        }
        plugin.emit(event: "signInStateChanged", data: body)
    }
}
