<?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\Helpers\DB as DB_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   = 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 ) {
			$success = Analytics::test_connection();
			if ( $success ) {
				AnalyticsJobs::get()->check_for_missing_dates( 'analytics' );

				// Temporary option to clean the data before storing AI traffic.
				if ( ! get_option( 'rank_math_analytics_opt_in_for_ai', false ) ) {
					update_option( 'rank_math_analytics_opt_in_for_ai', 'yes', false );

					DB::traffic()->truncate();
				}
			}
		}

		if ( $this->adsense_connected ) {
			$success = Adsense::test_connection();
			if ( $success ) {
				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 = $row['date'] ?? '';

			if ( empty( $date ) ) {
				continue;
			}

			$day = substr( $date, 0, 4 ) . '-' . substr( $date, 4, 2 ) . '-' . substr( $date, 6, 2 );

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

		return $dates;
	}

	/**
	 * Get analytics data and save it into database.
	 *
	 * @param string $date Date to fetch data for.
	 */
	public function get_analytics_data( $date ) {
		$args = [
			'start_date' => $date,
			'end_date'   => $date,
			'dimensions' => [
				[ 'name' => 'pagePath' ],
				[ 'name' => 'pageReferrer' ],
			],
			'metrics'    => [
				[ 'name' => 'screenPageViews' ],
			],
		];

		// Include country filter (no dimension needed).
		$stored = get_option(
			'rank_math_google_analytic_options',
			[
				'account_id'       => '',
				'property_id'      => '',
				'view_id'          => '',
				'measurement_id'   => '',
				'stream_name'      => '',
				'country'          => '',
				'install_code'     => '',
				'anonymize_ip'     => '',
				'local_ga_js'      => '',
				'exclude_loggedin' => '',
			]
		);

		if ( ! empty( $stored['country'] ) && 'all' !== $stored['country'] ) {
			$args['dimensionFilter']['andGroup']['expressions'][] = [
				'filter' => [
					'fieldName'    => 'countryId',
					'stringFilter' => [
						'matchType' => 'EXACT',
						'value'     => $stored['country'],
					],
				],
			];
		}

		// Add hostname filter for multisite.
		if ( is_multisite() ) {
			$args['dimensionFilter']['andGroup']['expressions'][] = [
				'filter' => [
					'fieldName'    => 'hostname',
					'stringFilter' => [
						'matchType' => 'EXACT',
						'value'     => preg_replace( '#^https?://(www\.)?#i', '', Helper::get_home_url() ),
					],
				],
			];
		}

		$rows = Analytics::get_analytics( $args );

		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.
		DB_Helper::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();
		}
	}
}
