<?php

/**
 * Fetch eligible posts.
 *
 * @since      1.2.0
 * @package    WP Auto Republish
 * @subpackage Wpar\Core
 * @author     Sayan Datta <iamsayan@protonmail.com>
 */
namespace Wpar\Core;

use  Wpar\Helpers\Hooker ;
use  Wpar\Helpers\HelperFunctions ;
defined( 'ABSPATH' ) || exit;
/**
 * Republication class.
 */
class FetchPosts
{
    use  HelperFunctions, Hooker ;
    /**
     * Query Data
     *
     * (default value: array())
     *
     * @var array
     * @access private
     */
    private  $query = array() ;
    /**
     * Register functions.
     */
    public function register()
    {
        $this->action( 'init', 'generate_task' );
        $this->action( 'wpar/global_republish_fetch_posts', 'run_republish_process' );
    }
    
    /**
     * Generate Action event if not already exists.
     */
    public function generate_task()
    {
        $interval = $this->do_filter( 'global_cron_interval', 1 );
        if ( !$this->has_next_action( 'wpar/global_republish_fetch_posts' ) ) {
            $this->set_recurring_action( strtotime( '+1 minutes' ), MINUTE_IN_SECONDS * $interval, 'wpar/global_republish_fetch_posts' );
        }
    }
    
    /**
     * Run auto republish process.
     */
    public function run_republish_process()
    {
        if ( $this->is_enabled( 'enable_plugin', true ) && $this->valid_next_run() ) {
            
            if ( get_transient( 'wpar_global_lock' ) === false ) {
                // run post republish query
                $this->get_old_posts();
                // lock republish query
                set_transient( 'wpar_global_lock', true, 10 );
                // update log reference
                update_option( 'wpar_global_last_run', time() );
            }
        
        }
    }
    
    /**
     * Get eligible posts.
     */
    private function get_old_posts()
    {
        $post_types = $this->get_data( 'wpar_post_types', [ 'post' ] );
        
        if ( !empty($post_types) ) {
            foreach ( $post_types as $post_type ) {
                if ( !$this->has_future_posts( $post_type ) ) {
                    $this->query_posts( $post_type );
                }
            }
            $this->complete();
        }
    
    }
    
    /**
     * Get eligible post ids for every available post types
     *
     * @param string $post_type WordPress post types
     */
    private function query_posts( $post_type )
    {
        $timestamp = $this->current_timestamp();
        $overwrite = $this->get_data( 'wpar_exclude_by_type', 'none' );
        $taxonomies = $this->get_data( 'wpar_post_taxonomy', [] );
        $post_age = $this->get_data( 'wpar_republish_post_age', 120 );
        if ( !in_array( $post_age, [
            '30',
            '45',
            '60',
            '90',
            '120',
            '180',
            '240',
            '365',
            '730',
            '1095'
        ] ) ) {
            $post_age = 14400;
        }
        $cats = $tags = $terms = [];
        $args = [
            'post_status' => 'publish',
            'post_type'   => $post_type,
            'numberposts' => -1,
        ];
        if ( $post_age != 0 ) {
            $args['date_query'][]['before'] = $this->do_filter( 'post_before_date', gmdate( 'Y-m-d', strtotime( "-{$post_age} days", $timestamp ) ), $timestamp );
        }
        
        if ( !in_array( $post_type, [ 'post', 'page', 'attachment' ] ) ) {
            $args['meta_query'] = [
                'relation' => 'AND',
                [
                'key'     => 'wpar_global_republish_status',
                'compare' => 'NOT EXISTS',
            ],
                [
                'key'     => '_wpar_post_republish_occurrence',
                'compare' => 'NOT EXISTS',
            ],
            ];
        } else {
            $args['meta_query'] = [ [
                'key'     => 'wpar_global_republish_status',
                'compare' => 'NOT EXISTS',
            ] ];
        }
        
        
        if ( $overwrite != 'none' && !empty($taxonomies) ) {
            foreach ( $taxonomies as $taxonomy ) {
                $get_item = explode( '|', $taxonomy );
                $type = $get_item[0];
                $taxonomy_name = $get_item[1];
                $term_id = $get_item[2];
                if ( $post_type === $type && is_object_in_taxonomy( $post_type, $taxonomy_name ) ) {
                    
                    if ( $taxonomy_name == 'category' ) {
                        $cats[] = $term_id;
                    } elseif ( $taxonomy_name == 'post_tag' ) {
                        $tags[] = $term_id;
                    } else {
                    }
                
                }
            }
            
            if ( $overwrite == 'include' ) {
                if ( !empty($cats) ) {
                    $args['category__in'] = $cats;
                }
                if ( !empty($tags) ) {
                    $args['tag__in'] = $tags;
                }
            } elseif ( $overwrite == 'exclude' ) {
                if ( !empty($cats) ) {
                    $args['category__not_in'] = $cats;
                }
                if ( !empty($tags) ) {
                    $args['tag__not_in'] = $tags;
                }
            }
        
        }
        
        $args = $this->do_filter( 'query_args', $args );
        //error_log( print_r( $args, true ) );
        // store post objects
        $this->query[] = get_posts( $args );
    }
    
    /**
     * Complete
     */
    private function complete()
    {
        $timestamp = $this->current_timestamp();
        $query = $this->query;
        $post_types = $this->get_data( 'wpar_post_types', [ 'post' ] );
        $number_posts = $this->get_data( 'number_of_posts', 1 );
        $orderby = $this->get_data( 'wpar_republish_orderby', 'date' );
        $order = $this->get_data( 'wpar_republish_method', 'old_first' );
        $overwrite = $this->get_data( 'wpar_exclude_by_type', 'none' );
        $exclude_ids = $this->get_data( 'wpar_override_category_tag' );
        $exclude_ids = preg_replace( [
            '/[^\\d,]/',
            '/(?<=,),+/',
            '/^,+/',
            '/,+$/'
        ], '', $exclude_ids );
        $post_ids = [];
        
        if ( !empty($query) ) {
            $posts_list = array_merge( ...$query );
            $post_ids = wp_list_pluck( $posts_list, 'ID' );
        }
        
        //error_log( print_r( $post_ids, true ) );
        
        if ( !empty($post_ids) ) {
            $args = [
                'post_type'   => $post_types,
                'post_status' => 'publish',
                'post__in'    => $post_ids,
                'numberposts' => $number_posts,
                'orderby'     => $orderby,
            ];
            
            if ( !empty($order) ) {
                $args['order'] = 'ASC';
                if ( $order == 'new_first' ) {
                    $args['order'] = 'DESC';
                }
            }
            
            if ( $args['orderby'] != 'date' ) {
                $args['orderby'] = 'date';
            }
            if ( !empty($exclude_ids) ) {
                
                if ( $overwrite == 'include' ) {
                    $args['post__in'] = array_diff( $post_ids, explode( ',', $exclude_ids ) );
                } elseif ( $overwrite == 'exclude' ) {
                    $args['post__in'] = array_unique( array_merge( $post_ids, explode( ',', $exclude_ids ) ) );
                }
            
            }
            $args = $this->do_filter( 'post_query_args', $args );
            //error_log( print_r( $args, true ) );
            // get the required date time
            $datetime = $this->next_schedule( $timestamp, 'local' );
            $posts = get_posts( $args );
            if ( !empty($posts) ) {
                foreach ( $posts as $post ) {
                    // delete previosly scheduled hook if exists any.
                    $this->unschedule_all_actions( 'wpar/global_republish_single_post', [ $post->ID ] );
                    // schedule single post republish event
                    $this->set_single_action( get_gmt_from_date( $datetime, 'U' ), 'wpar/global_republish_single_post', [ $post->ID ] );
                    // update required post metas
                    $this->update_meta( $post->ID, 'wpar_global_republish_status', 'pending' );
                    $this->update_meta( $post->ID, '_wpar_global_republish_datetime', $datetime );
                }
            }
        }
        
        // update future reference
        update_option( 'wpar_last_global_cron_run', $timestamp );
    }
    
    /**
     * Generate Single cron time.
     * 
     * @param int     $timestamp Local Timestamp
     * @param array   $weekdays  Available weekdays
     * @param string  $format    Datetime format
     * 
     * @return int|string  Generated UTC timestamp
     */
    private function next_schedule( $timestamp, $format = 'GMT' )
    {
        $weekdays = $this->get_data( 'wpar_days', [
            'sun',
            'mon',
            'tue',
            'wed',
            'thu',
            'fri',
            'sat'
        ] );
        $start_time_input = $this->get_data( 'wpar_start_time', '05:00:00' );
        $end_time_input = $this->get_data( 'wpar_end_time', '23:59:59' );
        $current_date = gmdate( 'Y-m-d', $timestamp );
        $start_time = strtotime( $start_time_input );
        $end_time = strtotime( $end_time_input );
        
        if ( $start_time <= $end_time ) {
            $start_time = strtotime( $current_date . ' ' . $start_time_input );
            $end_time = strtotime( $current_date . ' ' . $end_time_input );
        } else {
            $start_time = strtotime( $current_date . ' ' . $start_time_input );
            $end_time = strtotime( '+1 day', strtotime( $current_date . ' ' . $end_time_input ) );
        }
        
        $slop = $this->get_data( 'wpar_random_republish_interval', 14400 );
        if ( !in_array( $slop, [
            '3600',
            '7200',
            '14400',
            '21600'
        ] ) ) {
            $slop = 14400;
        }
        $timestamp = $timestamp + wp_rand( 30, $slop );
        
        if ( $timestamp >= $start_time && $timestamp <= $end_time ) {
            $final_timestamp = $timestamp;
        } else {
            $rand_time = $start_time + wp_rand( 300, 900 );
            $final_timestamp = strtotime( gmdate( 'Y-m-d', $timestamp ) . ' ' . gmdate( 'H:i:s', $rand_time ) );
        }
        
        
        if ( !in_array( lcfirst( gmdate( 'D', $final_timestamp ) ), $weekdays ) ) {
            $i = 1;
            while ( $i <= 7 ) {
                $next_timestamp = strtotime( '+' . $i . ' days', $final_timestamp );
                $next_date = lcfirst( gmdate( 'D', $next_timestamp ) );
                if ( in_array( $next_date, $weekdays ) ) {
                    break;
                }
                $i++;
            }
            $final_timestamp = $next_timestamp;
        }
        
        $formatted_date = gmdate( 'Y-m-d H:i:s', $final_timestamp );
        if ( $format == 'local' ) {
            return $formatted_date;
        }
        return get_gmt_from_date( $formatted_date, 'U' );
    }
    
    /**
     * Check if current run is actually eligible.
     */
    private function valid_next_run()
    {
        $last = get_option( 'wpar_last_global_cron_run' );
        $current_time = $this->current_timestamp();
        $interval = $this->get_data( 'wpar_minimun_republish_interval', 43200 );
        $proceed = false;
        // switch
        if ( $this->slot_available() ) {
            
            if ( false === $last ) {
                $proceed = true;
            } elseif ( is_numeric( $last ) ) {
                if ( $current_time - $last >= $interval ) {
                    $proceed = true;
                }
            }
        
        }
        return $proceed;
    }
    
    /**
     * Check if weekdays are available.
     * 
     * @return bool
     */
    private function slot_available()
    {
        $timestamp = $this->current_timestamp();
        $start_time_input = $this->get_data( 'wpar_start_time', '05:00:00' );
        $end_time_input = $this->get_data( 'wpar_end_time', '23:59:59' );
        $current_date = gmdate( 'Y-m-d', $timestamp );
        $start_time = strtotime( $start_time_input );
        $end_time = strtotime( $end_time_input );
        
        if ( $start_time <= $end_time ) {
            $start_time = strtotime( $current_date . ' ' . $start_time_input );
            $end_time = strtotime( $current_date . ' ' . $end_time_input );
        } else {
            $start_time = strtotime( $current_date . ' ' . $start_time_input );
            $end_time = strtotime( '+1 day', strtotime( $current_date . ' ' . $end_time_input ) );
        }
        
        $cur_time = $timestamp;
        $weekdays = $this->get_data( 'wpar_days' );
        $next_date = lcfirst( gmdate( 'D', $timestamp ) );
        $available = false;
        if ( $cur_time >= $start_time && $cur_time <= $end_time ) {
            if ( !empty($weekdays) && in_array( $next_date, $weekdays ) ) {
                $available = true;
            }
        }
        return $available;
    }
    
    /**
     * Check if has any future posts.
     * 
     * @since v1.1.7
     */
    private function has_future_posts( $post_type )
    {
        // cureent timestmap
        $timestamp = $this->current_timestamp();
        // get future posts
        $posts = $this->do_filter( 'has_future_post_args', get_posts( [
            'numberposts' => -1,
            'post_type'   => $post_type,
            'sort_order'  => 'ASC',
            'post_status' => 'future',
            'date_query'  => [
            'year'  => gmdate( 'Y', $timestamp ),
            'month' => gmdate( 'n', $timestamp ),
            'day'   => gmdate( 'j', $timestamp ),
        ],
        ] ), $post_type );
        if ( !empty($posts) && count( $posts ) > 0 && $this->do_filter( 'has_future_post_check', false ) ) {
            return true;
        }
        return false;
    }

}