package com.openclaw.alfred.auth

import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.activity.ComponentActivity
import net.openid.appauth.*

/**
 * Manages OAuth authentication flow with Authentik.
 * Handles login, token storage, and token refresh.
 */
class AuthManager(private val context: Context) {
    
    private val TAG = "AuthManager"
    
    private val authService: AuthorizationService = AuthorizationService(context)
    
    private val serviceConfig = AuthorizationServiceConfiguration(
        OAuthConfig.AUTHORIZATION_ENDPOINT,
        OAuthConfig.TOKEN_ENDPOINT
    )
    
    private val prefs = context.getSharedPreferences(OAuthConfig.PREFS_NAME, Context.MODE_PRIVATE)
    
    /**
     * Check if user is currently logged in (has valid token).
     */
    fun isLoggedIn(): Boolean {
        val token = prefs.getString(OAuthConfig.KEY_ACCESS_TOKEN, null)
        val expiry = prefs.getLong(OAuthConfig.KEY_TOKEN_EXPIRY, 0)
        
        Log.d(TAG, "isLoggedIn check: token=${token?.take(10)}..., expiry=$expiry")
        
        if (token.isNullOrEmpty()) {
            Log.d(TAG, "isLoggedIn: false (no token)")
            return false
        }
        
        // Check if token is expired (with 30 second buffer for safety)
        val now = System.currentTimeMillis()
        val bufferMs = 30 * 1000  // 30 second buffer (reduced from 60s for longer persistence)
        val isValid = expiry > (now + bufferMs)
        Log.d(TAG, "isLoggedIn: $isValid (now=$now, expiry=$expiry, diff=${expiry - now}ms, buffer=${bufferMs}ms)")
        return isValid
    }
    
    /**
     * Get the current access token (if logged in).
     */
    fun getAccessToken(): String? {
        return if (isLoggedIn()) {
            prefs.getString(OAuthConfig.KEY_ACCESS_TOKEN, null)
        } else {
            null
        }
    }
    
    /**
     * Start the OAuth login flow.
     * Opens browser for Authentik authentication.
     */
    fun startLogin(activity: ComponentActivity, onComplete: (Boolean, String?) -> Unit) {
        Log.d(TAG, "Starting OAuth login flow")
        
        val authRequest = AuthorizationRequest.Builder(
            serviceConfig,
            OAuthConfig.CLIENT_ID,
            ResponseTypeValues.CODE,
            OAuthConfig.REDIRECT_URI
        )
            .setScope(OAuthConfig.SCOPE)
            .build()
        
        // Store the request for later validation
        val authRequestJson = authRequest.jsonSerializeString()
        prefs.edit().putString("pending_auth_request", authRequestJson).apply()
        
        val authIntent = authService.getAuthorizationRequestIntent(authRequest)
        activity.startActivity(authIntent)
    }
    
    /**
     * Handle OAuth callback after user authorizes.
     * Called from OAuthCallbackActivity.
     */
    fun handleAuthorizationResponse(
        intent: Intent,
        onSuccess: () -> Unit,
        onError: (String) -> Unit
    ) {
        // Try AppAuth's standard parsing first
        var response = AuthorizationResponse.fromIntent(intent)
        var exception = AuthorizationException.fromIntent(intent)
        
        // If that fails, manually parse the redirect URI
        if (response == null && exception == null) {
            val data = intent.data
            if (data != null) {
                Log.d(TAG, "Manually parsing OAuth response from: $data")
                
                // Retrieve the stored auth request
                val authRequestJson = prefs.getString("pending_auth_request", null)
                if (authRequestJson != null) {
                    try {
                        val authRequest = AuthorizationRequest.jsonDeserialize(authRequestJson)
                        response = AuthorizationResponse.Builder(authRequest)
                            .fromUri(data)
                            .build()
                        
                        // Clear the pending request
                        prefs.edit().remove("pending_auth_request").apply()
                    } catch (e: Exception) {
                        Log.e(TAG, "Failed to deserialize auth request", e)
                        exception = AuthorizationException.fromTemplate(
                            AuthorizationException.GeneralErrors.JSON_DESERIALIZATION_ERROR,
                            e
                        )
                    }
                } else {
                    Log.e(TAG, "No pending auth request found")
                    exception = AuthorizationException.GeneralErrors.PROGRAM_CANCELED_AUTH_FLOW
                }
            }
        }
        
        when {
            response != null -> {
                Log.d(TAG, "Authorization successful, exchanging code for token")
                exchangeCodeForToken(response, onSuccess, onError)
            }
            exception != null -> {
                Log.e(TAG, "Authorization failed: ${exception.message}")
                onError("Authorization failed: ${exception.message}")
            }
            else -> {
                Log.e(TAG, "Authorization failed: Unknown error")
                onError("Authorization failed: Unknown error")
            }
        }
    }
    
    /**
     * Exchange authorization code for access token.
     */
    private fun exchangeCodeForToken(
        authResponse: AuthorizationResponse,
        onSuccess: () -> Unit,
        onError: (String) -> Unit
    ) {
        val tokenRequest = authResponse.createTokenExchangeRequest()
        
        authService.performTokenRequest(tokenRequest) { tokenResponse, exception ->
            when {
                tokenResponse != null -> {
                    Log.d(TAG, "Token exchange successful")
                    saveTokens(tokenResponse)
                    onSuccess()
                }
                exception != null -> {
                    Log.e(TAG, "Token exchange failed: ${exception.message}")
                    onError("Token exchange failed: ${exception.message}")
                }
                else -> {
                    Log.e(TAG, "Token exchange failed: Unknown error")
                    onError("Token exchange failed: Unknown error")
                }
            }
        }
    }
    
    /**
     * Save tokens to SharedPreferences.
     */
    private fun saveTokens(tokenResponse: TokenResponse) {
        val expiresIn = tokenResponse.accessTokenExpirationTime ?: 0L
        
        Log.d(TAG, "Saving tokens: access=${tokenResponse.accessToken?.take(10)}..., expiry=$expiresIn")
        
        prefs.edit().apply {
            putString(OAuthConfig.KEY_ACCESS_TOKEN, tokenResponse.accessToken)
            putString(OAuthConfig.KEY_REFRESH_TOKEN, tokenResponse.refreshToken)
            putString(OAuthConfig.KEY_ID_TOKEN, tokenResponse.idToken)
            putLong(OAuthConfig.KEY_TOKEN_EXPIRY, expiresIn)
            apply()
        }
        
        Log.d(TAG, "Tokens saved successfully, verifying...")
        // Verify the save
        val saved = prefs.getString(OAuthConfig.KEY_ACCESS_TOKEN, null)
        val savedExpiry = prefs.getLong(OAuthConfig.KEY_TOKEN_EXPIRY, 0)
        Log.d(TAG, "Verification: token=${saved?.take(10)}..., expiry=$savedExpiry")
    }
    
    /**
     * Check if token needs refresh (expired or expiring soon).
     */
    fun needsRefresh(): Boolean {
        val expiry = prefs.getLong(OAuthConfig.KEY_TOKEN_EXPIRY, 0)
        val now = System.currentTimeMillis()
        val bufferMs = 5 * 60 * 1000 // 5 minute buffer - refresh before expiry
        
        val needsRefresh = expiry < (now + bufferMs)
        Log.d(TAG, "needsRefresh: $needsRefresh (expiry in ${(expiry - now) / 1000}s)")
        return needsRefresh
    }
    
    /**
     * Refresh the access token using the refresh token.
     * @return true if refresh successful, false if refresh token is invalid/missing
     */
    fun refreshToken(onSuccess: () -> Unit, onError: (String) -> Unit) {
        val refreshToken = prefs.getString(OAuthConfig.KEY_REFRESH_TOKEN, null)
        
        if (refreshToken.isNullOrEmpty()) {
            Log.w(TAG, "No refresh token available, cannot refresh")
            onError("No refresh token available")
            return
        }
        
        Log.d(TAG, "Refreshing access token using refresh token")
        
        val tokenRequest = TokenRequest.Builder(serviceConfig, OAuthConfig.CLIENT_ID)
            .setGrantType(GrantTypeValues.REFRESH_TOKEN)
            .setRefreshToken(refreshToken)
            .build()
        
        authService.performTokenRequest(tokenRequest) { tokenResponse, exception ->
            when {
                tokenResponse != null -> {
                    Log.d(TAG, "Token refresh successful")
                    saveTokens(tokenResponse)
                    onSuccess()
                }
                exception != null -> {
                    Log.e(TAG, "Token refresh failed: ${exception.message}")
                    // Clear tokens on refresh failure (refresh token is invalid)
                    logout()
                    onError("Token refresh failed: ${exception.message}")
                }
                else -> {
                    Log.e(TAG, "Token refresh failed: Unknown error")
                    logout()
                    onError("Token refresh failed: Unknown error")
                }
            }
        }
    }
    
    /**
     * Log out user and clear stored tokens.
     */
    fun logout() {
        Log.d(TAG, "Logging out user")
        prefs.edit().clear().apply()
    }
    
    /**
     * Clean up resources.
     */
    fun dispose() {
        authService.dispose()
    }
}
