<?php
/**
 * Jobs.
 *
 * @since      1.0.54
 * @package    RankMathPro
 * @subpackage RankMathPro\modules
 * @author     Rank Math <support@rankmath.com>
 */

namespace RankMathPro\Analytics\Workflow;

use DateTime;
use Exception;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMathPro\Analytics\DB;
use RankMath\Analytics\Workflow\Base;
use RankMath\Analytics\DB as AnalyticsDB;
use RankMathPro\Google\Adsense;
use RankMath\Google\Analytics;
use RankMath\Analytics\Workflow\Jobs as AnalyticsJobs;

defined( 'ABSPATH' ) || exit;

/**
 * Jobs class.
 */
class Jobs {

	use Hooker;

	/**
	 * Is an Analytics account connected?
	 *
	 * @var boolean
	 */
	private $analytics_connected = false;

	/**
	 * Is an AdSense account connected?
	 *
	 * @var boolean
	 */
	private $adsense_connected = false;

	/**
	 * Main instance
	 *
	 * Ensure only one instance is loaded or can be loaded.
	 *
	 * @return Jobs
	 */
	public static function get() {
		static $instance;

		if ( is_null( $instance ) && ! ( $instance instanceof Jobs ) ) {
			$instance = new Jobs();
			$instance->hooks();
		}

		return $instance;
	}

	/**
	 * Hooks.
	 */
	public function hooks() {
		$this->analytics_connected = Analytics::is_analytics_connected();
		$this->adsense_connected   = \RankMathPro\Google\Adsense::is_adsense_connected();

		// Check missing data for analytics and adsense.
		$this->action( 'rank_math/analytics/data_fetch', 'data_fetch' );

		// Data Fetcher.
		if ( $this->adsense_connected ) {
			$this->filter( 'rank_math/analytics/get_adsense_days', 'get_adsense_days' );
			$this->action( 'rank_math/analytics/get_adsense_data', 'get_adsense_data', 10, 2 );
		}

		if ( $this->analytics_connected ) {
			$this->action( 'rank_math/analytics/get_analytics_days', 'get_analytics_days' );
			$this->action( 'rank_math/analytics/get_analytics_data', 'get_analytics_data' );
			$this->action( 'rank_math/analytics/handle_analytics_response', 'handle_analytics_response' );
			$this->action( 'rank_math/analytics/clear_cache', 'clear_cache' );
		}

		// Cache.
		$this->action( 'rank_math/analytics/purge_cache', 'purge_cache' );
		$this->action( 'rank_math/analytics/delete_by_days', 'delete_by_days' );
		$this->action( 'rank_math/analytics/delete_data_log', 'delete_data_log' );
	}

	/**
	 * Check missing data for analytics and adsense. Perform this task periodically.
	 */
	public function data_fetch() {
		if ( $this->analytics_connected ) {
			AnalyticsJobs::get()->check_for_missing_dates( 'analytics' );
		}

		if ( $this->adsense_connected ) {
			AnalyticsJobs::get()->check_for_missing_dates( 'adsense' );
		}
	}

	/**
	 * Set the analytics start and end dates.
	 *
	 * @param array $args The start and end dates.
	 */
	public function get_analytics_days( $args = [] ) {
		$rows = Analytics::get_analytics(
			[
				'start_date' => $args['start_date'],
				'end_date'   => $args['end_date'],
			],
			true
		);
		if ( is_wp_error( $rows ) || empty( $rows ) ) {
			return [];
		}

		$empty_dates = get_option( 'rank_math_analytics_empty_dates', [] );
		$dates       = [];

		foreach ( $rows as $row ) {
			$date = '';

			// GA4.
			if ( isset( $row['dimensionValues'] ) ) {
				$date = $row['dimensionValues'][0]['value'];
			} elseif ( isset( $row['dimensions'] ) ) {
				$date = $row['dimensions'][0];
			}

			if ( ! empty( $date ) ) {
				$date = substr( $date, 0, 4 ) . '-' . substr( $date, 4, 2 ) . '-' . substr( $date, 6, 2 );

				if ( ! AnalyticsDB::date_exists( $date, 'analytics' ) && ! in_array( $date, $empty_dates, true ) ) {
					$dates[] = [
						'start_date' => $date,
						'end_date'   => $date,
					];
				}
			}
		}

		return $dates;
	}

	/**
	 * Get analytics data and save it into database.
	 *
	 * @param string $date Date to fetch data for.
	 */
	public function get_analytics_data( $date ) {
		$rows = Analytics::get_analytics(
			[
				'start_date' => $date,
				'end_date'   => $date,
			]
		);

		if ( is_wp_error( $rows ) || empty( $rows ) ) {
			return [];
		}

		try {
			DB::add_analytics_bulk( $date, $rows );
			return $rows;
		} catch ( Exception $e ) {} // phpcs:ignore
	}

	/**
	 * Set the AdSense start and end dates.
	 *
	 * @param array $args The start and end dates.
	 */
	public function get_adsense_days( $args = [] ) {
		$dates = [];

		$begin = new DateTime( $args['start_date'] );
		$end   = new DateTime( $args['end_date'] );

		$missing_dates = [];
		for ( $i = $end; $i >= $begin; $i->modify( '-1 day' ) ) {
			$date = $i->format( 'Y-m-d' );
			if ( ! AnalyticsDB::date_exists( $date, 'adsense' ) ) {
				$missing_dates[] = $date;
			}
		}

		if ( empty( $missing_dates ) ) {
			$dates[] = [
				'start_date' => $args['start_date'],
				'end_date'   => $args['end_date'],
			];

			return $dates;
		}

		// Request for one date range because its not large data to send individual request for each date.
		$dates[] = [
			'start_date' => $missing_dates[ count( $missing_dates ) - 1 ],
			'end_date'   => $missing_dates[0],
		];

		return $dates;
	}

	/**
	 * Get adsense data and save it into database.
	 *
	 * @param string $start_date The start date to fetch.
	 * @param string $end_date   The end date to fetch.
	 */
	public function get_adsense_data( $start_date = '', $end_date = '' ) {
		$rows = Adsense::get_adsense(
			[
				'start_date' => $start_date,
				'end_date'   => $end_date,
			]
		);
		if ( is_wp_error( $rows ) || empty( $rows ) ) {
			return [];
		}

		try {
			DB::add_adsense( $rows );
			return $rows;
		} catch ( Exception $e ) {} // phpcs:ignore
	}

	/**
	 * Handlle analytics response.
	 *
	 * @param array $data API request and response data.
	 */
	public function handle_analytics_response( $data = [] ) {
		if ( 200 !== $data['code'] ) {
			return;
		}

		if ( isset( $data['formatted_response']['rows'] ) && ! empty( $data['formatted_response']['rows'] ) ) {
			return;
		}

		$dates = get_option( 'rank_math_analytics_empty_dates', [] );
		if ( ! $dates ) {
			$dates = [];
		}

		$dates[] = $data['args']['dateRanges'][0]['startDate'];
		$dates[] = $data['args']['dateRanges'][0]['endDate'];

		$dates = array_unique( $dates );

		update_option( 'rank_math_analytics_empty_dates', $dates );
	}

	/**
	 * Clear cache.
	 */
	public function clear_cache() {
		global $wpdb;

		// Delete all useless data from analytics data table.
		$wpdb->get_results( "DELETE FROM {$wpdb->prefix}rank_math_analytics_ga WHERE page NOT IN ( SELECT page from {$wpdb->prefix}rank_math_analytics_objects )" );
	}

	/**
	 * Purge cache.
	 *
	 * @param object $table Table insance.
	 */
	public function purge_cache( $table ) {
		$table->whereLike( 'option_name', 'losing_posts' )->delete();
		$table->whereLike( 'option_name', 'winning_posts' )->delete();
		$table->whereLike( 'option_name', 'losing_keywords' )->delete();
		$table->whereLike( 'option_name', 'winning_keywords' )->delete();
		$table->whereLike( 'option_name', 'tracked_keywords_summary' )->delete();
	}

	/**
	 * Delete analytics and adsense data by days.
	 *
	 * @param  int $days Decide whether to delete all or delete 90 days data.
	 */
	public function delete_by_days( $days ) {
		if ( -1 === $days ) {
			if ( $this->analytics_connected ) {
				DB::traffic()->truncate();
			}
			if ( $this->adsense_connected ) {
				DB::adsense()->truncate();
			}

			return;
		}

		$start = date_i18n( 'Y-m-d H:i:s', strtotime( '-1 days' ) );
		$end   = date_i18n( 'Y-m-d H:i:s', strtotime( '-' . $days . ' days' ) );

		if ( $this->analytics_connected ) {
			DB::traffic()->whereBetween( 'created', [ $end, $start ] )->delete();
		}

		if ( $this->adsense_connected ) {
			DB::adsense()->whereBetween( 'created', [ $end, $start ] )->delete();
		}
	}

	/**
	 * Delete record for comparison.
	 *
	 * @param string $start Start date.
	 */
	public function delete_data_log( $start ) {
		if ( $this->analytics_connected ) {
			DB::traffic()->where( 'created', '<', $start )->delete();
		}

		if ( $this->adsense_connected ) {
			DB::adsense()->where( 'created', '<', $start )->delete();
		}
	}
}
