import Foundation
import AuthenticationServices
import CryptoKit
import Security
import UIKit
import ComposeApp

/// Handles Google OAuth flow using ASWebAuthenticationSession.
/// Obtains a Google ID token, then sends it to the backend via APILayer.
@MainActor
final class GoogleSignInManager: NSObject, ObservableObject, ASWebAuthenticationPresentationContextProviding {
    static let shared = GoogleSignInManager()

    @Published var isLoading = false
    @Published var errorMessage: String?

    /// Called on successful sign-in with (isVerified: Bool)
    var onSignInSuccess: ((Bool) -> Void)?

    private var webAuthSession: ASWebAuthenticationSession?
    private var codeVerifier: String?
    private var oauthState: String?

    private let redirectScheme = "honeydue"
    private var redirectURI: String { "\(redirectScheme):/oauth2callback" }

    // MARK: - Public

    func signIn() {
        guard !isLoading else { return }

        guard let clientId = resolvedClientID() else {
            errorMessage = "Google Sign-In is not configured. A Google Cloud client ID is required."
            return
        }

        isLoading = true
        errorMessage = nil

        // PKCE + state are required for secure native OAuth.
        let verifier = Self.randomURLSafeString(length: 64)
        let challenge = Self.sha256Base64URL(verifier)
        let state = Self.randomURLSafeString(length: 32)
        codeVerifier = verifier
        oauthState = state

        // Build Google OAuth URL
        var components = URLComponents(string: "https://accounts.google.com/o/oauth2/v2/auth")!
        components.queryItems = [
            URLQueryItem(name: "client_id", value: clientId),
            URLQueryItem(name: "redirect_uri", value: redirectURI),
            URLQueryItem(name: "response_type", value: "code"),
            URLQueryItem(name: "scope", value: "openid email profile"),
            URLQueryItem(name: "access_type", value: "offline"),
            URLQueryItem(name: "prompt", value: "select_account"),
            URLQueryItem(name: "code_challenge", value: challenge),
            URLQueryItem(name: "code_challenge_method", value: "S256"),
            URLQueryItem(name: "state", value: state),
        ]

        guard let authURL = components.url else {
            isLoading = false
            errorMessage = "Failed to build authentication URL"
            return
        }

        let session = ASWebAuthenticationSession(
            url: authURL,
            callbackURLScheme: redirectScheme
        ) { [weak self] callbackURL, error in
            Task { @MainActor in
                guard let self else { return }

                if let error {
                    self.resetOAuthState()
                    self.isLoading = false
                    // Don't show error for user cancellation
                    if (error as NSError).code != ASWebAuthenticationSessionError.canceledLogin.rawValue {
                        self.errorMessage = "Sign in failed: \(error.localizedDescription)"
                    }
                    return
                }

                guard let callbackURL,
                      let components = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false) else {
                    self.isLoading = false
                    self.resetOAuthState()
                    self.errorMessage = "Failed to get authorization code from Google"
                    return
                }

                if let oauthError = components.queryItems?.first(where: { $0.name == "error" })?.value {
                    self.isLoading = false
                    self.resetOAuthState()
                    self.errorMessage = "Google Sign-In error: \(oauthError)"
                    return
                }

                guard let callbackState = components.queryItems?.first(where: { $0.name == "state" })?.value,
                      callbackState == self.oauthState else {
                    self.isLoading = false
                    self.resetOAuthState()
                    self.errorMessage = "Invalid Google OAuth state. Please try again."
                    return
                }

                guard let code = components.queryItems?.first(where: { $0.name == "code" })?.value,
                      let verifier = self.codeVerifier else {
                    self.isLoading = false
                    self.resetOAuthState()
                    self.errorMessage = "Failed to get authorization code from Google"
                    return
                }

                await self.exchangeCodeForToken(
                    code: code,
                    redirectURI: self.redirectURI,
                    clientId: clientId,
                    codeVerifier: verifier
                )
            }
        }

        session.presentationContextProvider = self
        session.prefersEphemeralWebBrowserSession = false
        webAuthSession = session
        session.start()
    }

    // MARK: - ASWebAuthenticationPresentationContextProviding

    nonisolated func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        MainActor.assumeIsolated {
            // Return the key window for presentation
            let scenes = UIApplication.shared.connectedScenes
            let windowScene = scenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene
            return windowScene?.windows.first(where: { $0.isKeyWindow }) ?? ASPresentationAnchor()
        }
    }

    // MARK: - Private

    /// Exchange authorization code for ID token via Google's token endpoint
    private func exchangeCodeForToken(
        code: String,
        redirectURI: String,
        clientId: String,
        codeVerifier: String
    ) async {
        let tokenURL = URL(string: "https://oauth2.googleapis.com/token")!
        var request = URLRequest(url: tokenURL)
        request.httpMethod = "POST"
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")

        let body = [
            "code": code,
            "client_id": clientId,
            "redirect_uri": redirectURI,
            "grant_type": "authorization_code",
            "code_verifier": codeVerifier,
        ]

        var encoded = URLComponents()
        encoded.queryItems = body.map { URLQueryItem(name: $0.key, value: $0.value) }
        request.httpBody = encoded.percentEncodedQuery?.data(using: .utf8)

        do {
            let (data, response) = try await URLSession.shared.data(for: request)

            guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
                resetOAuthState()
                isLoading = false
                errorMessage = "Failed to exchange authorization code"
                return
            }

            let json: [String: Any]
            do {
                guard let parsed = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
                    resetOAuthState()
                    isLoading = false
                    errorMessage = "Failed to get ID token from Google"
                    return
                }
                json = parsed
            } catch {
                print("GoogleSignInManager: Failed to parse token response JSON: \(error)")
                resetOAuthState()
                isLoading = false
                errorMessage = "Failed to get ID token from Google"
                return
            }

            guard let idToken = json["id_token"] as? String else {
                resetOAuthState()
                isLoading = false
                errorMessage = "Failed to get ID token from Google"
                return
            }

            // Send ID token to backend
            await sendToBackend(idToken: idToken)
        } catch {
            resetOAuthState()
            isLoading = false
            errorMessage = "Network error: \(error.localizedDescription)"
        }
    }

    /// Send Google ID token to backend for verification and authentication
    private func sendToBackend(idToken: String) async {
        let request = GoogleSignInRequest(idToken: idToken)
        let result: Any
        do {
            result = try await APILayer.shared.googleSignIn(request: request)
        } catch {
            print("GoogleSignInManager: Backend sign-in request failed: \(error)")
            resetOAuthState()
            isLoading = false
            errorMessage = "Sign in failed: \(error.localizedDescription)"
            return
        }

        if let success = result as? ApiResultSuccess<GoogleSignInResponse>, let response = success.data {
            resetOAuthState()
            isLoading = false

            // Share token and API URL with widget extension
            WidgetDataManager.shared.saveAuthToken(response.token)
            WidgetDataManager.shared.saveAPIBaseURL(ApiClient.shared.getBaseUrl())

            // Track Google Sign In
            AnalyticsManager.shared.track(.userSignedInGoogle(isNewUser: response.isNewUser))

            // Call success callback with verification status
            onSignInSuccess?(response.user.verified)

        } else if let error = ApiResultBridge.error(from: result) {
            resetOAuthState()
            isLoading = false
            errorMessage = ErrorMessageParser.parse(error.message)
        } else {
            resetOAuthState()
            isLoading = false
            errorMessage = "Sign in failed. Please try again."
        }
    }

    private func resolvedClientID() -> String? {
        if let fromInfo = Bundle.main.object(forInfoDictionaryKey: "HONEYDUE_GOOGLE_WEB_CLIENT_ID") as? String {
            let trimmed = fromInfo.trimmingCharacters(in: .whitespacesAndNewlines)
            if !trimmed.isEmpty {
                return trimmed
            }
        }

        let fromShared = ApiConfig.shared.GOOGLE_WEB_CLIENT_ID.trimmingCharacters(in: .whitespacesAndNewlines)
        return fromShared.isEmpty ? nil : fromShared
    }

    private func resetOAuthState() {
        codeVerifier = nil
        oauthState = nil
    }

    private static func randomURLSafeString(length: Int) -> String {
        let allowed = Array("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")
        var bytes = [UInt8](repeating: 0, count: length)
        _ = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
        return String(bytes.map { allowed[Int($0) % allowed.count] })
    }

    private static func sha256Base64URL(_ input: String) -> String {
        let digest = SHA256.hash(data: Data(input.utf8))
        return Data(digest)
            .base64EncodedString()
            .replacingOccurrences(of: "+", with: "-")
            .replacingOccurrences(of: "/", with: "_")
            .replacingOccurrences(of: "=", with: "")
    }
}
