<?php
declare(strict_types=1);
// ==================================================
// Nuclear Fancy Pants Edition™ -- NUCLEAR+++
// File: /home/ploi/automarketer-ai.com/public/wp-content/plugins/automarketer-ai/includes/class-comment-fetcher.php
// Description: Fetches comments and replies from active social channels and stores them in wp_amai_social_comments - NUCLEAR+++
// Last Edited: 2026-05-15
// ==================================================

namespace AutoMarketerAI;

defined('ABSPATH') || exit;

class CommentFetcher
{
    private const CRON_HOOK      = 'amai_comment_fetch';
    private const INTERVAL       = 'amai_five_minutes';
    private const GRAPH_BASE     = 'https://graph.facebook.com/v19.0';
    private const X_API_BASE     = 'https://api.x.com/2';

    // Supported channels. Adding a new channel: add its slug here,
    // add a case to isChannelActive() and run(), and implement a fetch method.
    private const SUPPORTED_CHANNELS = ['facebook', 'instagram', 'x', 'wordpress', 'woocommerce'];

    // --------------------------------------------------
    // Register cron hook. Reuses amai_five_minutes interval
    // already registered by BounceHandler -- no duplicate needed.
    // --------------------------------------------------
    public static function register(): void
    {
        add_action(self::CRON_HOOK, [self::class, 'run']);
        if (!wp_next_scheduled(self::CRON_HOOK)) {
            wp_schedule_event(time(), self::INTERVAL, self::CRON_HOOK);
        }
    }

    // --------------------------------------------------
    // Deregister cron hook on plugin deactivation.
    // --------------------------------------------------
    public static function deregister(): void
    {
        $timestamp = wp_next_scheduled(self::CRON_HOOK);
        if ($timestamp) {
            wp_unschedule_event($timestamp, self::CRON_HOOK);
        }
    }

    // --------------------------------------------------
    // Public: entry point called by cron every 5 minutes.
    // --------------------------------------------------
    public static function run(): void
    {
        foreach (self::SUPPORTED_CHANNELS as $channel) {
            if (!self::isChannelActive($channel)) {
                continue;
            }
            try {
                match ($channel) {
                    'facebook'   => self::fetchFacebook(),
                    'instagram'  => self::fetchInstagram(),
                    'x'          => self::fetchX(),
                    'wordpress'  => self::fetchWordPress(),
                    'woocommerce' => self::fetchWooCommerce(),
                };
            } catch (\Throwable $e) {
                error_log('[amai] CommentFetcher channel=' . $channel . ' error=' . $e->getMessage());
            }
        }
    }

    // --------------------------------------------------
    // Check channel is active in modules and has a live connection.
    // WordPress and WooCommerce are always active when the plugin
    // is installed -- no module toggle needed.
    // --------------------------------------------------
    private static function isChannelActive(string $channel): bool
    {
        $active_modules = (array) get_option('amai_active_modules', []);
        $module_slug    = ($channel === 'instagram') ? 'facebook' : $channel;
        if (!in_array($module_slug, $active_modules, true)) {
            return false;
        }
        return match ($channel) {
            'facebook', 'instagram' => FacebookAuth::getPageToken() !== null,
            'x'                     => XAuth::isConnected(),
            'wordpress'             => true,
            'woocommerce'           => class_exists('WooCommerce'),
            default                 => false,
        };
    }

    // --------------------------------------------------
    // Facebook: fetch comments on all posted FB posts.
    // --------------------------------------------------
    private static function fetchFacebook(): void
    {
        $token = FacebookAuth::getPageToken();
        if ($token === null) {
            return;
        }
        $post_ids = self::getPostedPlatformIds('facebook', 'facebook_post_id');
        foreach ($post_ids as $row) {
            $fb_post_id = (string) ($row['facebook_post_id'] ?? '');
            if ($fb_post_id === '') {
                continue;
            }
            self::fetchFacebookPostComments($fb_post_id, $token);
        }
    }

    // --------------------------------------------------
    // Fetch all comments and replies on a single FB post.
    // Paginates until no next cursor is returned.
    // --------------------------------------------------
    private static function fetchFacebookPostComments(string $fb_post_id, string $token): void
    {
        $url = self::GRAPH_BASE . '/' . $fb_post_id . '/comments?' . http_build_query([
            'fields'       => 'id,message,from,created_time,parent',
            'access_token' => $token,
            'limit'        => 100,
            'filter'       => 'stream',
        ]);

        while ($url !== '') {
            $response = wp_remote_get($url, ['timeout' => 15]);
            if (is_wp_error($response)) {
                break;
            }
            if ((int) wp_remote_retrieve_response_code($response) !== 200) {
                break;
            }
            $json = json_decode(wp_remote_retrieve_body($response), true);
            if (!is_array($json) || empty($json['data'])) {
                break;
            }
            foreach ($json['data'] as $comment) {
                $comment_id = (string) ($comment['id'] ?? '');
                if ($comment_id === '') {
                    continue;
                }
                $parent_id = (string) ($comment['parent']['id'] ?? '');
                self::upsertComment(
                    'facebook',
                    $comment_id,
                    $fb_post_id,
                    ($parent_id !== '' && $parent_id !== $comment_id) ? $parent_id : '',
                    (string) ($comment['from']['name'] ?? ''),
                    (string) ($comment['from']['id'] ?? ''),
                    (string) ($comment['message'] ?? ''),
                    self::parseDateTime((string) ($comment['created_time'] ?? ''))
                );
            }
            $url = (string) ($json['paging']['next'] ?? '');
        }
    }

    // --------------------------------------------------
    // Instagram: fetch comments on all posted IG media.
    // Uses the same page access token as Facebook.
    // --------------------------------------------------
    private static function fetchInstagram(): void
    {
        $token = FacebookAuth::getPageToken();
        if ($token === null) {
            return;
        }
        $post_ids = self::getPostedPlatformIds('instagram', 'instagram_media_id');
        foreach ($post_ids as $row) {
            $media_id = (string) ($row['instagram_media_id'] ?? '');
            if ($media_id === '') {
                continue;
            }
            self::fetchInstagramMediaComments($media_id, $token);
        }
    }

    // --------------------------------------------------
    // Fetch top-level comments and their replies on a single IG media object.
    // --------------------------------------------------
    private static function fetchInstagramMediaComments(string $media_id, string $token): void
    {
        $url = self::GRAPH_BASE . '/' . $media_id . '/comments?' . http_build_query([
            'fields'       => 'id,text,username,timestamp,replies{id,text,username,timestamp}',
            'access_token' => $token,
            'limit'        => 100,
        ]);

        while ($url !== '') {
            $response = wp_remote_get($url, ['timeout' => 15]);
            if (is_wp_error($response)) {
                break;
            }
            if ((int) wp_remote_retrieve_response_code($response) !== 200) {
                break;
            }
            $json = json_decode(wp_remote_retrieve_body($response), true);
            if (!is_array($json) || empty($json['data'])) {
                break;
            }
            foreach ($json['data'] as $comment) {
                $comment_id = (string) ($comment['id'] ?? '');
                if ($comment_id === '') {
                    continue;
                }
                $author = (string) ($comment['username'] ?? '');
                self::upsertComment(
                    'instagram',
                    $comment_id,
                    $media_id,
                    '',
                    $author,
                    $author,
                    (string) ($comment['text'] ?? ''),
                    self::parseDateTime((string) ($comment['timestamp'] ?? ''))
                );
                // Replies to this top-level comment
                foreach ((array) ($comment['replies']['data'] ?? []) as $reply) {
                    $reply_id = (string) ($reply['id'] ?? '');
                    if ($reply_id === '') {
                        continue;
                    }
                    $reply_author = (string) ($reply['username'] ?? '');
                    self::upsertComment(
                        'instagram',
                        $reply_id,
                        $media_id,
                        $comment_id,
                        $reply_author,
                        $reply_author,
                        (string) ($reply['text'] ?? ''),
                        self::parseDateTime((string) ($reply['timestamp'] ?? ''))
                    );
                }
            }
            $url = (string) ($json['paging']['next'] ?? '');
        }
    }

    // --------------------------------------------------
    // X: fetch mentions directed at the connected account.
    // Matches referenced_tweets against known x_post_id values
    // to link replies back to the originating post.
    // Unmatched mentions stored with empty platform_post_id.
    // --------------------------------------------------
    private static function fetchX(): void
    {
        $token = XAuth::getAccessToken();
        if ($token === null) {
            return;
        }
        $user_id = XAuth::getXUserId();
        if ($user_id === null) {
            return;
        }

        $params = [
            'tweet.fields' => 'id,text,author_id,created_at,referenced_tweets',
            'expansions'   => 'author_id,referenced_tweets.id',
            'user.fields'  => 'id,name,username',
            'max_results'  => 100,
        ];
        $since_id = self::getLastPlatformCommentId('x');
        if ($since_id !== '') {
            $params['since_id'] = $since_id;
        }

        $response = wp_remote_get(
            self::X_API_BASE . '/users/' . $user_id . '/mentions?' . http_build_query($params),
            [
                'timeout' => 15,
                'headers' => ['Authorization' => 'Bearer ' . $token],
            ]
        );

        if (is_wp_error($response)) {
            return;
        }
        if ((int) wp_remote_retrieve_response_code($response) !== 200) {
            return;
        }
        $json = json_decode(wp_remote_retrieve_body($response), true);
        if (!is_array($json) || empty($json['data'])) {
            return;
        }

        // Build user_id -> name map from expansions
        $users = [];
        foreach ((array) ($json['includes']['users'] ?? []) as $u) {
            $users[(string) ($u['id'] ?? '')] = (string) ($u['name'] ?? '');
        }

        $known_tweet_ids = self::getKnownXPostIds();

        foreach ($json['data'] as $tweet) {
            $tweet_id = (string) ($tweet['id'] ?? '');
            if ($tweet_id === '') {
                continue;
            }
            $author_id         = (string) ($tweet['author_id'] ?? '');
            $author_name       = $users[$author_id] ?? '';
            $content           = (string) ($tweet['text'] ?? '');
            $posted_at         = self::parseDateTime((string) ($tweet['created_at'] ?? ''));
            $platform_post_id  = '';
            $parent_comment_id = '';

            foreach ((array) ($tweet['referenced_tweets'] ?? []) as $ref) {
                if (($ref['type'] ?? '') === 'replied_to') {
                    $ref_id = (string) ($ref['id'] ?? '');
                    if (in_array($ref_id, $known_tweet_ids, true)) {
                        $platform_post_id = $ref_id;
                    } else {
                        $parent_comment_id = $ref_id;
                    }
                    break;
                }
            }

            self::upsertComment(
                'x',
                $tweet_id,
                $platform_post_id,
                $parent_comment_id,
                $author_name,
                $author_id,
                $content,
                $posted_at
            );
        }
    }

    // --------------------------------------------------
    // WordPress: fetch approved comments on published posts.
    // Pulls from wp_comments where comment_type = '' (standard
    // post comments) and comment_approved = '1'.
    // Uses wp_comment_id as the synthetic platform_comment_id
    // with a 'wp_' prefix to avoid collision with social IDs.
    // Only fetches comments newer than the last known one to
    // avoid re-processing on every cron run.
    // --------------------------------------------------
    private static function fetchWordPress(): void
    {
        global $wpdb;

        $since = self::getLastWpCommentId('wordpress');

        $rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT c.comment_ID, c.comment_post_ID, c.comment_parent,
                        c.comment_author, c.comment_author_email, c.comment_content,
                        c.comment_date_gmt
                 FROM {$wpdb->comments} c
                 INNER JOIN {$wpdb->posts} p ON p.ID = c.comment_post_ID
                 WHERE c.comment_approved = '1'
                   AND c.comment_type     = ''
                   AND p.post_status      = 'publish'
                   AND c.comment_ID       > %d
                 ORDER BY c.comment_ID ASC
                 LIMIT 200",
                $since
            ),
            ARRAY_A
        );

        if (empty($rows)) {
            return;
        }

        foreach ($rows as $row) {
            $comment_id  = (int) $row['comment_ID'];
            $post_id     = (int) $row['comment_post_ID'];
            $parent_id   = (int) $row['comment_parent'];
            $author      = (string) $row['comment_author'];
            $author_id   = (string) $row['comment_author_email'];
            $content     = (string) $row['comment_content'];
            $date        = (string) $row['comment_date_gmt'];

            $platform_comment_id        = 'wp_' . $comment_id;
            $platform_post_id           = (string) $post_id;
            $parent_platform_comment_id = $parent_id > 0 ? 'wp_' . $parent_id : '';
            $commented_at               = self::parseDateTime($date);

            self::upsertCommentWithWpId(
                'wordpress',
                $platform_comment_id,
                $platform_post_id,
                $parent_platform_comment_id,
                $author,
                $author_id,
                $content,
                $commented_at,
                $comment_id
            );
        }
    }

    // --------------------------------------------------
    // WooCommerce: fetch approved product reviews.
    // Pulls from wp_comments where comment_type = 'review'
    // and comment_approved = '1', post_type = 'product'.
    // Same wp_comment_id prefix scheme as WordPress channel.
    // --------------------------------------------------
    private static function fetchWooCommerce(): void
    {
        global $wpdb;

        $since = self::getLastWpCommentId('woocommerce');

        $rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT c.comment_ID, c.comment_post_ID, c.comment_parent,
                        c.comment_author, c.comment_author_email, c.comment_content,
                        c.comment_date_gmt
                 FROM {$wpdb->comments} c
                 INNER JOIN {$wpdb->posts} p ON p.ID = c.comment_post_ID
                 WHERE c.comment_approved = '1'
                   AND c.comment_type     = 'review'
                   AND p.post_type        = 'product'
                   AND p.post_status      = 'publish'
                   AND c.comment_ID       > %d
                 ORDER BY c.comment_ID ASC
                 LIMIT 200",
                $since
            ),
            ARRAY_A
        );

        if (empty($rows)) {
            return;
        }

        foreach ($rows as $row) {
            $comment_id  = (int) $row['comment_ID'];
            $post_id     = (int) $row['comment_post_ID'];
            $parent_id   = (int) $row['comment_parent'];
            $author      = (string) $row['comment_author'];
            $author_id   = (string) $row['comment_author_email'];
            $content     = (string) $row['comment_content'];
            $date        = (string) $row['comment_date_gmt'];

            $platform_comment_id        = 'wp_' . $comment_id;
            $platform_post_id           = (string) $post_id;
            $parent_platform_comment_id = $parent_id > 0 ? 'wp_' . $parent_id : '';
            $commented_at               = self::parseDateTime($date);

            self::upsertCommentWithWpId(
                'woocommerce',
                $platform_comment_id,
                $platform_post_id,
                $parent_platform_comment_id,
                $author,
                $author_id,
                $content,
                $commented_at,
                $comment_id
            );
        }
    }

    // --------------------------------------------------
    // Upsert a comment row. On duplicate update content only --
    // never overwrite status, ai_draft, reply_content, or replied_at.
    // Followup detection runs on new inserts only.
    // --------------------------------------------------
    private static function upsertComment(
        string $channel,
        string $platform_comment_id,
        string $platform_post_id,
        string $parent_platform_comment_id,
        string $author_name,
        string $author_platform_id,
        string $content,
        string $commented_at
    ): void {
        self::upsertCommentWithWpId(
            $channel,
            $platform_comment_id,
            $platform_post_id,
            $parent_platform_comment_id,
            $author_name,
            $author_platform_id,
            $content,
            $commented_at,
            null
        );
    }

    // --------------------------------------------------
    // Core upsert logic. wp_comment_id is null for social
    // channels and set for WordPress/WooCommerce channels.
    // --------------------------------------------------
    private static function upsertCommentWithWpId(
        string $channel,
        string $platform_comment_id,
        string $platform_post_id,
        string $parent_platform_comment_id,
        string $author_name,
        string $author_platform_id,
        string $content,
        string $commented_at,
        ?int   $wp_comment_id
    ): void {
        global $wpdb;

        if ($content === '' || $commented_at === '') {
            return;
        }

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

        $existing_id = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT id FROM `{$table}` WHERE channel = %s AND platform_comment_id = %s LIMIT 1",
                $channel,
                $platform_comment_id
            )
        );

        if ($existing_id > 0) {
            $wpdb->query(
                $wpdb->prepare(
                    "UPDATE `{$table}` SET content = %s, commented_at = %s WHERE id = %d",
                    $content,
                    $commented_at,
                    $existing_id
                )
            );
            return;
        }

        $is_followup = self::detectFollowup(
            $channel,
            $platform_post_id,
            $author_platform_id,
            $platform_comment_id
        );

        $reply_to_comment_id = null;
        if ($is_followup) {
            $reply_to_comment_id = self::findOurReplyId(
                $channel,
                $platform_post_id,
                $author_platform_id
            );
        }

        if ($wp_comment_id !== null) {
            $wpdb->query(
                $wpdb->prepare(
                    "INSERT INTO `{$table}`
                        (channel, platform_comment_id, platform_post_id, parent_platform_comment_id,
                         author_name, author_platform_id, content, commented_at,
                         status, is_followup, reply_to_comment_id, wp_comment_id)
                     VALUES (%s, %s, %s, %s, %s, %s, %s, %s, 'new', %d, %s, %d)",
                    $channel,
                    $platform_comment_id,
                    $platform_post_id,
                    $parent_platform_comment_id,
                    $author_name,
                    $author_platform_id,
                    $content,
                    $commented_at,
                    $is_followup ? 1 : 0,
                    $reply_to_comment_id !== null ? (string) $reply_to_comment_id : null,
                    $wp_comment_id
                )
            );
        } else {
            $wpdb->query(
                $wpdb->prepare(
                    "INSERT INTO `{$table}`
                        (channel, platform_comment_id, platform_post_id, parent_platform_comment_id,
                         author_name, author_platform_id, content, commented_at,
                         status, is_followup, reply_to_comment_id)
                     VALUES (%s, %s, %s, %s, %s, %s, %s, %s, 'new', %d, %s)",
                    $channel,
                    $platform_comment_id,
                    $platform_post_id,
                    $parent_platform_comment_id,
                    $author_name,
                    $author_platform_id,
                    $content,
                    $commented_at,
                    $is_followup ? 1 : 0,
                    $reply_to_comment_id !== null ? (string) $reply_to_comment_id : null
                )
            );
        }

        $new_id = (int) $wpdb->insert_id;
        if ($new_id > 0) {
            CommentResponder::handleNew($new_id);
        }
    }

    // --------------------------------------------------
    // Followup detection: true if this author has a prior replied
    // comment on this post -- they are engaging again after we responded.
    // --------------------------------------------------
    private static function detectFollowup(
        string $channel,
        string $platform_post_id,
        string $author_platform_id,
        string $current_comment_id
    ): bool {
        if ($platform_post_id === '' || $author_platform_id === '') {
            return false;
        }
        global $wpdb;
        $table = $wpdb->prefix . 'amai_social_comments';
        $count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM `{$table}`
                 WHERE channel              = %s
                   AND platform_post_id     = %s
                   AND author_platform_id   = %s
                   AND platform_comment_id != %s
                   AND status               = 'replied'",
                $channel,
                $platform_post_id,
                $author_platform_id,
                $current_comment_id
            )
        );
        return $count > 0;
    }

    // --------------------------------------------------
    // Find the id of our most recent replied row for this author
    // on this post -- stored as reply_to_comment_id on the followup.
    // --------------------------------------------------
    private static function findOurReplyId(
        string $channel,
        string $platform_post_id,
        string $author_platform_id
    ): ?int {
        global $wpdb;
        $table = $wpdb->prefix . 'amai_social_comments';
        $id = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT id FROM `{$table}`
                 WHERE channel            = %s
                   AND platform_post_id   = %s
                   AND author_platform_id = %s
                   AND status             = 'replied'
                 ORDER BY replied_at DESC
                 LIMIT 1",
                $channel,
                $platform_post_id,
                $author_platform_id
            )
        );
        return $id !== null ? (int) $id : null;
    }

    // --------------------------------------------------
    // Get distinct platform post IDs for posted rows on a channel.
    // --------------------------------------------------
    private static function getPostedPlatformIds(string $channel, string $column): array
    {
        global $wpdb;
        $table = $wpdb->prefix . 'amai_scheduled_posts';
        return (array) $wpdb->get_results(
            $wpdb->prepare(
                "SELECT DISTINCT `{$column}` FROM `{$table}`
                 WHERE channel = %s AND status = 'posted' AND `{$column}` IS NOT NULL AND `{$column}` != ''",
                $channel
            ),
            ARRAY_A
        );
    }

    // --------------------------------------------------
    // Get all stored X tweet IDs from posted X posts.
    // --------------------------------------------------
    private static function getKnownXPostIds(): array
    {
        global $wpdb;
        $table = $wpdb->prefix . 'amai_scheduled_posts';
        return (array) $wpdb->get_col(
            "SELECT x_post_id FROM `{$table}`
             WHERE channel = 'x' AND status = 'posted' AND x_post_id IS NOT NULL AND x_post_id != ''"
        );
    }

    // --------------------------------------------------
    // Get the most recent platform_comment_id for a channel.
    // Used as since_id for X mentions to avoid re-fetching old data.
    // --------------------------------------------------
    private static function getLastPlatformCommentId(string $channel): string
    {
        global $wpdb;
        $table = $wpdb->prefix . 'amai_social_comments';
        $id = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT platform_comment_id FROM `{$table}`
                 WHERE channel = %s
                 ORDER BY commented_at DESC
                 LIMIT 1",
                $channel
            )
        );
        return is_string($id) ? $id : '';
    }

    // --------------------------------------------------
    // Get the highest wp_comment_id already stored for a
    // WordPress or WooCommerce channel. Used as a cursor
    // so the fetcher only pulls new rows on each cron run.
    // --------------------------------------------------
    private static function getLastWpCommentId(string $channel): int
    {
        global $wpdb;
        $table = $wpdb->prefix . 'amai_social_comments';
        $id = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT MAX(wp_comment_id) FROM `{$table}` WHERE channel = %s",
                $channel
            )
        );
        return $id !== null ? (int) $id : 0;
    }

    // --------------------------------------------------
    // Parse an ISO 8601 datetime string to MySQL datetime format.
    // Returns empty string on failure.
    // --------------------------------------------------
    private static function parseDateTime(string $raw): string
    {
        if ($raw === '') {
            return '';
        }
        $ts = strtotime($raw);
        if ($ts === false) {
            return '';
        }
        return gmdate('Y-m-d H:i:s', $ts);
    }
}