<?php

/**
 * SocialPromoter Facebook Connector
 *
 * Handles OAuth 2.0 authentication and posting to Facebook Pages via the
 * Graph API.
 *
 * @package PublisherKits_SocialPromoter
 */
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
/**
 * Class PKITS_SP_Connector_Facebook
 */
class PKITS_SP_Connector_Facebook implements PKITS_SP_Connector_Interface {
    // ----------------------------------------------------------------
    // Interface metadata methods
    // ----------------------------------------------------------------
    /** @return string */
    public function get_slug() {
        return 'facebook';
    }

    /** @return string */
    public function get_label() {
        return 'Facebook';
    }

    /** @return string */
    public function get_badge() {
        return 'FB';
    }

    /** @return string */
    public function get_tier() {
        return 'free';
    }

    /** @return int */
    public function get_char_limit() {
        return 63206;
    }

    /** @return string */
    public function get_auth_type() {
        return 'oauth2';
    }

    /**
     * @return array
     */
    public function get_credential_fields() {
        /**
         * Credential fields for the Facebook connection form. The premium build
         * appends its engagement-stats opt-in here; the free form is app id and
         * secret only.
         *
         * @param array $fields Field definitions.
         */
        return apply_filters( 'pkits_sp_facebook_credential_fields', array(array(
            'id'    => 'app_id',
            'label' => 'App ID',
            'type'  => 'text',
        ), array(
            'id'    => 'app_secret',
            'label' => 'App Secret',
            'type'  => 'password',
        )) );
    }

    /** @return string */
    public function get_accounts_description() {
        return 'Connect your Facebook developer app to post to Facebook Pages. Create a free app at <a href="https://developers.facebook.com" target="_blank" rel="noopener noreferrer">developers.facebook.com</a>.';
    }

    /**
     * Facebook Graph API version.
     *
     * @var string
     */
    const GRAPH_VERSION = 'v21.0';

    /**
     * Facebook Graph API base URL.
     *
     * @var string
     */
    const GRAPH_BASE = 'https://graph.facebook.com/';

    /**
     * Build the Facebook OAuth authorization URL.
     *
     * @param string $app_id       Facebook App ID.
     * @param string $redirect_uri Callback URL registered in the Facebook app.
     * @param string $state        CSRF state token.
     * @return string Authorization URL to redirect the user to.
     */
    public function get_auth_url( $app_id, $redirect_uri, $state ) {
        // Base scopes needed for posting and reading likes/comments. read_insights
        // (page-post impressions/reach/clicks) is NOT included by default: an app
        // that has not added read_insights would fail the whole OAuth dialog with
        // "Invalid Scopes". Sites whose Facebook app HAS added read_insights can
        // opt in via this filter (return the string with ',read_insights' appended)
        // to light up impressions on the pages they administer.
        $scope = apply_filters( 'pkits_sp_facebook_scopes', 'pages_manage_posts,pages_read_engagement,pages_show_list' );
        $params = array(
            'client_id'     => $app_id,
            'redirect_uri'  => $redirect_uri,
            'scope'         => $scope,
            'state'         => $state,
            'response_type' => 'code',
        );
        return 'https://www.facebook.com/' . self::GRAPH_VERSION . '/dialog/oauth?' . http_build_query( $params );
    }

    /**
     * Exchange an authorization code for a long-lived page access token.
     *
     * @param string $app_id       Facebook App ID.
     * @param string $app_secret   Facebook App Secret.
     * @param string $code         Authorization code from the callback.
     * @param string $redirect_uri Callback URL (must match the one used in get_auth_url).
     * @return array|WP_Error Array of page account data objects, or WP_Error.
     */
    public function get_accounts(
        $app_id,
        $app_secret,
        $code,
        $redirect_uri
    ) {
        // Step 1: exchange code for short-lived user token.
        $token_url = self::GRAPH_BASE . self::GRAPH_VERSION . '/oauth/access_token';
        $token_params = array(
            'client_id'     => $app_id,
            'client_secret' => $app_secret,
            'redirect_uri'  => $redirect_uri,
            'code'          => $code,
        );
        $response = wp_remote_get( $token_url . '?' . http_build_query( $token_params ), array(
            'timeout' => 20,
        ) );
        if ( is_wp_error( $response ) ) {
            return $response;
        }
        $data = json_decode( wp_remote_retrieve_body( $response ), true );
        if ( empty( $data['access_token'] ) ) {
            $msg = ( isset( $data['error']['message'] ) ? $data['error']['message'] : 'Unknown Facebook OAuth error.' );
            return new WP_Error('fb_token_exchange_failed', esc_html( $msg ));
        }
        $user_token = $data['access_token'];
        // Step 2: exchange for long-lived user token.
        $ll_params = array(
            'grant_type'        => 'fb_exchange_token',
            'client_id'         => $app_id,
            'client_secret'     => $app_secret,
            'fb_exchange_token' => $user_token,
        );
        $ll_response = wp_remote_get( self::GRAPH_BASE . self::GRAPH_VERSION . '/oauth/access_token?' . http_build_query( $ll_params ), array(
            'timeout' => 20,
        ) );
        // The long-lived exchange must succeed. If it is silently skipped, the
        // page tokens inherit the short (~2h) expiry and the account shows
        // "connected" but stops working within hours, with no clue why.
        if ( is_wp_error( $ll_response ) ) {
            return $ll_response;
        }
        $ll_data = json_decode( wp_remote_retrieve_body( $ll_response ), true );
        if ( empty( $ll_data['access_token'] ) ) {
            $msg = ( isset( $ll_data['error']['message'] ) ? $ll_data['error']['message'] : 'Could not obtain a long-lived Facebook token.' );
            return new WP_Error('fb_ll_exchange_failed', esc_html( $msg ));
        }
        $user_token = $ll_data['access_token'];
        // Step 3: get the pages this user manages.
        $pages_response = wp_remote_get( self::GRAPH_BASE . self::GRAPH_VERSION . '/me/accounts?access_token=' . rawurlencode( $user_token ) . '&fields=id,name,access_token,category', array(
            'timeout' => 20,
        ) );
        if ( is_wp_error( $pages_response ) ) {
            return $pages_response;
        }
        $pages_data = json_decode( wp_remote_retrieve_body( $pages_response ), true );
        if ( empty( $pages_data['data'] ) ) {
            return new WP_Error('fb_no_pages', 'No Facebook Pages found. Make sure the connected account manages at least one Page.');
        }
        $accounts = array();
        foreach ( $pages_data['data'] as $page ) {
            if ( empty( $page['id'] ) || empty( $page['access_token'] ) ) {
                continue;
            }
            $accounts[] = array(
                'platform'     => 'facebook',
                'account_id'   => sanitize_text_field( $page['id'] ),
                'label'        => sanitize_text_field( $page['name'] ?? $page['id'] ),
                'category'     => sanitize_text_field( $page['category'] ?? '' ),
                'access_token' => $page['access_token'],
                'app_id'       => $app_id,
                'connected_at' => gmdate( 'Y-m-d H:i:s' ),
            );
        }
        return $accounts;
    }

    /**
     * Post a message to a Facebook Page, optionally with an image.
     *
     * When image_data is provided, posts to /photos endpoint for a
     * proper image post. Otherwise posts to /feed as a link or text post.
     *
     * @param array      $account    Account data (with decrypted access_token).
     * @param string     $message    Post message text.
     * @param string     $link       Optional URL to include as a link attachment.
     * @param array|null $image_data Optional image data from PKITS_SP_Image_Handler.
     * @return array|WP_Error Array with 'platform_post_id', or WP_Error.
     */
    public function post(
        array $account,
        $message,
        $link = '',
        $image_data = null
    ) {
        // Image post: use /photos endpoint for proper image attachment.
        if ( !empty( $image_data ) ) {
            $result = $this->post_with_image( $account, $message, $image_data );
            PKITS_SP_Image_Handler::cleanup_temp( $image_data );
            return $result;
        }
        $url = self::GRAPH_BASE . self::GRAPH_VERSION . '/' . $account['account_id'] . '/feed';
        $body = array(
            'message'      => $message,
            'access_token' => $account['access_token'],
        );
        if ( !empty( $link ) ) {
            $body['link'] = esc_url_raw( $link );
        }
        $response = wp_remote_post( $url, array(
            'body'    => $body,
            'timeout' => 30,
        ) );
        if ( is_wp_error( $response ) ) {
            return $response;
        }
        $code = wp_remote_retrieve_response_code( $response );
        $data = json_decode( wp_remote_retrieve_body( $response ), true );
        if ( $code < 200 || $code >= 300 || empty( $data['id'] ) ) {
            $msg = ( isset( $data['error']['message'] ) ? $data['error']['message'] : wp_remote_retrieve_body( $response ) );
            return new WP_Error('fb_post_failed', 'Facebook post failed (' . $code . '): ' . esc_html( $msg ));
        }
        return array(
            'platform_post_id' => sanitize_text_field( $data['id'] ),
        );
    }

    /**
     * Post an image to a Facebook Page as a proper feed story.
     *
     * Two steps, because a bare publish to /photos lands the image in the
     * Page's Photos album but does NOT reliably create a visible timeline
     * story — visitors then see the image only under Photos, never in the
     * feed. Instead:
     *   1. Upload the photo UNPUBLISHED (published=false) → get its media id.
     *   2. Create a /feed post with that media attached (attached_media).
     * The result is a first-class feed post, visible to everyone, and its id
     * is already the clean {page}_{post} id engagement stats need.
     *
     * The link (if any) is carried in the message text rather than as a
     * separate link param: a feed post cannot show both an attached photo and
     * a link-preview card, and the branded image is the point here.
     *
     * @param array  $account    Account data.
     * @param string $message    Post text (already includes the tracked URL).
     * @param array  $image_data Image data from PKITS_SP_Image_Handler.
     * @return array|WP_Error
     */
    private function post_with_image( array $account, $message, array $image_data ) {
        // ── Step 1: upload the photo, unpublished ──
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
        $image_binary = file_get_contents( $image_data['local_path'] );
        if ( false === $image_binary ) {
            return new WP_Error('fb_image_read_failed', 'Could not read image file.');
        }
        $boundary = wp_generate_password( 24, false );
        $body = "--{$boundary}\r\n";
        $body .= 'Content-Disposition: form-data; name="published"' . "\r\n\r\n";
        $body .= "false\r\n";
        $body .= "--{$boundary}\r\n";
        $body .= 'Content-Disposition: form-data; name="source"; filename="image.' . PKITS_SP_Image_Handler::mime_to_extension( $image_data['mime_type'] ) . '"' . "\r\n";
        $body .= 'Content-Type: ' . $image_data['mime_type'] . "\r\n\r\n";
        $body .= $image_binary . "\r\n";
        $body .= "--{$boundary}\r\n";
        $body .= 'Content-Disposition: form-data; name="access_token"' . "\r\n\r\n";
        $body .= $account['access_token'] . "\r\n";
        $body .= "--{$boundary}--\r\n";
        $photo_response = wp_remote_post( self::GRAPH_BASE . self::GRAPH_VERSION . '/' . $account['account_id'] . '/photos', array(
            'headers' => array(
                'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
            ),
            'body'    => $body,
            'timeout' => 60,
        ) );
        if ( is_wp_error( $photo_response ) ) {
            return $photo_response;
        }
        $photo_code = wp_remote_retrieve_response_code( $photo_response );
        $photo_data = json_decode( wp_remote_retrieve_body( $photo_response ), true );
        if ( $photo_code < 200 || $photo_code >= 300 || empty( $photo_data['id'] ) ) {
            $msg = ( isset( $photo_data['error']['message'] ) ? $photo_data['error']['message'] : wp_remote_retrieve_body( $photo_response ) );
            return new WP_Error('fb_image_upload_failed', 'Facebook photo upload failed (' . $photo_code . '): ' . esc_html( $msg ));
        }
        $photo_id = sanitize_text_field( $photo_data['id'] );
        // ── Step 2: create the feed post with the photo attached ──
        $feed_response = wp_remote_post( self::GRAPH_BASE . self::GRAPH_VERSION . '/' . $account['account_id'] . '/feed', array(
            'body'    => array(
                'message'           => $message,
                'attached_media[0]' => wp_json_encode( array(
                    'media_fbid' => $photo_id,
                ) ),
                'access_token'      => $account['access_token'],
            ),
            'timeout' => 30,
        ) );
        if ( is_wp_error( $feed_response ) ) {
            return $feed_response;
        }
        $feed_code = wp_remote_retrieve_response_code( $feed_response );
        $feed_data = json_decode( wp_remote_retrieve_body( $feed_response ), true );
        if ( $feed_code < 200 || $feed_code >= 300 || empty( $feed_data['id'] ) ) {
            $msg = ( isset( $feed_data['error']['message'] ) ? $feed_data['error']['message'] : wp_remote_retrieve_body( $feed_response ) );
            return new WP_Error('fb_image_post_failed', 'Facebook feed post failed (' . $feed_code . '): ' . esc_html( $msg ));
        }
        // /feed returns the {page}_{post} id directly.
        return array(
            'platform_post_id' => sanitize_text_field( $feed_data['id'] ),
        );
    }

}
