<?php
declare(strict_types=1);
// ==================================================
// Nuclear Fancy Pants Edition™ -- NUCLEAR+++
// File: /home/ploi/automarketer-ai.com/public/wp-content/plugins/automarketer-ai/includes/class-pinterest-auth.php
// Description: Pinterest OAuth 2.0 PKCE flow, token exchange, encrypted token storage, daily refresh cron - NUCLEAR+++
// Last Edited: 2026-05-27
// ==================================================

namespace AutoMarketerAI;

defined('ABSPATH') || exit;

final class PinterestAuth
{
    // Pinterest API endpoints
    private const AUTH_BASE     = 'https://www.pinterest.com/oauth/';
    private const TOKEN_URL     = 'https://api.pinterest.com/v5/oauth/token';
    private const USER_URL      = 'https://api.pinterest.com/v5/user_account';
    private const REVOKE_URL    = 'https://api.pinterest.com/v5/oauth/token/revoke';

    // Scopes required for read + publishing
    private const SCOPES = [
        'user_accounts:read',
        'pins:read',
        'pins:write',
        'boards:read',
    ];

    // wp_options keys
    private const STATE_OPTION    = 'amai_pin_oauth_state';
    private const VERIFIER_OPTION = 'amai_pin_code_verifier';

    // WP cron hook
    public const CRON_HOOK = 'amai_pin_token_refresh';

    // Token lifetimes (Pinterest: access 30 days, refresh 1 year)
    private const ACCESS_TOKEN_TTL  = 2592000;  // 30 days
    private const REFRESH_TOKEN_TTL = 31536000; // 365 days

    // Refresh when fewer than this many hours remain on the access token
    private const REFRESH_THRESHOLD_HOURS = 48;

    // Avatar attachment option key
    private const AVATAR_ATTACHMENT_OPTION = 'amai_pin_avatar_attachment_id';

    // OAuth callback query param
    private const CALLBACK_PARAM = 'amai_pin_callback';

    // --------------------------------------------------
    // Public: build the OAuth authorization URL (PKCE)
    // --------------------------------------------------
    public static function getAuthUrl(): string
    {
        if (self::getClientId() === '' || self::getClientSecret() === '') {
            return '';
        }

        // Generate PKCE code verifier (43-128 chars, URL-safe)
        $verifier = rtrim(strtr(base64_encode(random_bytes(48)), '+/', '-_'), '=');
        update_option(self::VERIFIER_OPTION, $verifier, false);

        // Code challenge = BASE64URL(SHA256(verifier))
        $challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');

        $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'                 => implode(',', self::SCOPES),
            'state'                 => $state,
            'code_challenge'        => $challenge,
            'code_challenge_method' => 'S256',
        ];

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

    // --------------------------------------------------
    // Public: callback URL for Pinterest OAuth redirect
    // --------------------------------------------------
    public static function getCallbackUrl(): string
    {
        return home_url('/pinterest/callback');
    }

    // --------------------------------------------------
    // Public: handle the OAuth callback
    // --------------------------------------------------
    public static function handleCallback(): void
    {
        $code  = sanitize_text_field((string) ($_GET['code'] ?? ''));
        $state = sanitize_text_field((string) ($_GET['state'] ?? ''));
        $error = sanitize_text_field((string) ($_GET['error'] ?? ''));

        if ($error !== '') {
            self::redirectWithError('pinterest_auth_denied');
            return;
        }

        if ($code === '' || $state === '') {
            self::redirectWithError('pinterest_missing_params');
            return;
        }

        $stored_state = (string) get_option(self::STATE_OPTION, '');
        delete_option(self::STATE_OPTION);

        if (!hash_equals($stored_state, $state)) {
            self::redirectWithError('pinterest_invalid_state');
            return;
        }

        $verifier = (string) get_option(self::VERIFIER_OPTION, '');
        delete_option(self::VERIFIER_OPTION);

        if ($verifier === '') {
            self::redirectWithError('pinterest_missing_verifier');
            return;
        }

        $token_data = self::exchangeCode($code, $verifier);

        if (isset($token_data['error'])) {
            self::redirectWithError('pinterest_token_exchange_failed');
            return;
        }

        $access_token  = (string) ($token_data['access_token'] ?? '');
        $refresh_token = (string) ($token_data['refresh_token'] ?? '');

        if ($access_token === '') {
            self::redirectWithError('pinterest_empty_token');
            return;
        }

        $user_info = self::fetchUserInfo($access_token);

        if (isset($user_info['error'])) {
            self::redirectWithError('pinterest_user_fetch_failed');
            return;
        }

        $pinterest_user_id = (string) ($user_info['id'] ?? '');
        $username          = (string) ($user_info['username'] ?? '');
        $display_name      = (string) ($user_info['display_name'] ?? $username);
        $avatar_url        = (string) ($user_info['profile_image'] ?? '');
        $follower_count    = (int) ($user_info['follower_count'] ?? 0);

        if ($pinterest_user_id === '') {
            self::redirectWithError('pinterest_empty_user_id');
            return;
        }

        self::saveConnection(
            $pinterest_user_id,
            $username,
            $display_name,
            $avatar_url,
            $follower_count,
            $access_token,
            $refresh_token
        );

        self::scheduleCron();

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

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

        $table = $wpdb->prefix . 'amai_pinterest_connection';
        $row   = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM `{$table}` WHERE tenant_id = %s AND status = 'active' LIMIT 1",
                'default'
            ),
            ARRAY_A
        );

        return is_array($row) ? $row : null;
    }

    // --------------------------------------------------
    // Public: return true if a connection exists
    // --------------------------------------------------
    public static function isConnected(): bool
    {
        return self::getConnection() !== null;
    }

    // --------------------------------------------------
    // Public: return the decrypted access token or null
    // --------------------------------------------------
    public static function getAccessToken(): ?string
    {
        $connection = self::getConnection();
        if ($connection === null) {
            return null;
        }

        $encrypted = (string) ($connection['access_token_enc'] ?? '');
        if ($encrypted === '') {
            return null;
        }

        $decrypted = KeyVault::decrypt($encrypted);

        return ($decrypted !== '' && $decrypted !== false) ? $decrypted : null;
    }

    // --------------------------------------------------
    // Public: disconnect -- revoke and mark revoked
    // --------------------------------------------------
    public static function disconnect(): bool
    {
        global $wpdb;

        $token = self::getAccessToken();
        if ($token !== null) {
            wp_remote_post(
                self::REVOKE_URL,
                [
                    'timeout'   => 10,
                    'sslverify' => true,
                    'headers'   => [
                        'Content-Type'  => 'application/json',
                        'Authorization' => 'Bearer ' . $token,
                    ],
                    'body' => wp_json_encode(['token' => $token]),
                ]
            );
        }

        self::deleteAvatarAttachment();
        self::unscheduleCron();

        $table  = $wpdb->prefix . 'amai_pinterest_connection';
        $result = $wpdb->update(
            $table,
            ['status' => 'revoked'],
            ['tenant_id' => 'default']
        );

        return $result !== false;
    }

    // --------------------------------------------------
    // Public: refresh access token if near expiry
    // --------------------------------------------------
    public static function maybeRefreshToken(): bool
    {
        $connection = self::getConnection();
        if ($connection === null) {
            return true;
        }

        $expires_at = $connection['token_expires_at'] ?? null;
        if (!is_string($expires_at) || $expires_at === '') {
            return self::refreshAccessToken();
        }

        $expires_ts   = strtotime($expires_at);
        $threshold_ts = time() + (self::REFRESH_THRESHOLD_HOURS * HOUR_IN_SECONDS);

        if ($expires_ts === false || $expires_ts > $threshold_ts) {
            return true;
        }

        return self::refreshAccessToken();
    }

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

        $connection = self::getConnection();
        if ($connection === null) {
            return false;
        }

        $refresh_enc = (string) ($connection['refresh_token_enc'] ?? '');
        if ($refresh_enc === '') {
            return false;
        }

        $refresh_token = KeyVault::decrypt($refresh_enc);
        if ($refresh_token === '' || $refresh_token === false) {
            return false;
        }

        $result = self::exchangeRefreshToken($refresh_token);

        if (isset($result['error'])) {
            $wpdb->update(
                $wpdb->prefix . 'amai_pinterest_connection',
                ['status' => 'expired'],
                ['tenant_id' => 'default']
            );
            return false;
        }

        $new_access  = (string) ($result['access_token'] ?? '');
        $new_refresh = (string) ($result['refresh_token'] ?? '');

        if ($new_access === '') {
            return false;
        }

        $access_enc = KeyVault::encrypt($new_access);
        if ($access_enc === '' || $access_enc === false) {
            return false;
        }

        $update_data = [
            'access_token_enc' => $access_enc,
            'token_expires_at' => gmdate('Y-m-d H:i:s', time() + self::ACCESS_TOKEN_TTL),
        ];

        if ($new_refresh !== '') {
            $refresh_enc_new = KeyVault::encrypt($new_refresh);
            if ($refresh_enc_new !== '' && $refresh_enc_new !== false) {
                $update_data['refresh_token_enc']  = $refresh_enc_new;
                $update_data['refresh_expires_at'] = gmdate('Y-m-d H:i:s', time() + self::REFRESH_TOKEN_TTL);
            }
        }

        $wpdb->update(
            $wpdb->prefix . 'amai_pinterest_connection',
            $update_data,
            ['tenant_id' => 'default']
        );

        return true;
    }

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

    // --------------------------------------------------
    // Public: unschedule the token refresh cron
    // --------------------------------------------------
    public static function unscheduleCron(): void
    {
        $timestamp = wp_next_scheduled(self::CRON_HOOK);
        if ($timestamp !== false) {
            wp_unschedule_event($timestamp, self::CRON_HOOK);
        }
    }

    // --------------------------------------------------
    // Private: exchange auth code for tokens (PKCE)
    // --------------------------------------------------
    private static function exchangeCode(string $code, string $verifier): array
    {
        $credentials = base64_encode(self::getClientId() . ':' . self::getClientSecret());

        $response = wp_remote_post(
            self::TOKEN_URL,
            [
                'timeout'   => 15,
                'sslverify' => true,
                'headers'   => [
                    'Authorization' => 'Basic ' . $credentials,
                    'Content-Type'  => 'application/x-www-form-urlencoded',
                ],
                'body' => http_build_query([
                    'grant_type'    => 'authorization_code',
                    'code'          => $code,
                    'redirect_uri'  => self::getCallbackUrl(),
                    'code_verifier' => $verifier,
                ]),
            ]
        );

        return self::parseApiResponse($response);
    }

    // --------------------------------------------------
    // Private: exchange refresh token for new access token
    // --------------------------------------------------
    private static function exchangeRefreshToken(string $refresh_token): array
    {
        $credentials = base64_encode(self::getClientId() . ':' . self::getClientSecret());

        $response = wp_remote_post(
            self::TOKEN_URL,
            [
                'timeout'   => 15,
                'sslverify' => true,
                'headers'   => [
                    'Authorization' => 'Basic ' . $credentials,
                    'Content-Type'  => 'application/x-www-form-urlencoded',
                ],
                'body' => http_build_query([
                    'grant_type'    => 'refresh_token',
                    'refresh_token' => $refresh_token,
                ]),
            ]
        );

        return self::parseApiResponse($response);
    }

    // --------------------------------------------------
    // Private: fetch the authenticated user profile
    // --------------------------------------------------
    private static function fetchUserInfo(string $access_token): array
    {
        $response = wp_remote_get(
            self::USER_URL,
            [
                'timeout'   => 15,
                'sslverify' => true,
                'headers'   => [
                    'Authorization' => 'Bearer ' . $access_token,
                ],
            ]
        );

        return self::parseApiResponse($response);
    }

    // --------------------------------------------------
    // Private: persist the connection to the database
    // Uses raw SQL because avatar URLs may contain %
    // --------------------------------------------------
    private static function saveConnection(
        string $pinterest_user_id,
        string $username,
        string $display_name,
        string $avatar_url,
        int    $follower_count,
        string $access_token,
        string $refresh_token
    ): void {
        global $wpdb;

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

        $access_enc  = KeyVault::encrypt($access_token);
        $refresh_enc = ($refresh_token !== '') ? KeyVault::encrypt($refresh_token) : '';

        $local_avatar  = self::downloadAvatar($avatar_url);
        $stored_avatar = ($local_avatar !== null) ? $local_avatar : null;

        $token_expires_at   = gmdate('Y-m-d H:i:s', time() + self::ACCESS_TOKEN_TTL);
        $refresh_expires_at = gmdate('Y-m-d H:i:s', time() + self::REFRESH_TOKEN_TTL);
        $now                = current_time('mysql', true);

        $exists = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM `{$table}` WHERE tenant_id = %s",
                'default'
            )
        );

        if ($exists > 0) {
            $wpdb->query(
                $wpdb->prepare(
                    "UPDATE `{$table}` SET
                        pinterest_user_id = %s, username = %s, display_name = %s,
                        avatar_url = %s, follower_count = %d,
                        access_token_enc = %s, refresh_token_enc = %s,
                        token_expires_at = %s, refresh_expires_at = %s,
                        connected_at = %s, last_verified_at = %s,
                        status = 'active'
                    WHERE tenant_id = %s",
                    $pinterest_user_id,
                    $username,
                    $display_name,
                    $stored_avatar,
                    $follower_count,
                    $access_enc,
                    $refresh_enc,
                    $token_expires_at,
                    $refresh_expires_at,
                    $now,
                    $now,
                    'default'
                )
            );
        } else {
            $wpdb->query(
                $wpdb->prepare(
                    "INSERT INTO `{$table}` (
                        tenant_id, pinterest_user_id, username, display_name,
                        avatar_url, follower_count,
                        access_token_enc, refresh_token_enc,
                        token_expires_at, refresh_expires_at,
                        connected_at, last_verified_at, status
                    ) VALUES (%s, %s, %s, %s, %s, %d, %s, %s, %s, %s, %s, %s, 'active')",
                    'default',
                    $pinterest_user_id,
                    $username,
                    $display_name,
                    $stored_avatar,
                    $follower_count,
                    $access_enc,
                    $refresh_enc,
                    $token_expires_at,
                    $refresh_expires_at,
                    $now,
                    $now
                )
            );
        }
    }

    // --------------------------------------------------
    // Private: download avatar to WP media library
    // --------------------------------------------------
    private static function downloadAvatar(string $url): ?string
    {
        if ($url === '') {
            return null;
        }

        self::deleteAvatarAttachment();

        require_once ABSPATH . 'wp-admin/includes/media.php';
        require_once ABSPATH . 'wp-admin/includes/file.php';
        require_once ABSPATH . 'wp-admin/includes/image.php';

        $tmp = download_url($url);
        if (is_wp_error($tmp)) {
            return null;
        }

        $file_array = [
            'name'     => 'pinterest-avatar-' . time() . '.jpg',
            'tmp_name' => $tmp,
        ];

        $attachment_id = media_handle_sideload($file_array, 0, 'Pinterest avatar');

        if (is_wp_error($attachment_id)) {
            if (is_file($tmp)) {
                wp_delete_file($tmp);
            }
            return null;
        }

        update_option(self::AVATAR_ATTACHMENT_OPTION, $attachment_id, false);

        return (string) wp_get_attachment_url($attachment_id);
    }

    // --------------------------------------------------
    // Private: delete stored avatar from media library
    // --------------------------------------------------
    private static function deleteAvatarAttachment(): void
    {
        $attachment_id = (int) get_option(self::AVATAR_ATTACHMENT_OPTION, 0);
        if ($attachment_id <= 0) {
            return;
        }

        wp_delete_attachment($attachment_id, true);
        delete_option(self::AVATAR_ATTACHMENT_OPTION);
    }

    // --------------------------------------------------
    // Private: parse a Pinterest API response
    // --------------------------------------------------
    private static function parseApiResponse(mixed $response): array
    {
        if (is_wp_error($response)) {
            return ['error' => $response->get_error_message()];
        }

        $code = (int) wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);

        if (!is_array($data)) {
            return ['error' => 'invalid_json_response'];
        }

        if (isset($data['code']) && $code >= 400) {
            return ['error' => (string) ($data['message'] ?? 'api_error_' . $code)];
        }

        if ($code < 200 || $code >= 300) {
            return ['error' => 'http_' . $code];
        }

        return $data;
    }

    // --------------------------------------------------
    // Private: redirect to social page with error
    // --------------------------------------------------
    private static function redirectWithError(string $error_code): void
    {
        set_transient('amai_pinterest_auth_error', $error_code, 60);
        wp_safe_redirect(
            admin_url('admin.php?page=automarketer-ai-social&channel=pinterest&auth_error=1')
        );
        exit;
    }

    // --------------------------------------------------
    // Private: get Pinterest App ID from wp-config.php
    // --------------------------------------------------
    private static function getClientId(): string
    {
        return defined('AMAI_PIN_CLIENT_ID') ? (string) AMAI_PIN_CLIENT_ID : '';
    }

    // --------------------------------------------------
    // Private: get Pinterest App Secret from wp-config.php
    // --------------------------------------------------
    private static function getClientSecret(): string
    {
        return defined('AMAI_PIN_CLIENT_SECRET') ? (string) AMAI_PIN_CLIENT_SECRET : '';
    }
}