<?php

/**
 * Scheduler
 *
 * @package AIContentGenerator
 */
namespace AICG;

// Prevent direct access
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
/**
 * WP Cron-based scheduling system
 */
class Scheduler {
    /**
     * Keyword Manager instance
     */
    private $keyword_manager;

    /**
     * Content Generator instance
     */
    private $content_generator;

    /**
     * Constructor
     */
    public function __construct( Keyword_Manager $keyword_manager, Content_Generator $content_generator ) {
        $this->keyword_manager = $keyword_manager;
        $this->content_generator = $content_generator;
    }

    /**
     * Get table name
     */
    private function get_table() {
        global $wpdb;
        return $wpdb->prefix . 'roidaico_schedules';
    }

    /**
     * Save schedule
     */
    public function save_schedule( $data ) {
        global $wpdb;
        $table = $this->get_table();
        // Validate required fields
        if ( empty( $data['name'] ) ) {
            return new \WP_Error('invalid_data', __( 'Schedule name is required.', 'roidnet-ai-content-generator' ));
        }
        if ( empty( $data['days'] ) ) {
            return new \WP_Error('invalid_data', __( 'At least one day must be selected.', 'roidnet-ai-content-generator' ));
        }
        // Validate times
        $times = json_decode( $data['times'], true );
        if ( empty( $times ) || !is_array( $times ) ) {
            return new \WP_Error('invalid_data', __( 'At least one time slot is required.', 'roidnet-ai-content-generator' ));
        }
        // Check minimum interval
        if ( !$this->validate_time_intervals( $times ) ) {
            return new \WP_Error('invalid_interval', __( 'Minimum interval between times is 15 minutes.', 'roidnet-ai-content-generator' ));
        }
        $schedule_data = array(
            'name'        => sanitize_text_field( $data['name'] ),
            'category_id' => absint( $data['category_id'] ?? 0 ),
            'days'        => sanitize_text_field( $data['days'] ),
            'times'       => $data['times'],
            'post_type'   => sanitize_text_field( $data['post_type'] ?? 'post' ),
            'author_id'   => absint( $data['author_id'] ?? 0 ),
            'options'     => $data['options'] ?? '{}',
            'enabled'     => absint( $data['enabled'] ?? 1 ),
        );
        $format = array(
            '%s',
            '%d',
            '%s',
            '%s',
            '%s',
            '%d',
            '%s',
            '%d'
        );
        if ( !empty( $data['id'] ) ) {
            // Update existing
            $result = $wpdb->update(
                $table,
                $schedule_data,
                array(
                    'id' => $data['id'],
                ),
                $format,
                array('%d')
            );
            return ( $result !== false ? $data['id'] : new \WP_Error('db_error', __( 'Failed to update schedule.', 'roidnet-ai-content-generator' )) );
        }
        // Insert new
        $schedule_data['created_at'] = current_time( 'mysql' );
        $format[] = '%s';
        $result = $wpdb->insert( $table, $schedule_data, $format );
        return ( $result ? $wpdb->insert_id : new \WP_Error('db_error', __( 'Failed to create schedule.', 'roidnet-ai-content-generator' )) );
    }

    /**
     * Validate minimum time intervals
     */
    private function validate_time_intervals( $times ) {
        $min_interval = get_option( 'roidaico_min_interval_minutes', 15 );
        // Sort times
        sort( $times );
        for ($i = 1; $i < count( $times ); $i++) {
            $prev = strtotime( $times[$i - 1] );
            $curr = strtotime( $times[$i] );
            $diff_minutes = ($curr - $prev) / 60;
            if ( $diff_minutes < $min_interval ) {
                return false;
            }
        }
        return true;
    }

    /**
     * Get schedule by ID
     */
    public function get_schedule( $id ) {
        global $wpdb;
        $table = $this->get_table();
        return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $id ) );
    }

    /**
     * Get all schedules
     */
    public function get_all_schedules() {
        global $wpdb;
        $table = $this->get_table();
        return $wpdb->get_results( "SELECT * FROM {$table} ORDER BY created_at DESC" );
    }

    /**
     * Get enabled schedules
     */
    public function get_enabled_schedules() {
        global $wpdb;
        $table = $this->get_table();
        return $wpdb->get_results( "SELECT * FROM {$table} WHERE enabled = 1 ORDER BY created_at DESC" );
    }

    /**
     * Delete schedule
     */
    public function delete_schedule( $id ) {
        global $wpdb;
        return $wpdb->delete( $this->get_table(), array(
            'id' => $id,
        ), array('%d') );
    }

    /**
     * Toggle schedule enabled/disabled
     */
    public function toggle_schedule( $id, $enabled ) {
        global $wpdb;
        return $wpdb->update(
            $this->get_table(),
            array(
                'enabled' => ( $enabled ? 1 : 0 ),
            ),
            array(
                'id' => $id,
            ),
            array('%d'),
            array('%d')
        );
    }

    /**
     * Process scheduled publishing (called by WP Cron)
     */
    public function process_scheduled_publish() {
        $schedules = $this->get_enabled_schedules();
        if ( empty( $schedules ) ) {
            return;
        }
        $current_day = strtolower( current_time( 'D' ) );
        // mon, tue, wed, etc.
        $current_time = current_time( 'H:i' );
        foreach ( $schedules as $schedule ) {
            // Check if schedule is for today
            $days = explode( ',', $schedule->days );
            if ( !in_array( $current_day, $days ) ) {
                continue;
            }
            // Check if current time matches any scheduled time
            $times = json_decode( $schedule->times, true );
            if ( !$this->is_time_match( $current_time, $times, $schedule->last_run ) ) {
                continue;
            }
            // Check weekly publish limit for free users
            if ( !License_Manager::can_publish_this_week() ) {
                Action_Logger::log(
                    'scheduler',
                    __( 'Weekly publish limit reached (4/week). Upgrade to Pro for unlimited publishing.', 'roidnet-ai-content-generator' ),
                    'warning',
                    $schedule->id
                );
                continue;
            }
            // Count pending ready articles
            $pending_count = $this->get_pending_posts_count( $schedule->category_id );
            Action_Logger::log(
                'scheduler',
                sprintf( __( 'Checking schedule "%s". Ready drafts: %d', 'roidnet-ai-content-generator' ), $schedule->name, $pending_count ),
                'info',
                $schedule->id
            );
            // Get oldest ready draft
            $ready_post_id = $this->get_oldest_ready_draft( $schedule->category_id, $schedule->post_type, $schedule->author_id );
            if ( $ready_post_id ) {
                $post_title = get_the_title( $ready_post_id );
                Action_Logger::log(
                    'scheduler',
                    sprintf( __( 'Found oldest ready draft: "%s" (Post ID: %d). Publishing now...', 'roidnet-ai-content-generator' ), $post_title, $ready_post_id ),
                    'info',
                    $schedule->id
                );
                // Publish existing draft
                $update_post = array(
                    'ID'            => $ready_post_id,
                    'post_status'   => 'publish',
                    'post_date'     => current_time( 'mysql' ),
                    'post_date_gmt' => current_time( 'mysql', 1 ),
                );
                $result = wp_update_post( $update_post );
                if ( is_wp_error( $result ) ) {
                    Action_Logger::log(
                        'scheduler',
                        sprintf( __( 'Failed to publish draft %d: %s', 'roidnet-ai-content-generator' ), $ready_post_id, $result->get_error_message() ),
                        'error',
                        $schedule->id
                    );
                } else {
                    Action_Logger::log(
                        'scheduler',
                        sprintf( __( 'Successfully published article. Post ID: %d', 'roidnet-ai-content-generator' ), $ready_post_id ),
                        'success',
                        $schedule->id
                    );
                    // Publish linked translation posts
                    $this->publish_translations( $ready_post_id );
                    $this->update_last_run( $schedule->id );
                    // Increment weekly publish counter for free users
                    License_Manager::increment_weekly_publish_count();
                }
                continue;
                // Done for this schedule
            }
            // Fallback: If no draft ready, generate one and publish immediately (Just-in-Time fallback)
            Action_Logger::log(
                'scheduler',
                sprintf( __( 'No ready drafts for schedule "%s". Attempting JIT generation.', 'roidnet-ai-content-generator' ), $schedule->name ),
                'warning',
                $schedule->id
            );
            // Get next keyword from queue (passing null if category_id is 0 to match any keyword)
            $category_id = ( $schedule->category_id > 0 ? $schedule->category_id : null );
            $keyword = $this->keyword_manager->get_next_pending( $category_id );
            // Auto-generate if queue is empty
            if ( !$keyword && get_option( 'roidaico_auto_generate_keywords', false ) ) {
                $this->auto_generate_keywords( $schedule->category_id );
                $keyword = $this->keyword_manager->get_next_pending( $category_id );
            }
            if ( !$keyword ) {
                Action_Logger::log(
                    'scheduler',
                    sprintf( __( 'Skipped schedule "%s": No pending keywords.', 'roidnet-ai-content-generator' ), $schedule->name ),
                    'warning',
                    $schedule->id
                );
                continue;
            }
            // Generate and Publish
            $options = ( json_decode( $schedule->options, true ) ?: array() );
            $options['category_id'] = $schedule->category_id;
            $options['post_type'] = $schedule->post_type;
            $options['author_id'] = $schedule->author_id;
            $options['schedule_id'] = $schedule->id;
            $options['post_status'] = 'publish';
            $result = $this->content_generator->generate_from_keyword( $keyword->id, $options );
            $this->update_last_run( $schedule->id );
            if ( is_wp_error( $result ) ) {
                Action_Logger::log(
                    'scheduler',
                    sprintf( __( 'Failed to JIT generate article for keyword "%s": %s', 'roidnet-ai-content-generator' ), $keyword->keyword, $result->get_error_message() ),
                    'error',
                    $schedule->id
                );
            } else {
                Action_Logger::log(
                    'scheduler',
                    sprintf( __( 'Successfully JIT generated and published article for keyword "%s". Post ID: %d', 'roidnet-ai-content-generator' ), $keyword->keyword, $result ),
                    'success',
                    $schedule->id
                );
            }
        }
    }

    /**
     * Process buffer queue (filling drafts)
     */
    public function process_buffer_queue() {
        // 1. Double-check lock (transient) to prevent concurrent executions
        if ( get_transient( 'roidaico_buffer_lock' ) ) {
            return;
        }
        $schedules = $this->get_enabled_schedules();
        if ( empty( $schedules ) ) {
            return;
        }
        $max_articles = get_option( 'roidaico_max_ready_articles', 5 );
        $min_interval = get_option( 'roidaico_min_interval_minutes', 15 );
        $last_buffer_time = get_option( 'roidaico_last_buffer_time', 0 );
        // 2. Check if minimum interval has passed since last buffer generation
        $minutes_since_last = (time() - $last_buffer_time) / 60;
        if ( $minutes_since_last < $min_interval ) {
            return;
            // Wait for interval to pass
        }
        // 3. Set lock and update last buffer time IMMEDIATELY
        // This prevents multiple asynchronous spawns from entering this block
        set_transient( 'roidaico_buffer_lock', true, 10 * MINUTE_IN_SECONDS );
        update_option( 'roidaico_last_buffer_time', time() );
        try {
            foreach ( $schedules as $schedule ) {
                $pending_count = $this->get_pending_posts_count( $schedule->category_id );
                // If buffer is full, skip
                if ( $pending_count >= $max_articles ) {
                    continue;
                }
                $category_id = ( $schedule->category_id > 0 ? $schedule->category_id : null );
                $keyword = $this->keyword_manager->get_next_pending( $category_id );
                // Auto-generate keywords if needed
                if ( !$keyword && get_option( 'roidaico_auto_generate_keywords', false ) ) {
                    $this->auto_generate_keywords( $schedule->category_id );
                    $keyword = $this->keyword_manager->get_next_pending( $category_id );
                }
                if ( !$keyword ) {
                    continue;
                }
                Action_Logger::log(
                    'scheduler',
                    sprintf( __( 'Buffering: Generating draft for schedule "%s" using keyword: %s', 'roidnet-ai-content-generator' ), $schedule->name, $keyword->keyword ),
                    'info',
                    $schedule->id
                );
                // Parse options
                $options = ( json_decode( $schedule->options, true ) ?: array() );
                $options['category_id'] = $schedule->category_id;
                $options['post_type'] = $schedule->post_type;
                $options['author_id'] = $schedule->author_id;
                $options['schedule_id'] = $schedule->id;
                $options['post_status'] = 'draft';
                // Generate content
                $result = $this->content_generator->generate_from_keyword( $keyword->id, $options );
                if ( is_wp_error( $result ) ) {
                    Action_Logger::log(
                        'scheduler',
                        sprintf( __( 'Failed to buffer article for keyword "%s": %s', 'roidnet-ai-content-generator' ), $keyword->keyword, $result->get_error_message() ),
                        'error',
                        $schedule->id
                    );
                } else {
                    Action_Logger::log(
                        'scheduler',
                        sprintf( __( 'Successfully buffered article for keyword "%s". Post ID: %d', 'roidnet-ai-content-generator' ), $keyword->keyword, $result ),
                        'success',
                        $schedule->id
                    );
                }
                // We only do ONE article per run to respect the interval between articles
                break;
            }
        } finally {
            // 4. Always release the lock
            delete_transient( 'roidaico_buffer_lock' );
        }
    }

    /**
     * Get oldest ready draft
     */
    private function get_oldest_ready_draft( $category_id, $post_type = 'post', $author_id = 0 ) {
        $args = array(
            'post_type'      => $post_type,
            'post_status'    => 'draft',
            'posts_per_page' => 1,
            'orderby'        => array(
                'post_date' => 'ASC',
                'ID'        => 'ASC',
            ),
            'meta_key'       => '_roidaico_generated',
            'meta_value'     => '1',
            'fields'         => 'ids',
        );
        if ( $category_id > 0 ) {
            $args['cat'] = $category_id;
        }
        if ( $author_id > 0 ) {
            $args['author'] = $author_id;
        }
        $query = new \WP_Query($args);
        if ( $query->have_posts() ) {
            return $query->posts[0];
        }
        return false;
    }

    /**
     * Check if current time matches a scheduled time (with missed slot catchup)
     * 
     * This function catches ALL missed time slots from today, not just the current window.
     * This ensures publishing happens even if WP-Cron runs late due to no visitors.
     * 
     * @param string $current_time Current time in H:i format
     * @param array $scheduled_times Array of scheduled times
     * @param string|null $last_run Last run timestamp (used to prevent duplicates)
     * @return bool
     */
    private function is_time_match( $current_time, $scheduled_times, $last_run ) {
        $current_minutes = $this->time_to_minutes( $current_time );
        $today = current_time( 'Y-m-d' );
        // Sort times to process earliest first
        sort( $scheduled_times );
        foreach ( $scheduled_times as $time ) {
            $scheduled_minutes = $this->time_to_minutes( $time );
            // Only consider slots that have already started (current time >= scheduled time)
            if ( $current_minutes < $scheduled_minutes ) {
                continue;
                // This slot hasn't started yet
            }
            // Check if we already published in this specific time slot today
            if ( $last_run ) {
                $last_run_time = strtotime( $last_run );
                $slot_start = strtotime( $today . ' ' . $time . ':00' );
                // If last_run is after this slot's start time, we already published for this slot
                if ( $last_run_time >= $slot_start ) {
                    continue;
                    // Already published in this slot, try next slot
                }
            }
            // Found a slot that has passed but hasn't been published yet
            return true;
        }
        return false;
    }

    /**
     * Convert time string to minutes
     */
    private function time_to_minutes( $time ) {
        list( $hours, $minutes ) = explode( ':', $time );
        return (int) $hours * 60 + (int) $minutes;
    }

    /**
     * Update last run time
     */
    private function update_last_run( $schedule_id ) {
        global $wpdb;
        $wpdb->update(
            $this->get_table(),
            array(
                'last_run' => current_time( 'mysql' ),
            ),
            array(
                'id' => $schedule_id,
            ),
            array('%s'),
            array('%d')
        );
    }

    /**
     * Get pending (draft) posts count for a category
     */
    private function get_pending_posts_count( $category_id ) {
        $args = array(
            'post_type'      => 'post',
            'post_status'    => 'draft',
            'meta_key'       => '_roidaico_generated',
            'meta_value'     => '1',
            'posts_per_page' => -1,
            'fields'         => 'ids',
        );
        if ( $category_id > 0 ) {
            $args['cat'] = $category_id;
        }
        $query = new \WP_Query($args);
        $post_ids = $query->posts;
        if ( empty( $post_ids ) ) {
            return 0;
        }
        // Query the keywords table to find the unique translation groups
        global $wpdb;
        $table = $wpdb->prefix . 'roidaico_keywords';
        $placeholders = implode( ',', array_fill( 0, count( $post_ids ), '%d' ) );
        $results = $wpdb->get_results( $wpdb->prepare( "SELECT id, translation_of FROM {$table} WHERE post_id IN ({$placeholders})", $post_ids ) );
        if ( empty( $results ) ) {
            // Fallback to raw post count if database records aren't linked yet
            return count( $post_ids );
        }
        $unique_roots = array();
        foreach ( $results as $row ) {
            $root_id = ( !empty( $row->translation_of ) ? (int) $row->translation_of : (int) $row->id );
            $unique_roots[$root_id] = true;
        }
        return count( $unique_roots );
    }

    /**
     * Auto-generate keywords when queue is empty
     */
    private function auto_generate_keywords( $category_id ) {
        return false;
    }

    /**
     * Check keyword queue (called periodically)
     */
    public function check_keyword_queue() {
        if ( !get_option( 'roidaico_auto_generate_keywords', false ) ) {
            return;
        }
        $schedules = $this->get_enabled_schedules();
        foreach ( $schedules as $schedule ) {
            $pending_count = $this->keyword_manager->get_pending_count( $schedule->category_id );
            // If queue is nearly empty, generate more keywords
            if ( $pending_count < 5 ) {
                $this->auto_generate_keywords( $schedule->category_id );
            }
        }
    }

    /**
     * Get next scheduled run time
     */
    public function get_next_run( $schedule_id ) {
        $schedule = $this->get_schedule( $schedule_id );
        if ( !$schedule || !$schedule->enabled ) {
            return null;
        }
        $days = explode( ',', $schedule->days );
        $times = json_decode( $schedule->times, true );
        if ( empty( $days ) || empty( $times ) ) {
            return null;
        }
        sort( $times );
        $current_day = strtolower( current_time( 'D' ) );
        $current_time = current_time( 'H:i' );
        // Check remaining times today
        if ( in_array( $current_day, $days ) ) {
            foreach ( $times as $time ) {
                if ( $time > $current_time ) {
                    return current_time( 'Y-m-d' ) . ' ' . $time . ':00';
                }
            }
        }
        // Find next day
        $day_map = array(
            'sun' => 0,
            'mon' => 1,
            'tue' => 2,
            'wed' => 3,
            'thu' => 4,
            'fri' => 5,
            'sat' => 6,
        );
        $current_day_num = current_time( 'w' );
        for ($i = 1; $i <= 7; $i++) {
            $check_day_num = ($current_day_num + $i) % 7;
            $check_day = array_search( $check_day_num, $day_map );
            if ( in_array( $check_day, $days ) ) {
                $next_date = date( 'Y-m-d', strtotime( current_time( 'Y-m-d' ) . " +{$i} days" ) );
                return $next_date . ' ' . $times[0] . ':00';
            }
        }
        return null;
    }

    /**
     * Find all translation sibling posts and publish them
     */
    private function publish_translations( $post_id ) {
        $translation_ids = array();
        // Polylang
        if ( function_exists( 'pll_get_post_translations' ) ) {
            $translations = pll_get_post_translations( $post_id );
            if ( is_array( $translations ) ) {
                $translation_ids = array_values( $translations );
            }
        } elseif ( has_action( 'wpml_set_element_language_details' ) ) {
            $post_type = get_post_type( $post_id );
            $trid = apply_filters(
                'wpml_element_trid',
                null,
                $post_id,
                'post_' . $post_type
            );
            if ( $trid ) {
                $translations = apply_filters(
                    'wpml_get_element_translations',
                    null,
                    $trid,
                    'post_' . $post_type
                );
                if ( is_array( $translations ) ) {
                    foreach ( $translations as $trans ) {
                        if ( isset( $trans->element_id ) ) {
                            $translation_ids[] = (int) $trans->element_id;
                        }
                    }
                }
            }
        }
        if ( !empty( $translation_ids ) ) {
            foreach ( $translation_ids as $trans_post_id ) {
                if ( $trans_post_id == $post_id ) {
                    continue;
                }
                $post = get_post( $trans_post_id );
                if ( $post && $post->post_status === 'draft' ) {
                    wp_update_post( array(
                        'ID'            => $trans_post_id,
                        'post_status'   => 'publish',
                        'post_date'     => current_time( 'mysql' ),
                        'post_date_gmt' => current_time( 'mysql', 1 ),
                    ) );
                    Action_Logger::log( 'scheduler', sprintf( __( 'Successfully published linked translation article. Post ID: %d', 'roidnet-ai-content-generator' ), $trans_post_id ) );
                }
            }
        }
    }

    /**
     * Auto-translate keywords to other active languages and queue them
     */
    private function translate_auto_generated_keywords( $added_keywords, $category_id ) {
        $active_langs = $this->get_active_languages();
        $default_lang = get_option( 'roidaico_default_language', 'en' );
        // Sibling languages to translate to
        $target_langs = array_keys( $active_langs );
        // Exclude default language (since the generated keyword is in the default language)
        $target_langs = array_diff( $target_langs, array($default_lang) );
        if ( empty( $target_langs ) ) {
            return;
        }
        $ai_client = new Ai_Client();
        foreach ( $added_keywords as $kw_info ) {
            $parent_id = $kw_info['id'];
            $parent_text = $kw_info['keyword'];
            foreach ( $target_langs as $lang ) {
                // Translate the keyword
                $translated_text = $ai_client->translate_keyword( $parent_text, $active_langs[$lang] );
                if ( !is_wp_error( $translated_text ) && !empty( $translated_text ) ) {
                    $translated_category_id = $this->get_translated_category_id( $category_id, $lang );
                    $this->keyword_manager->add_keyword(
                        $translated_text,
                        $translated_category_id,
                        $lang,
                        $parent_id
                    );
                }
            }
        }
    }

    /**
     * Get active site languages
     */
    private function get_active_languages() {
        $langs = array();
        // Polylang
        if ( function_exists( 'pll_languages_list' ) ) {
            $slugs = ( pll_languages_list( array(
                'fields' => 'slug',
            ) ) ?: array() );
            $names = ( pll_languages_list( array(
                'fields' => 'name',
            ) ) ?: array() );
            if ( count( $slugs ) === count( $names ) && !empty( $slugs ) ) {
                $langs = array_combine( $slugs, $names );
            } else {
                foreach ( $slugs as $slug ) {
                    $langs[$slug] = strtoupper( $slug );
                }
            }
        } elseif ( function_exists( 'wpml_get_active_languages_filter' ) || has_filter( 'wpml_active_languages' ) ) {
            $wpml_langs = apply_filters( 'wpml_active_languages', null, array(
                'skip_missing' => 0,
            ) );
            if ( is_array( $wpml_langs ) ) {
                foreach ( $wpml_langs as $lang ) {
                    $langs[$lang['code']] = $lang['english_name'];
                }
            }
        }
        // Default languages if no multilingual plugin is active
        if ( empty( $langs ) ) {
            $locale = get_locale();
            $lang_code = substr( $locale, 0, 2 );
            $lang_name = strtoupper( $lang_code );
            if ( $lang_code === 'en' ) {
                $lang_name = __( 'English', 'roidnet-ai-content-generator' );
            } elseif ( $lang_code === 'ar' ) {
                $lang_name = __( 'Arabic', 'roidnet-ai-content-generator' );
            } elseif ( $lang_code === 'tr' ) {
                $lang_name = __( 'Turkish', 'roidnet-ai-content-generator' );
            }
            $langs = array(
                $lang_code => $lang_name,
            );
        }
        return $langs;
    }

    /**
     * Get the translated category ID for a given target language using Polylang or WPML
     */
    private function get_translated_category_id( $category_id, $target_lang ) {
        if ( empty( $category_id ) ) {
            return 0;
        }
        // Polylang
        if ( function_exists( 'pll_get_term' ) ) {
            $translated_id = pll_get_term( $category_id, $target_lang );
            if ( $translated_id ) {
                return (int) $translated_id;
            }
        } elseif ( has_filter( 'wpml_object_id' ) ) {
            $translated_id = apply_filters(
                'wpml_object_id',
                $category_id,
                'category',
                true,
                $target_lang
            );
            if ( $translated_id ) {
                return (int) $translated_id;
            }
        }
        return $category_id;
    }

}
