<?php
if (!defined('ABSPATH')) exit;

/**
 * Google Blogger integration (OAuth2) – basic post publishing.
 *
 * What it does:
 * - OAuth2 with offline access (refresh token)
 * - Lists blogs for the connected Google account
 * - Publishes a post (HTML content) to the selected blog
 *
 * Notes:
 * - Blogger API v3 does not offer a direct upload endpoint. To support images from the editor,
 *   the base64 image is uploaded to WordPress (uploads) and the public URL is embedded in the post HTML.
 * - Requires enabling the Blogger API in Google Cloud and adding the redirect URI.
 */
class Calliope_SP_Blogger {

    private $auth_base = 'https://accounts.google.com/o/oauth2/v2/auth';
    private $token_url = 'https://oauth2.googleapis.com/token';
    private $api_base  = 'https://www.googleapis.com/blogger/v3';

    public function get_client_id() {
        return (string) get_option('calliope_sp_blogger_client_id', '');
    }

    public function get_client_secret() {
        return (string) get_option('calliope_sp_blogger_client_secret', '');
    }

    public function get_access_token() {
        return (string) get_option('calliope_sp_blogger_access_token', '');
    }

    public function get_refresh_token() {
        return (string) get_option('calliope_sp_blogger_refresh_token', '');
    }

    public function get_expires_at() {
        return (int) get_option('calliope_sp_blogger_expires_at', 0);
    }

    public function get_blog_id() {
        return (string) get_option('calliope_sp_blogger_blog_id', '');
    }

    public function get_redirect_uri() {
        // Clean public callback URL (recommended for Google OAuth)
        return rest_url('calliope-social-publisher/v1/blogger/callback');
    }

    public function build_auth_url($state) {
        $client_id = $this->get_client_id();
        $redirect  = $this->get_redirect_uri();

        $scopes = [
            'https://www.googleapis.com/auth/blogger',
            'https://www.googleapis.com/auth/blogger.readonly',
        ];

        return add_query_arg([
            'client_id'     => $client_id,
            'redirect_uri'  => $redirect,
            'response_type' => 'code',
            'access_type'   => 'offline',
            'prompt'        => 'consent',
            'include_granted_scopes' => 'true',
            'scope'         => implode(' ', $scopes),
            'state'         => $state,
        ], $this->auth_base);
    }

    /**
     * Ensure we have a valid access token (refresh if needed).
     */
    public function ensure_access_token() {
        $token = $this->get_access_token();
        $exp   = $this->get_expires_at();

        // If token exists and not expired, keep it.
        if ($token && $exp && $exp > (time() + 30)) {
            return $token;
        }

        // If no refresh token, we cannot refresh.
        $refresh = $this->get_refresh_token();
        if (!$refresh) {
            return new WP_Error('cfg', 'Blogger: missing Refresh Token. Connect with “Connect with Blogger”.');
        }

        $client_id = $this->get_client_id();
        $secret    = $this->get_client_secret();
        if (!$client_id || !$secret) {
            return new WP_Error('cfg', 'Blogger: missing Client ID / Client Secret.');
        }

        $res = wp_remote_post($this->token_url, [
            'timeout' => 45,
            'headers' => ['Accept' => 'application/json'],
            'body'    => [
                'client_id'     => $client_id,
                'client_secret' => $secret,
                'refresh_token' => $refresh,
                'grant_type'    => 'refresh_token',
            ],
        ]);

        if (is_wp_error($res)) return $res;

        $http = (int) wp_remote_retrieve_response_code($res);
        $raw  = (string) wp_remote_retrieve_body($res);
        $json = json_decode($raw, true);

        if ($http < 200 || $http >= 300 || !is_array($json) || empty($json['access_token'])) {
            if (is_array($json) && (($json['error'] ?? '') === 'invalid_client' || stripos((string) ($json['error_description'] ?? ''), 'client secret is invalid') !== false)) {
                return new WP_Error(
                    'blogger_invalid_client',
                    'Blogger rejected the saved OAuth client credentials. Update the Client Secret in Google Cloud and reconnect Blogger.'
                );
            }
            $msg = is_array($json) ? json_encode($json) : $raw;
            return new WP_Error('oauth', 'Blogger refresh: HTTP ' . $http . ' | ' . $msg);
        }

        update_option('calliope_sp_blogger_access_token', sanitize_text_field($json['access_token']));
        if (!empty($json['expires_in'])) {
            update_option('calliope_sp_blogger_expires_at', time() + (int) $json['expires_in']);
        } else {
            update_option('calliope_sp_blogger_expires_at', time() + 3500);
        }

        return (string) $json['access_token'];
    }

    /**
     * Exchange authorization code for tokens.
     */
    public function exchange_code($code) {
        $client_id = $this->get_client_id();
        $secret    = $this->get_client_secret();
        $redirect  = $this->get_redirect_uri();

        if (!$client_id || !$secret) {
            return new WP_Error('cfg', 'Blogger: missing Client ID / Client Secret.');
        }

        $res = wp_remote_post($this->token_url, [
            'timeout' => 45,
            'headers' => ['Accept' => 'application/json'],
            'body'    => [
                'code'          => $code,
                'client_id'     => $client_id,
                'client_secret' => $secret,
                'redirect_uri'  => $redirect,
                'grant_type'    => 'authorization_code',
            ],
        ]);

        if (is_wp_error($res)) return $res;

        $http = (int) wp_remote_retrieve_response_code($res);
        $raw  = (string) wp_remote_retrieve_body($res);
        $json = json_decode($raw, true);

        if ($http < 200 || $http >= 300 || !is_array($json) || empty($json['access_token'])) {
            $msg = is_array($json) ? json_encode($json) : $raw;
            return new WP_Error('oauth', 'Blogger OAuth: HTTP ' . $http . ' | ' . $msg);
        }

        update_option('calliope_sp_blogger_access_token', sanitize_text_field($json['access_token']));
        if (!empty($json['expires_in'])) {
            update_option('calliope_sp_blogger_expires_at', time() + (int) $json['expires_in']);
        } else {
            update_option('calliope_sp_blogger_expires_at', time() + 3500);
        }

        if (!empty($json['refresh_token'])) {
            update_option('calliope_sp_blogger_refresh_token', sanitize_text_field($json['refresh_token']));
        }

        return true;
    }

    /**
     * List blogs for current user.
     */
    public function fetch_blogs() {
        $token = $this->ensure_access_token();
        if (is_wp_error($token)) return $token;

        $url = $this->api_base . '/users/self/blogs';
        $res = wp_remote_get($url, [
            'timeout' => 25,
            'headers' => [
                'Authorization' => 'Bearer ' . $token,
                'Accept'        => 'application/json',
            ],
        ]);
        if (is_wp_error($res)) return $res;

        $http = (int) wp_remote_retrieve_response_code($res);
        $raw  = (string) wp_remote_retrieve_body($res);
        $json = json_decode($raw, true);

        if ($http < 200 || $http >= 300) {
            $msg = is_array($json) ? json_encode($json) : $raw;
            return new WP_Error('api', 'Blogger blogs: HTTP ' . $http . ' | ' . $msg);
        }
        if (!is_array($json)) {
            return new WP_Error('api', 'Blogger blogs: invalid response.');
        }

        $items = $json['items'] ?? [];
        if (!is_array($items)) $items = [];

        // Normalize for UI select.
        $out = [];
        foreach ($items as $b) {
            if (!is_array($b)) continue;
            $id = sanitize_text_field((string)($b['id'] ?? ''));
            if (!$id) continue;
            $name = sanitize_text_field((string)($b['name'] ?? ''));
            $urlb = esc_url_raw((string)($b['url'] ?? ''));
            $out[] = [
                'id'   => $id,
                'name' => $name ?: $id,
                'url'  => $urlb,
            ];
        }

        update_option('calliope_sp_blogger_blogs_last', $out);
        if (!$this->get_blog_id() && !empty($out[0]['id'])) {
            update_option('calliope_sp_blogger_blog_id', $out[0]['id']);
        }

        return $out;
    }

    public function test_connection() {
        // Must have refresh token to be reliable.
        if (!$this->get_refresh_token()) {
            return new WP_Error('cfg', 'Blogger: you are not connected yet. Use “Connect with Blogger”.');
        }
        $blogs = $this->fetch_blogs();
        if (is_wp_error($blogs)) return $blogs;
        if (empty($blogs)) {
            return new WP_Error('blogs', 'Blogger: no blogs were found in this account.');
        }
        return true;
    }

    public function publish($data) {
        $blog_id = $this->get_blog_id();
        if (!$blog_id) {
            // Attempt to fetch blogs and pick first.
            $blogs = $this->fetch_blogs();
            if (is_wp_error($blogs)) return $blogs;
            $blog_id = $this->get_blog_id();
        }
        if (!$blog_id) {
            return new WP_Error('cfg', 'Blogger: please select a Blog.');
        }

        $temp_image = null;

        // If a base64 image is provided from Gutenberg, convert it to a managed uploads URL.
        // On failed publishes we clean it up immediately. On success we keep it because the
        // published Blogger post references that public image URL directly.
        if (!empty($data['image_b64']) && empty($data['image_url'])) {
            $temp_image = $this->image_url_from_b64((string) $data['image_b64']);
            if (is_wp_error($temp_image)) return $temp_image;
            $data['image_url'] = $temp_image['url'];
        }

        $token = $this->ensure_access_token();
        if (is_wp_error($token)) return $token;

        $title = !empty($data['title']) ? sanitize_text_field((string)$data['title']) : '';
        $html  = $this->format_html($data);

        $payload = [
            'kind'    => 'blogger#post',
            'title'   => $title,
            'content' => $html,
        ];

        $url = $this->api_base . '/blogs/' . rawurlencode($blog_id) . '/posts/';
        $res = wp_remote_post($url, [
            'timeout' => 35,
            'headers' => [
                'Authorization' => 'Bearer ' . $token,
                'Content-Type'  => 'application/json; charset=utf-8',
                'Accept'        => 'application/json',
            ],
            'body' => wp_json_encode($payload),
        ]);
        if (is_wp_error($res)) {
            if ($temp_image && !empty($temp_image['path'])) {
                $this->delete_temp_image($temp_image['path']);
            }
            return $res;
        }

        $http = (int) wp_remote_retrieve_response_code($res);
        $raw  = (string) wp_remote_retrieve_body($res);
        if ($http < 200 || $http >= 300) {
            if ($temp_image && !empty($temp_image['path'])) {
                $this->delete_temp_image($temp_image['path']);
            }
            $json = json_decode($raw, true);
            $msg  = is_array($json) ? json_encode($json) : $raw;
            return new WP_Error('publish', 'Blogger publish: HTTP ' . $http . ' | ' . $msg);
        }

        return true;
    }

    private function format_html($data) {
        $parts = [];

        // Image (if present)
        if (!empty($data['image_url'])) {
            $img = esc_url((string)$data['image_url']);
            if ($img) {
                $alt = !empty($data['alt_text']) ? esc_attr((string)$data['alt_text']) : '';
                $parts[] = '<p><img src="' . $img . '" alt="' . $alt . '" /></p>';
            }
        }

        if (!empty($data['content'])) {
            $txt = trim((string)$data['content']);
            $txt = esc_html($txt);
            $txt = nl2br($txt);
            $parts[] = '<p>' . $txt . '</p>';
        }
        if (!empty($data['link'])) {
            $u = esc_url((string)$data['link']);
            if ($u) {
                $parts[] = '<p><a href="' . $u . '">' . $u . '</a></p>';
            }
        }
        if (!empty($data['hashtags'])) {
            $h = esc_html(trim((string)$data['hashtags']));
            $parts[] = '<p>' . $h . '</p>';
        }
        return implode("\n", array_filter($parts));
    }

    /**
     * Store the base64 image in a managed uploads folder and return its public URL.
     *
     * Blogger posts reference this external image URL directly, so successful publishes
     * intentionally keep the file in place. Failed publishes are cleaned up immediately.
     *
     * @param string $b64
     * @return array{url:string,path:string}|WP_Error
     */
    private function image_url_from_b64($b64) {
        $asset = class_exists('Calliope_SP_Media_Helper')
            ? Calliope_SP_Media_Helper::create_public_image_from_b64($b64, [
                'context'         => 'blogger',
                'filename_prefix' => 'calliope-social-publisher-blogger',
                'network'         => 'blogger',
                'max_attempts'    => 6,
            ])
            : new WP_Error('upload_failed', 'Blogger could not prepare a public image URL.');

        if (is_wp_error($asset)) {
            return $asset;
        }

        return [
            'url'  => esc_url_raw((string) $asset['url']),
            'path' => (string) $asset['local_path'],
        ];
    }

    /**
     * Remove a temporary image created for a failed Blogger publish.
     *
     * @param string $path
     * @return void
     */
    private function delete_temp_image($path) {
        $path = (string) $path;
        if ($path !== '' && file_exists($path) && is_file($path)) {
            wp_delete_file($path);
        }
    }
}

