<?php
declare(strict_types=1);
// ==================================================
// Nuclear Fancy Pants Edition™ — NUCLEAR+++
// File: /includes/class-content-engine.php
// Description: Wednesday cron handler — calls Core to generate weekly posts, stores results in DB - NUCLEAR+++
// Last Edited: 2026-04-09
// ==================================================

namespace AutoMarketerAI;

defined('ABSPATH') || exit;

final class ContentEngine
{
    private const CRON_HOOK           = 'amai_generate_weekly_posts';
    private const CRON_HOOK_INSTAGRAM = 'amai_generate_weekly_posts_instagram';
    private const CRON_HOOK_TIKTOK    = 'amai_generate_weekly_posts_tiktok';
    private const CRON_HOOK_X         = 'amai_generate_weekly_posts_x';
    private const CRON_HOOK_GOOGLE      = 'amai_generate_weekly_posts_google';
    private const CRON_SCHEDULE       = 'weekly_wednesday';
    private const KEY_ID              = 'WP_PROXY';

    // Register cron schedule and hook — called from automarketer-ai.php boot
    public static function init(): void
    {
        add_filter('cron_schedules',              [self::class, 'addSchedule']);
        add_action(self::CRON_HOOK,               [self::class, 'run']);
        add_action(self::CRON_HOOK_INSTAGRAM,     [self::class, 'runInstagram']);
        add_action(self::CRON_HOOK_TIKTOK,        [self::class, 'runTikTok']);
        add_action(self::CRON_HOOK_X,             [self::class, 'runX']);
        add_action(self::CRON_HOOK_GOOGLE,        [self::class, 'runGoogle']);
        add_action('wp',                          [self::class, 'maybeSchedule']);
        register_deactivation_hook(AMAI_PLUGIN_DIR . 'automarketer-ai.php', [self::class, 'clearSchedule']);
    }

    // Add a weekly Wednesday 10am UTC schedule if not already registered
    public static function addSchedule(array $schedules): array
    {
        if (!isset($schedules[self::CRON_SCHEDULE])) {
            $schedules[self::CRON_SCHEDULE] = [
                'interval' => WEEK_IN_SECONDS,
                'display'  => 'Weekly on Wednesday',
            ];
        }
        return $schedules;
    }

    // Schedule both cron events if not already scheduled
    public static function maybeSchedule(): void
    {
        if (!wp_next_scheduled(self::CRON_HOOK)) {
            $next = self::nextWednesdayTimestamp();
            wp_schedule_event($next, self::CRON_SCHEDULE, self::CRON_HOOK);
        }
        if (!wp_next_scheduled(self::CRON_HOOK_INSTAGRAM)) {
            // Offset Instagram by 5 minutes to avoid simultaneous API calls
            $next = self::nextWednesdayTimestamp() + 300;
            wp_schedule_event($next, self::CRON_SCHEDULE, self::CRON_HOOK_INSTAGRAM);
        }
        if (!wp_next_scheduled(self::CRON_HOOK_TIKTOK)) {
            // Offset TikTok by 10 minutes to avoid simultaneous API calls
            $next = self::nextWednesdayTimestamp() + 600;
            wp_schedule_event($next, self::CRON_SCHEDULE, self::CRON_HOOK_TIKTOK);
        }
        if (!wp_next_scheduled(self::CRON_HOOK_X)) {
            // Offset X by 15 minutes to avoid simultaneous API calls
            $next = self::nextWednesdayTimestamp() + 900;
            wp_schedule_event($next, self::CRON_SCHEDULE, self::CRON_HOOK_X);
        }
        if (!wp_next_scheduled(self::CRON_HOOK_GOOGLE)) {
            // Offset Google by 20 minutes to avoid simultaneous API calls
            $next = self::nextWednesdayTimestamp() + 1200;
            wp_schedule_event($next, self::CRON_SCHEDULE, self::CRON_HOOK_GOOGLE);
        }
    }

    // Clear both scheduled events on deactivation
    public static function clearSchedule(): void
    {
        $ts = wp_next_scheduled(self::CRON_HOOK);
        if ($ts) {
            wp_unschedule_event($ts, self::CRON_HOOK);
        }
        $ts = wp_next_scheduled(self::CRON_HOOK_INSTAGRAM);
        if ($ts) {
            wp_unschedule_event($ts, self::CRON_HOOK_INSTAGRAM);
        }
        $ts = wp_next_scheduled(self::CRON_HOOK_TIKTOK);
        if ($ts) {
            wp_unschedule_event($ts, self::CRON_HOOK_TIKTOK);
        }
        $ts = wp_next_scheduled(self::CRON_HOOK_X);
        if ($ts) {
            wp_unschedule_event($ts, self::CRON_HOOK_X);
        }
        $ts = wp_next_scheduled(self::CRON_HOOK_GOOGLE);
        if ($ts) {
            wp_unschedule_event($ts, self::CRON_HOOK_GOOGLE);
        }
    }

    // Main Facebook cron handler
    public static function run(): void
    {
        $context = PostGenerator::buildContext();
        if ($context === null) {
            error_log('[AutoMarketer AI] ContentEngine::run - no Facebook connection or business profile, skipping generation');
            return;
        }

        $batch = PostGenerator::nextWeekBatch();

        if (self::batchExists($batch, 'facebook')) {
            error_log('[AutoMarketer AI] ContentEngine::run - facebook batch ' . $batch . ' already exists, skipping');
            return;
        }

        $resolved_fb = PostGenerator::resolveWeeklyTypes('facebook');
        $perf_fb     = PerformanceAnalyzer::analyze('facebook');
        $fb_context  = array_merge($context, ['post_types' => $resolved_fb]);
        if ($perf_fb !== null) {
            $fb_context['performance_insights'] = $perf_fb;
        }
        $competitor = CompetitorAnalyzer::formatForPrompt();
        if ($competitor !== '') { $fb_context['competitor_context'] = $competitor; }
        $posts = self::callCore($fb_context, $batch, '/api/content/generate-week');
        if ($posts === null) {
            error_log('[AutoMarketer AI] ContentEngine::run - Core call failed for batch ' . $batch);
            update_option('amai_generation_failed', ['channel' => 'facebook', 'batch' => $batch, 'time' => time()], false);
            return;
        }

        self::storePosts($posts, $context, $batch, 'facebook');
    }

    // Instagram cron handler
    public static function runInstagram(): void
    {
        // Require Instagram account ID to be stored — Layer 1 must be complete
        $instagram_account_id = FacebookAuth::getInstagramAccountId();
        if ($instagram_account_id === null) {
            error_log('[AutoMarketer AI] ContentEngine::runInstagram - no Instagram account ID, skipping generation');
            return;
        }

        $context = PostGenerator::buildContext();
        if ($context === null) {
            error_log('[AutoMarketer AI] ContentEngine::runInstagram - no Facebook connection or business profile, skipping generation');
            return;
        }

        $batch = PostGenerator::nextWeekBatch();

        if (self::batchExists($batch, 'instagram')) {
            error_log('[AutoMarketer AI] ContentEngine::runInstagram - instagram batch ' . $batch . ' already exists, skipping');
            return;
        }

        $resolved_ig = PostGenerator::resolveWeeklyTypes('instagram');
        $perf_ig     = PerformanceAnalyzer::analyze('instagram');
        $ig_context  = array_merge($context, ['post_types' => $resolved_ig]);
        if ($perf_ig !== null) {
            $ig_context['performance_insights'] = $perf_ig;
        }
        $competitor = CompetitorAnalyzer::formatForPrompt();
        if ($competitor !== '') { $ig_context['competitor_context'] = $competitor; }
        $posts = self::callCore($ig_context, $batch, '/api/content/generate-week-instagram');
        if ($posts === null) {
            error_log('[AutoMarketer AI] ContentEngine::runInstagram - Core call failed for batch ' . $batch);
            return;
            update_option('amai_generation_failed', ['channel' => 'instagram', 'batch' => $batch, 'time' => time()], false);
        }

        self::storePosts($posts, $context, $batch, 'instagram');
    }

    // TikTok cron handler
    public static function runTikTok(): void
    {
        // Require TikTok connection to be active
        $connection = TikTokAuth::getConnection();
        if ($connection === null) {
            error_log('[AutoMarketer AI] ContentEngine::runTikTok - no TikTok connection, skipping generation');
            return;
        }

        $context = PostGenerator::buildContext();
        if ($context === null) {
            error_log('[AutoMarketer AI] ContentEngine::runTikTok - no business profile, skipping generation');
            return;
        }

        $batch = PostGenerator::nextWeekBatch();

        if (self::batchExists($batch, 'tiktok')) {
            error_log('[AutoMarketer AI] ContentEngine::runTikTok - tiktok batch ' . $batch . ' already exists, skipping');
            return;
        }

        $resolved_tk = PostGenerator::resolveWeeklyTypes('tiktok');
        $perf_tk     = PerformanceAnalyzer::analyze('tiktok');
        $tk_context  = array_merge($context, ['post_types' => $resolved_tk]);
        if ($perf_tk !== null) {
            $tk_context['performance_insights'] = $perf_tk;
        }
        $competitor = CompetitorAnalyzer::formatForPrompt();
        if ($competitor !== '') { $tk_context['competitor_context'] = $competitor; }
        $posts = self::callCore($tk_context, $batch, '/api/content/generate-week-tiktok');
        if ($posts === null) {
            error_log('[AutoMarketer AI] ContentEngine::runTikTok - Core call failed for batch ' . $batch);
            return;
        }
            update_option('amai_generation_failed', ['channel' => 'tiktok', 'batch' => $batch, 'time' => time()], false);

        self::storePosts($posts, $context, $batch, 'tiktok');
    }

    // X cron handler
    public static function runX(): void
    {
        // Require X connection to be active
        if (!XAuth::isConnected()) {
            error_log('[AutoMarketer AI] ContentEngine::runX - no X connection, skipping generation');
            return;
        }

        $context = PostGenerator::buildContext();
        if ($context === null) {
            error_log('[AutoMarketer AI] ContentEngine::runX - no business profile, skipping generation');
            return;
        }

        $batch = PostGenerator::nextWeekBatch();

        if (self::batchExists($batch, 'x')) {
            error_log('[AutoMarketer AI] ContentEngine::runX - x batch ' . $batch . ' already exists, skipping');
            return;
        }

        $resolved_x = PostGenerator::resolveWeeklyTypes('x');
        $perf_x      = PerformanceAnalyzer::analyze('x');
        $x_context   = array_merge($context, ['post_types' => $resolved_x]);
        if ($perf_x !== null) {
            $x_context['performance_insights'] = $perf_x;
        }
        $competitor = CompetitorAnalyzer::formatForPrompt();
        if ($competitor !== '') { $x_context['competitor_context'] = $competitor; }
        $posts = self::callCore($x_context, $batch, '/api/content/generate-week-x');
        if ($posts === null) {
            error_log('[AutoMarketer AI] ContentEngine::runX - Core call failed for batch ' . $batch);
            return;
        }

            update_option('amai_generation_failed', ['channel' => 'x', 'batch' => $batch, 'time' => time()], false);
        self::storePosts($posts, $context, $batch, 'x');
    }
    public static function runGoogle(): void
    {
        if (!GoogleAuth::isConnected()) {
            error_log('[AutoMarketer AI] ContentEngine::runGoogle - no Google connection, skipping generation');
            return;
        }
        $context = PostGenerator::buildContext();
        if ($context === null) {
            error_log('[AutoMarketer AI] ContentEngine::runGoogle - no business profile, skipping generation');
            return;
        }
        $batch = PostGenerator::nextWeekBatch();
        if (self::batchExists($batch, 'google')) {
            error_log('[AutoMarketer AI] ContentEngine::runGoogle - google batch ' . $batch . ' already exists, skipping');
            return;
        }
        $resolved_google = PostGenerator::resolveWeeklyTypes('google');
        $perf_google    = PerformanceAnalyzer::analyze('google');
        $google_context = array_merge($context, ['post_types' => $resolved_google]);
        if ($perf_google !== null) {
            $google_context['performance_insights'] = $perf_google;
        }
        $competitor = CompetitorAnalyzer::formatForPrompt();
        if ($competitor !== '') { $google_context['competitor_context'] = $competitor; }
        $posts = self::callCore($google_context, $batch, '/api/content/generate-week-google');
        if ($posts === null) {
            error_log('[AutoMarketer AI] ContentEngine::runGoogle - Core call failed for batch ' . $batch);
            update_option('amai_generation_failed', ['channel' => 'google', 'batch' => $batch, 'time' => time()], false);
            return;
        }
        self::storePosts($posts, $context, $batch, 'google');
    }

    // Check if posts for this batch and channel already exist in the DB
    private static function batchExists(string $batch, string $channel): bool
    {
        global $wpdb;

        $table = $wpdb->prefix . 'amai_scheduled_posts';
        $count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM `{$table}` WHERE tenant_id = %s AND generation_batch = %s AND channel = %s",
                'default',
                $batch,
                $channel
            )
        );

        return $count > 0;
    }

    // Call a Core content generation endpoint with HMAC-signed request.
    // Returns array of post objects on success, null on failure.
    private static function callCore(array $context, string $batch, string $path): ?array
    {
        if (!defined('AMAI_HMAC_SECRET') || AMAI_HMAC_SECRET === '') {
            error_log('[AutoMarketer AI] ContentEngine::callCore - AMAI_HMAC_SECRET not defined');
            return null;
        }
        if (!defined('AMAI_CORE_URL') || AMAI_CORE_URL === '') {
            error_log('[AutoMarketer AI] ContentEngine::callCore - AMAI_CORE_URL not defined');
            return null;
        }

        $method    = 'POST';
        $url       = rtrim(AMAI_CORE_URL, '/') . $path;
        $timestamp = (string) time();
        $nonce     = bin2hex(random_bytes(16));
        $tenant    = amai_tenant_id();
        $key_id    = self::KEY_ID;

        $payload   = array_merge($context, ['batch' => $batch]);
        $body      = (string) json_encode($payload);
        $raw_secret = (string) get_option('amai_tenant_hmac_secret', '');
        if ($raw_secret === '') {
            return null;
        }
        $secret    = hex2bin($raw_secret);
        $body_b64  = base64_encode(hash('sha256', $body, true));
        $canonical = implode('|', [$method, $path, $timestamp, $nonce, $tenant, $key_id, $body_b64]);
        $signature = rtrim(base64_encode(hash_hmac('sha256', $canonical, $secret, true)), "\n");

        $response = wp_remote_post($url, [
            'timeout' => 90,
            'headers' => [
                'Content-Type'       => 'application/json',
                'X-Amai-Tenant'      => $tenant,
                'X-Amai-Key-Id'      => $key_id,
                'X-Amai-Timestamp'   => $timestamp,
                'X-Amai-Nonce'       => $nonce,
                'X-Amai-Body-SHA256' => $body_b64,
                'X-Amai-Signature'   => $signature,
            ],
            'body' => $body,
        ]);

        if (is_wp_error($response)) {
            error_log('[AutoMarketer AI] ContentEngine::callCore - wp_error: ' . $response->get_error_message());
            return null;
        }

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

        if ($code !== 200 || !is_array($data) || !isset($data['posts'])) {
            error_log('[AutoMarketer AI] ContentEngine::callCore - bad response ' . $code . ': ' . substr($raw, 0, 300));
            return null;
        }

        return $data['posts'];
    }

    // Store generated posts in wp_amai_scheduled_posts.
    // Uses raw prepare+query to avoid wpdb % character issues.
    // Unique key is now (tenant_id, channel, generation_batch, post_type) — migration 010.
    // Initial status is 'approved' when approval mode is 'autopilot', else 'draft'.
    // After each real insert, computes and stores scheduled_utc via
    // PostingEngine::computeAndStoreUtc() so the posting engine can
    // use timezone-correct UTC times without any manual back-fill.
    // Unsplash photo search runs for Facebook posts only — Instagram image
    // sourcing will be handled separately when the image provider architecture is built.
    private static function storePosts(array $posts, array $context, string $batch, string $channel): void
    {
        global $wpdb;

        $table          = $wpdb->prefix . 'amai_scheduled_posts';
        $resolved_types = PostGenerator::resolveWeeklyTypes($channel);
        $dates          = PostGenerator::weekDates($resolved_types);
        $page_id        = (string) ($context['facebook_page_id'] ?? '');
        // Posting time resolved per channel + day-of-week via BestTimeLearner (multi-armed bandit).
        $initial_status = (get_option('amai_approval_mode', 'review') === 'autopilot') ? 'approved' : 'draft';

        foreach ($posts as $post) {
            $post_type = (string) ($post['post_type'] ?? '');
            $content   = (string) ($post['content']   ?? '');

            if ($post_type === '' || $content === '' || !isset($dates[$post_type])) {
                continue;
            }

            // Strip all non-ASCII characters from content before INSERT.
            // Multibyte chars (em-dash, curly quotes etc.) corrupt $wpdb prepared statement parameter binding.
            $content = preg_replace('/[^\x00-\x7F]/', '', $content);

            $scheduled_date = $dates[$post_type];
            // Day index 0=Mon ... 4=Fri based on position in the dates map
            $day_index      = (int) array_search($post_type, array_keys($dates), true);
            $post_time = BestTimeLearner::getBestTime($channel, $day_index);

            $sql = $wpdb->prepare(
                "INSERT INTO `{$table}`
                    (tenant_id, channel, facebook_page_id, post_type, scheduled_date, scheduled_time,
                     status, post_content, generation_batch, created_at, updated_at)
                 VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, UTC_TIMESTAMP(), UTC_TIMESTAMP())
                 ON DUPLICATE KEY UPDATE updated_at = updated_at",
                'default',
                $channel,
                $page_id,
                $post_type,
                $scheduled_date,
                $post_time,
                $initial_status,
                $content,
                $batch
            );

            $wpdb->query($sql);

            // insert_id is non-zero only on a real INSERT — zero means the
            // ON DUPLICATE KEY branch fired (same channel+batch+type already existed),
            // in which case scheduled_utc was already set and we leave it alone.
            $new_id = (int) $wpdb->insert_id;
            if ($new_id > 0) {
                PostingEngine::computeAndStoreUtc($new_id);

                // Unsplash photo search for all channels
                if (defined('UNSPLASH_ACCESS_KEY') && UNSPLASH_ACCESS_KEY !== '') {
                    $keywords = Unsplash::extractKeywordsAi($content);
                    $photo    = Unsplash::search($keywords);
                    if ($photo !== null) {
                        $attachment_id = Unsplash::downloadToMedia($photo['url'], $photo['photographer'], $new_id, (string) ($photo['profile_url'] ?? ''));
                        if ($attachment_id > 0) {
                            $credit = $photo['photographer'] . '|' . $photo['profile_url'];
                            $wpdb->update(
                                $table,
                                ['unsplash_attachment_id' => $attachment_id, 'unsplash_credit' => $credit],
                                ['id' => $new_id],
                                ['%d', '%s'],
                                ['%d']
                            );
                        }
                    }
                }
            }
        }
    }

    // Calculate the next Wednesday at 10:00 UTC as a Unix timestamp
    private static function nextWednesdayTimestamp(): int
    {
        $now  = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
        $next = new \DateTimeImmutable('next wednesday 10:00', new \DateTimeZone('UTC'));

        // If today is Wednesday and it's before 10:00, use today
        if ((int) $now->format('N') === 3 && (int) $now->format('H') < 10) {
            $next = new \DateTimeImmutable('today 10:00', new \DateTimeZone('UTC'));
        }

        return $next->getTimestamp();
    }
}