<?php
declare(strict_types=1);
// ==================================================
// Nuclear Fancy Pants Edition™ — NUCLEAR+++
// File: /includes/class-google-auth.php
// Description: Google Business Profile OAuth 2.0 flow, token exchange, encrypted storage, auto-refresh - NUCLEAR+++
// Last Edited: 2026-04-20
// ==================================================

namespace AutoMarketerAI;

// No direct access
defined('ABSPATH') || exit;

final class GoogleAuth
{
    // Google OAuth endpoints
    private const AUTH_URL  = 'https://accounts.google.com/o/oauth2/v2/auth';
    private const TOKEN_URL = 'https://oauth2.googleapis.com/token';
    private const REVOKE_URL = 'https://oauth2.googleapis.com/revoke';

    // Google Business Profile API base
    private const GBP_API_BASE = 'https://mybusinessaccountmanagement.googleapis.com/v1';
    private const GBP_INFO_BASE = 'https://mybusinessbusinessinformation.googleapis.com/v1';
    private const GBP_POSTS_BASE = 'https://mybusiness.googleapis.com/v4';

    // OAuth 2.0 scopes
    private const SCOPES = 'https://www.googleapis.com/auth/business.manage';

    // Refresh token 10 days before expiry (Google refresh tokens are long-lived but can be revoked)
    private const TOKEN_REFRESH_BUFFER_SECONDS = 300;

    // Cron hook for periodic token refresh
    public const CRON_HOOK = 'amai_google_token_refresh';

    // wp_options keys for OAuth state
    private const STATE_OPTION = 'amai_google_oauth_state';

    // --------------------------------------------------
    // Public: build the OAuth 2.0 authorization URL
    // --------------------------------------------------
    public static function getAuthUrl(): string
    {
        if (!defined('AMAI_GOOGLE_CLIENT_ID') || (string) AMAI_GOOGLE_CLIENT_ID === '') {
            return '';
        }
        $state = bin2hex(random_bytes(16));
        update_option(self::STATE_OPTION, $state, false);

        $params = [
            'client_id'     => self::getClientId(),
            'redirect_uri'  => self::getCallbackUrl(),
            'response_type' => 'code',
            'scope'         => self::SCOPES,
            'state'         => $state,
            'access_type'   => 'offline',
            'prompt'        => 'consent',
        ];

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

    // --------------------------------------------------
    // Public: handle the OAuth callback
    // --------------------------------------------------
    public static function handleCallback(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_die('Unauthorized');
        }

        $code  = isset($_GET['code'])  ? sanitize_text_field(wp_unslash($_GET['code']))  : '';
        $state = isset($_GET['state']) ? sanitize_text_field(wp_unslash($_GET['state'])) : '';
        $error = isset($_GET['error']) ? sanitize_text_field(wp_unslash($_GET['error'])) : '';

        if ($error !== '') {
            self::redirectWithError('Google authorization was denied: ' . $error);
            return;
        }

        if ($code === '' || $state === '') {
            self::redirectWithError('Missing authorization code or state parameter.');
            return;
        }

        // Verify state matches
        $savedState = get_option(self::STATE_OPTION, '');
        delete_option(self::STATE_OPTION);

        if (!is_string($savedState) || $savedState === '' || !hash_equals($savedState, $state)) {
            self::redirectWithError('Invalid OAuth state. Please try connecting again.');
            return;
        }

        // Exchange authorization code for tokens
        $tokenData = self::exchangeCodeForTokens($code);
        if ($tokenData === null) {
            self::redirectWithError('Failed to exchange authorization code for tokens.');
            return;
        }

        // Fetch account and location info
        $locationData = self::fetchLocation($tokenData['access_token']);
        if ($locationData === null) {
            self::redirectWithError('No verified Google Business Profile found. Make sure your account has a verified location with Owner or Manager access, then try again.');
            return;
        }

        // Store connection
        $saved = self::saveConnection($tokenData, $locationData);
        if (!$saved) {
            self::redirectWithError('Failed to save Google Business Profile connection.');
            return;
        }

        // Schedule token refresh cron
        self::scheduleCron();

        wp_safe_redirect(
            admin_url('admin.php?page=automarketer-ai-social&channel=google&amai_google_connected=1')
        );
        exit;
    }

    // --------------------------------------------------
    // Public: disconnect Google account
    // --------------------------------------------------
    public static function disconnect(): bool
    {
        global $wpdb;

        $table = $wpdb->prefix . 'amai_google_connection';

        // Get current token for revocation
        $row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT access_token_enc FROM {$table} WHERE tenant_id = %s AND status = 'active' LIMIT 1",
                self::getTenantId()
            ),
            ARRAY_A
        );

        if (is_array($row) && isset($row['access_token_enc']) && $row['access_token_enc'] !== '') {
            $accessToken = KeyVault::decrypt($row['access_token_enc']);
            if (is_string($accessToken) && $accessToken !== '') {
                self::revokeToken($accessToken);
            }
        }

        // Delete connection row
        $wpdb->delete($table, ['tenant_id' => self::getTenantId()], ['%s']);

        // Cancel scheduled posts
        $postsTable = $wpdb->prefix . 'amai_scheduled_posts';
        $wpdb->query(
            $wpdb->prepare(
                "UPDATE {$postsTable} SET status = 'rejected', failure_reason = 'Google Business Profile disconnected'
                 WHERE channel = 'google' AND status IN ('draft', 'approved') AND tenant_id = %s",
                self::getTenantId()
            )
        );

        // Unschedule cron
        self::unscheduleCron();

        return true;
    }

    // --------------------------------------------------
    // Public: get the active connection or null
    // --------------------------------------------------
    public static function getConnection(): ?array
    {
        global $wpdb;

        $table = $wpdb->prefix . 'amai_google_connection';

        $row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM {$table} WHERE tenant_id = %s LIMIT 1",
                self::getTenantId()
            ),
            ARRAY_A
        );

        if (!is_array($row)) {
            return null;
        }

        return $row;
    }

    // --------------------------------------------------
    // Public: check if Google is connected and active
    // --------------------------------------------------
    public static function isConnected(): bool
    {
        $conn = self::getConnection();
        return $conn !== null && ($conn['status'] ?? '') === 'active';
    }

    // --------------------------------------------------
    // Public: get a valid access token, refreshing if needed
    // --------------------------------------------------
    public static function getAccessToken(): ?string
    {
        $conn = self::getConnection();
        if ($conn === null || $conn['status'] !== 'active') {
            return null;
        }

        $accessToken = KeyVault::decrypt($conn['access_token_enc']);
        if (!is_string($accessToken) || $accessToken === '') {
            return null;
        }

        // Check if token is expired or about to expire
        $expiresAt = $conn['token_expires_at'] ?? null;
        if ($expiresAt !== null) {
            $expiresTs = strtotime($expiresAt);
            if ($expiresTs !== false && $expiresTs <= (time() + self::TOKEN_REFRESH_BUFFER_SECONDS)) {
                $refreshed = self::refreshAccessToken();
                if ($refreshed) {
                    $conn = self::getConnection();
                    if ($conn === null) {
                        return null;
                    }
                    $accessToken = KeyVault::decrypt($conn['access_token_enc']);
                    if (!is_string($accessToken) || $accessToken === '') {
                        return null;
                    }
                }
            }
        }

        return $accessToken;
    }

    // --------------------------------------------------
    // Public: get location ID from connection
    // --------------------------------------------------
    public static function getLocationId(): ?string
    {
        $conn = self::getConnection();
        if ($conn === null) {
            return null;
        }
        $id = $conn['location_id'] ?? '';
        return $id !== '' ? $id : null;
    }

    // --------------------------------------------------
    // Public: refresh the access token using the refresh token
    // --------------------------------------------------
    public static function refreshAccessToken(): bool
    {
        global $wpdb;

        $table = $wpdb->prefix . 'amai_google_connection';

        $conn = self::getConnection();
        if ($conn === null || $conn['status'] !== 'active') {
            return false;
        }

        $refreshTokenEnc = $conn['refresh_token_enc'] ?? '';
        if ($refreshTokenEnc === '') {
            return false;
        }

        $refreshToken = KeyVault::decrypt($refreshTokenEnc);
        if (!is_string($refreshToken) || $refreshToken === '') {
            self::markConnectionError('refresh_token_decrypt_failed');
            return false;
        }

        $response = wp_remote_post(self::TOKEN_URL, [
            'timeout' => 15,
            'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
            'body'    => [
                'grant_type'    => 'refresh_token',
                'refresh_token' => $refreshToken,
                'client_id'     => self::getClientId(),
                'client_secret' => self::getClientSecret(),
            ],
        ]);

        if (is_wp_error($response)) {
            return false;
        }

        $httpCode = (int) wp_remote_retrieve_response_code($response);
        $json     = json_decode(wp_remote_retrieve_body($response), true);

        if ($httpCode !== 200 || !is_array($json) || !isset($json['access_token'])) {
            if ($httpCode === 400 || $httpCode === 401) {
                self::markConnectionError('refresh_token_expired');
            }
            return false;
        }

        $expiresIn = isset($json['expires_in']) ? (int) $json['expires_in'] : 3600;

        $wpdb->update(
            $table,
            [
                'access_token_enc' => KeyVault::encrypt((string) $json['access_token']),
                'token_expires_at' => gmdate('Y-m-d H:i:s', time() + $expiresIn),
                'last_verified_at' => gmdate('Y-m-d H:i:s'),
                'status'           => 'active',
            ],
            ['tenant_id' => self::getTenantId()],
            ['%s', '%s', '%s', '%s'],
            ['%s']
        );

        return true;
    }

    // --------------------------------------------------
    // Public: schedule/unschedule cron
    // --------------------------------------------------
    public static function scheduleCron(): void
    {
        if (!wp_next_scheduled(self::CRON_HOOK)) {
            wp_schedule_event(time(), 'twicedaily', self::CRON_HOOK);
        }
    }

    public static function unscheduleCron(): void
    {
        $ts = wp_next_scheduled(self::CRON_HOOK);
        if ($ts !== false) {
            wp_unschedule_event($ts, self::CRON_HOOK);
        }
    }

    // --------------------------------------------------
    // Private: exchange authorization code for tokens
    // --------------------------------------------------
    private static function exchangeCodeForTokens(string $code): ?array
    {
        $response = wp_remote_post(self::TOKEN_URL, [
            'timeout' => 15,
            'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
            'body'    => [
                'grant_type'    => 'authorization_code',
                'code'          => $code,
                'redirect_uri'  => self::getCallbackUrl(),
                'client_id'     => self::getClientId(),
                'client_secret' => self::getClientSecret(),
            ],
        ]);

        if (is_wp_error($response)) {
            return null;
        }

        $httpCode = (int) wp_remote_retrieve_response_code($response);
        $json     = json_decode(wp_remote_retrieve_body($response), true);

        if ($httpCode !== 200 || !is_array($json) || !isset($json['access_token'])) {
            return null;
        }

        return [
            'access_token'  => (string) $json['access_token'],
            'refresh_token' => isset($json['refresh_token']) ? (string) $json['refresh_token'] : '',
            'expires_in'    => isset($json['expires_in']) ? (int) $json['expires_in'] : 3600,
        ];
    }

    // --------------------------------------------------
    // Private: fetch the first verified GBP location
    // --------------------------------------------------
    private static function fetchLocation(string $accessToken): ?array
    {
        // Step 1: get account list
        $accountsResponse = wp_remote_get(self::GBP_API_BASE . '/accounts', [
            'timeout' => 15,
            'headers' => ['Authorization' => 'Bearer ' . $accessToken],
        ]);

        if (is_wp_error($accountsResponse)) {
            return null;
        }

        $accountsJson = json_decode(wp_remote_retrieve_body($accountsResponse), true);
        if (!is_array($accountsJson) || empty($accountsJson['accounts'])) {
            return null;
        }

        $accountName = (string) ($accountsJson['accounts'][0]['name'] ?? '');
        if ($accountName === '') {
            return null;
        }

        $accountId = basename($accountName);

        // Step 2: get locations for this account
        $locationsResponse = wp_remote_get(
            self::GBP_INFO_BASE . '/' . $accountName . '/locations?readMask=name,title',
            [
                'timeout' => 15,
                'headers' => ['Authorization' => 'Bearer ' . $accessToken],
            ]
        );

        if (is_wp_error($locationsResponse)) {
            return null;
        }

        $locationsJson = json_decode(wp_remote_retrieve_body($locationsResponse), true);
        if (!is_array($locationsJson) || empty($locationsJson['locations'])) {
            return null;
        }

        $location     = $locationsJson['locations'][0];
        $locationName = (string) ($location['name'] ?? '');
        $businessName = (string) ($location['title'] ?? '');

        if ($locationName === '') {
            return null;
        }

        return [
            'google_account_id' => $accountId,
            'location_id'       => $locationName,
            'location_name'     => $locationName,
            'business_name'     => $businessName,
        ];
    }

    // --------------------------------------------------
    // Private: save or update the connection row
    // --------------------------------------------------
    private static function saveConnection(array $tokenData, array $locationData): bool
    {
        global $wpdb;

        $table    = $wpdb->prefix . 'amai_google_connection';
        $tenantId = self::getTenantId();

        $refreshToken = $tokenData['refresh_token'];
        if ($refreshToken === '') {
            // Try to preserve existing refresh token if Google did not return a new one
            $existing = self::getConnection();
            if ($existing !== null && isset($existing['refresh_token_enc']) && $existing['refresh_token_enc'] !== '') {
                $refreshToken = KeyVault::decrypt($existing['refresh_token_enc']);
            }
        }

        $data = [
            'google_account_id' => $locationData['google_account_id'],
            'location_id'       => $locationData['location_id'],
            'location_name'     => $locationData['location_name'],
            'business_name'     => $locationData['business_name'],
            'access_token_enc'  => KeyVault::encrypt($tokenData['access_token']),
            'refresh_token_enc' => $refreshToken !== '' ? KeyVault::encrypt($refreshToken) : '',
            'token_expires_at'  => gmdate('Y-m-d H:i:s', time() + $tokenData['expires_in']),
            'connected_at'      => gmdate('Y-m-d H:i:s'),
            'last_verified_at'  => gmdate('Y-m-d H:i:s'),
            'status'            => 'active',
        ];

        $formats = ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'];

        $existingRow = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT id FROM {$table} WHERE tenant_id = %s LIMIT 1",
                $tenantId
            )
        );

        if ($existingRow !== null) {
            $wpdb->update($table, $data, ['tenant_id' => $tenantId], $formats, ['%s']);
        } else {
            $data['tenant_id'] = $tenantId;
            $formats[]         = '%s';
            $wpdb->insert($table, $data, $formats);
        }

        return $wpdb->last_error === '';
    }

    // --------------------------------------------------
    // Private: revoke an access token
    // --------------------------------------------------
    private static function revokeToken(string $token): void
    {
        wp_remote_post(self::REVOKE_URL . '?token=' . rawurlencode($token), [
            'timeout' => 10,
            'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
        ]);
    }

    // --------------------------------------------------
    // Private: mark connection as error
    // --------------------------------------------------
    private static function markConnectionError(string $reason): void
    {
        global $wpdb;

        $wpdb->update(
            $wpdb->prefix . 'amai_google_connection',
            ['status' => 'error'],
            ['tenant_id' => self::getTenantId()],
            ['%s'],
            ['%s']
        );
    }

    // --------------------------------------------------
    // Private: redirect with error message
    // --------------------------------------------------
    private static function redirectWithError(string $message): void
    {
        wp_safe_redirect(
            admin_url('admin.php?page=automarketer-ai-social&channel=google&amai_google_error=' . rawurlencode($message))
        );
        exit;
    }

    // --------------------------------------------------
    // Private: get callback URL
    // --------------------------------------------------
    private static function getCallbackUrl(): string
    {
        return home_url('/google/callback');
    }

    // --------------------------------------------------
    // Private: get Client ID from wp-config constant
    // --------------------------------------------------
    private static function getClientId(): string
    {
        if (!defined('AMAI_GOOGLE_CLIENT_ID') || !is_string(AMAI_GOOGLE_CLIENT_ID) || AMAI_GOOGLE_CLIENT_ID === '') {
            return '';
        }
        return AMAI_GOOGLE_CLIENT_ID;
    }

    // --------------------------------------------------
    // Private: get Client Secret from wp-config constant
    // --------------------------------------------------
    private static function getClientSecret(): string
    {
        if (!defined('AMAI_GOOGLE_CLIENT_SECRET') || !is_string(AMAI_GOOGLE_CLIENT_SECRET) || AMAI_GOOGLE_CLIENT_SECRET === '') {
            return '';
        }
        return AMAI_GOOGLE_CLIENT_SECRET;
    }

    // --------------------------------------------------
    // Private: get tenant ID
    // --------------------------------------------------
    private static function getTenantId(): string
    {
        return get_option('amai_tenant_id', 'default');
    }
}