<?php

namespace App\Services;

use App\Models\GoogleMeetConfiguration;
use App\Models\MeetingLog;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Carbon\Carbon;

/**
 * OAuth2Service
 * 
 * Handles Google OAuth2 authentication flow for Google Meet API.
 * Provides methods for authorization URL generation, token exchange,
 * token refresh, and token revocation.
 * 
 * @see https://developers.google.com/identity/protocols/oauth2
 */
class OAuth2Service
{
    /**
     * Google OAuth2 authorization URL.
     */
    const AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';

    /**
     * Google OAuth2 token URL.
     */
    const TOKEN_URL = 'https://oauth2.googleapis.com/token';

    /**
     * Google OAuth2 revoke URL.
     */
    const REVOKE_URL = 'https://oauth2.googleapis.com/revoke';

    /**
     * OAuth2 scopes required for Google Meet.
     */
    const SCOPES = [
        'https://www.googleapis.com/auth/calendar.events',
        'https://www.googleapis.com/auth/calendar',
    ];

    /**
     * Configuration service instance.
     *
     * @var GoogleMeetConfigurationService
     */
    protected GoogleMeetConfigurationService $configService;

    /**
     * Create a new OAuth2Service instance.
     *
     * @param GoogleMeetConfigurationService $configService
     */
    public function __construct(GoogleMeetConfigurationService $configService)
    {
        $this->configService = $configService;
    }

    /**
     * Generate Google OAuth2 authorization URL.
     *
     * @param GoogleMeetConfiguration $config
     * @return string
     */
    public function getAuthorizationUrl(GoogleMeetConfiguration $config): string
    {
        $params = [
            'client_id' => $config->client_id,
            'redirect_uri' => $config->redirect_uri,
            'response_type' => 'code',
            'scope' => implode(' ', self::SCOPES),
            'access_type' => 'offline',
            'prompt' => 'consent',
            'state' => Str::random(40),
        ];

        // Store state in session for CSRF protection
        session(['google_oauth_state' => $params['state']]);

        return self::AUTH_URL . '?' . http_build_query($params);
    }

    /**
     * Handle OAuth2 callback and exchange code for tokens.
     *
     * @param string $code
     * @param string|null $state
     * @return array
     * @throws \Exception
     */
    public function handleCallback(string $code, ?string $state = null): array
    {
        // Verify state for CSRF protection
        if ($state && $state !== session('google_oauth_state')) {
            throw new \Exception('Invalid OAuth state. Possible CSRF attack.');
        }

        $config = $this->configService->getConfigurationOrFail();
        $startTime = microtime(true);

        try {
            $response = Http::asForm()->post(self::TOKEN_URL, [
                'code' => $code,
                'client_id' => $config->client_id,
                'client_secret' => $config->client_secret,
                'redirect_uri' => $config->redirect_uri,
                'grant_type' => 'authorization_code',
            ]);

            if (!$response->successful()) {
                $error = $response->json('error_description', 'Failed to exchange authorization code');
                throw new \Exception($error);
            }

            $tokenData = $response->json();

            // Store tokens in configuration
            $this->configService->storeTokens($config, $tokenData);

            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            Log::info('Google OAuth2 callback successful', [
                'config_id' => $config->id,
                'execution_time_ms' => $executionTime,
            ]);

            return [
                'success' => true,
                'message' => 'Authentication successful',
                'expires_in' => $tokenData['expires_in'] ?? 3600,
            ];
        } catch (\Exception $e) {
            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            Log::error('Google OAuth2 callback failed', [
                'error' => $e->getMessage(),
                'execution_time_ms' => $executionTime,
            ]);

            throw $e;
        }
    }

    /**
     * Refresh expired access token.
     *
     * @param GoogleMeetConfiguration $config
     * @return bool
     * @throws \Exception
     */
    public function refreshAccessToken(GoogleMeetConfiguration $config): bool
    {
        if (!$config->refresh_token) {
            throw new \Exception('No refresh token available. Please re-authenticate.');
        }

        $startTime = microtime(true);

        try {
            $response = Http::asForm()->post(self::TOKEN_URL, [
                'refresh_token' => $config->refresh_token,
                'client_id' => $config->client_id,
                'client_secret' => $config->client_secret,
                'grant_type' => 'refresh_token',
            ]);

            if (!$response->successful()) {
                $error = $response->json('error_description', 'Failed to refresh token');
                
                // Log failure
                $executionTime = (int) ((microtime(true) - $startTime) * 1000);
                MeetingLog::logTokenRefresh(false, $executionTime, $error);
                
                throw new \Exception($error);
            }

            $tokenData = $response->json();

            // Update access token
            $this->configService->updateAccessToken(
                $config,
                $tokenData['access_token'],
                $tokenData['expires_in']
            );

            $executionTime = (int) ((microtime(true) - $startTime) * 1000);
            MeetingLog::logTokenRefresh(true, $executionTime);

            Log::info('Google OAuth2 token refreshed', [
                'config_id' => $config->id,
                'execution_time_ms' => $executionTime,
            ]);

            return true;
        } catch (\Exception $e) {
            Log::error('Google OAuth2 token refresh failed', [
                'error' => $e->getMessage(),
            ]);

            throw $e;
        }
    }

    /**
     * Revoke OAuth2 access.
     *
     * @param GoogleMeetConfiguration $config
     * @return bool
     */
    public function revokeAccess(GoogleMeetConfiguration $config): bool
    {
        $startTime = microtime(true);

        try {
            if ($config->access_token) {
                $response = Http::asForm()->post(self::REVOKE_URL, [
                    'token' => $config->access_token,
                ]);

                Log::info('Google OAuth2 access revoked', [
                    'config_id' => $config->id,
                    'success' => $response->successful(),
                ]);
            }

            // Clear tokens from configuration
            $this->configService->clearTokens($config);

            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            MeetingLog::create([
                'type' => MeetingLog::TYPE_REVOKE_ACCESS,
                'status' => MeetingLog::STATUS_SUCCESS,
                'execution_time_ms' => $executionTime,
            ]);

            return true;
        } catch (\Exception $e) {
            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            MeetingLog::create([
                'type' => MeetingLog::TYPE_REVOKE_ACCESS,
                'status' => MeetingLog::STATUS_FAILED,
                'error_message' => $e->getMessage(),
                'execution_time_ms' => $executionTime,
            ]);

            Log::error('Google OAuth2 revoke failed', [
                'error' => $e->getMessage(),
            ]);

            return false;
        }
    }

    /**
     * Check if access token is expired.
     *
     * @param GoogleMeetConfiguration $config
     * @return bool
     */
    public function isTokenExpired(GoogleMeetConfiguration $config): bool
    {
        return $config->isTokenExpired();
    }

    /**
     * Check if token needs refresh and refresh if necessary.
     *
     * @param GoogleMeetConfiguration $config
     * @return bool
     */
    public function ensureValidToken(GoogleMeetConfiguration $config): bool
    {
        if (!$config->hasTokens()) {
            return false;
        }

        if ($this->isTokenExpired($config)) {
            try {
                return $this->refreshAccessToken($config);
            } catch (\Exception $e) {
                Log::error('Failed to refresh expired token', [
                    'error' => $e->getMessage(),
                ]);
                return false;
            }
        }

        return true;
    }

    /**
     * Get valid access token (refresh if necessary).
     *
     * @param GoogleMeetConfiguration $config
     * @return string|null
     */
    public function getValidAccessToken(GoogleMeetConfiguration $config): ?string
    {
        if (!$this->ensureValidToken($config)) {
            return null;
        }

        // Refresh the model to get updated token
        $config->refresh();

        return $config->access_token;
    }

    /**
     * Validate OAuth2 credentials by attempting token info retrieval.
     *
     * @param string $clientId
     * @param string $clientSecret
     * @param string $redirectUri
     * @return array
     */
    public function validateCredentials(string $clientId, string $clientSecret, string $redirectUri): array
    {
        $errors = [];

        // Validate Client ID format
        if (!preg_match('/^[0-9]+-[a-zA-Z0-9]+\.apps\.googleusercontent\.com$/', $clientId)) {
            $errors[] = 'Invalid Client ID format. Expected format: xxx-xxx.apps.googleusercontent.com';
        }

        // Validate Client Secret format
        if (strlen($clientSecret) < 20) {
            $errors[] = 'Client Secret appears to be too short.';
        }

        // Validate Redirect URI
        if (!filter_var($redirectUri, FILTER_VALIDATE_URL)) {
            $errors[] = 'Invalid Redirect URI format.';
        }

        if (!str_starts_with($redirectUri, 'https://') && !str_starts_with($redirectUri, 'http://localhost')) {
            $errors[] = 'Redirect URI should use HTTPS in production.';
        }

        return [
            'valid' => empty($errors),
            'errors' => $errors,
        ];
    }
}
