<?php
namespace Actirise\Includes;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

use Actirise\Includes\Api;
use DOMDocument;
use DOMXPath;
use Exception;

/**
 * Ad format management class
 *
 * @link       https://actirise.com
 * @since      3.2.0
 * @package    actirise
 * @subpackage actirise/includes
 * @author     actirise <wordpress@actirise.com>
 *
 * @phpstan-type SlotScaling array{active: bool, upsize: bool, downsize: bool}
 * @phpstan-type MediaTypeBanner array{sizes: array<int, array{0: int, 1: int}>}
 * @phpstan-type MediaTypeNative array{
 *     sizes: array<int, array{0: int, 1: int}>,
 *     sendTargetingKeys: bool,
 *     title: array{required: bool},
 *     image: array{required: bool},
 *     sponsoredBy: array{required: bool},
 *     icon: array{required: bool},
 *     body: array{required: bool},
 *     clickUrl: array{required: bool},
 *     price: array{required: bool},
 *     cta: array{required: bool},
 *     privacyLink: array{required: bool},
 *     displayUrl: array{required: bool}
 * }
 * @phpstan-type MediaTypes array{banner?: MediaTypeBanner, native?: MediaTypeNative}
 * @phpstan-type SlotInjection array{
 *     xpath: array<int, string>,
 *     minHeightDistance: int|null,
 *     minStartNodes: int|null,
 *     nearSlotsCheck: array<mixed>,
 *     nearXpathsCheck: array<mixed>,
 *     nearCheckDistance: int|null,
 *     repeat: bool,
 *     hierarchy: string,
 *     nearCheck: bool
 * }
 * @phpstan-type SlotConfig array{
 *     bidTimeout: int|null,
 *     slotType: string|null,
 *     margin: int|null,
 *     nomargin: bool,
 *     initialHeight: int,
 *     refresh: int,
 *     mediaTypes: MediaTypes,
 *     injection: array<int, SlotInjection>,
 *     bidders: array<int, string>,
 *     targets: array{devices: array<int, string>},
 *     template: string|null,
 *     displayEvent: string|null
 * }
 * @phpstan-type Slot array{
 *     name: string,
 *     uuid: string,
 *     scaling: SlotScaling,
 *     nomargin: bool,
 *     visible: bool,
 *     safeFrame: bool,
 *     initialHeight: int,
 *     configs: array<int, SlotConfig>,
 *     brand: bool,
 *     presizedDiv: bool
 * }
 * @phpstan-type SlotsResponse array{slots: array<int, Slot>}
 */
final class AdFormat {

	/**
	 * @var Api
	 */
	private $api;

	/**
	 * AdFormat constructor.
	 */
	public function __construct() {
		$this->api = new Api();
	}

	/**
	 * Get the slots for the current website configuration
	 *
	 * @since 3.2.0
	 * @return SlotsResponse|\WP_Error
	 */
	public function get_slots() {
		/** @var string $token */
		$token = Options::get( 'settings-analytics-token', '' );

		$website_id = Options::get( 'website-id', '' );

		if ( $website_id === '' ) {
			return new \WP_Error( '500', 'Unable to retrieve website ID' );
		}

		$endpoint = 'website_configs/' . $website_id . '/slots';
		$response = $this->api->get( 'api', $endpoint, array(), $token );

		if ( is_wp_error( $response ) ) {
			return new \WP_Error( '500', 'Error while get_slots' );
		}

		/** @var SlotsResponse $response */
		return $response;
	}

	/**
	 * Extract and cache enabled slot names from a slots response.
	 *
	 * @since 4.1.0
	 * @param array<mixed> $slots_response The response from get_slots().
	 * @return void
	 */
	public static function cache_enabled_slugs( $slots_response ) {
		if ( ! isset( $slots_response['slots'] ) || ! is_array( $slots_response['slots'] ) ) {
			return;
		}

		$enabled_slugs = array();
		foreach ( $slots_response['slots'] as $slot ) {
			if ( ! isset( $slot['name'], $slot['configs'] ) || ! is_array( $slot['configs'] ) ) {
				continue;
			}

			$devices = array(
				'desktop' => false,
				'mobile'  => false,
			);

			foreach ( $slot['configs'] as $config ) {
				if ( ! isset( $config['active'] ) || ! $config['active'] ) {
					continue;
				}
				if ( ! isset( $config['targets']['devices'] ) || ! is_array( $config['targets']['devices'] ) ) {
					continue;
				}
				foreach ( $config['targets']['devices'] as $device ) {
					if ( $device === 'desktop' || $device === 'tablet' ) {
						$devices['desktop'] = true;
					} elseif ( $device === 'mobile' ) {
						$devices['mobile'] = true;
					}
				}
			}

			if ( $devices['desktop'] || $devices['mobile'] ) {
				/** @var string $name */
				$name                   = $slot['name'];
				$enabled_slugs[ $name ] = $devices;
			}
		}

		Options::update( 'ad-format-enabled-slugs', $enabled_slugs );
	}

	/**
	 * Update the slots for the current website configuration
	 *
	 * @since 3.2.0
	 *
	 * @param string $slots JSON string containing slots.
	 * @param string $in_image
	 * @param string $note
	 *
	 * @return SlotsResponse|\WP_Error
	 */
	public function update_slots( $slots, $in_image, $note = '' ) {
		/** @var string $token */
		$token = Options::get( 'settings-analytics-token', '' );

		$website_id = Options::get( 'website-id', '' );

		if ( $website_id === '' ) {
			return new \WP_Error( '500', 'Unable to retrieve website ID' );
		}

		$json_slots    = json_decode( $slots, true );
		$json_in_image = json_decode( $in_image, true );

		if ( empty( $json_slots ) || ! is_array( $json_slots ) ) {
			return new \WP_Error( '400', 'Valid slots data is required' );
		}

		$endpoint = 'website_configs/' . $website_id . '/slots';
		$args     = array(
			'slots'   => $json_slots,
			'inImage' => $json_in_image,
			'note'    => $note,
		);
		$response = $this->api->post( 'api', $endpoint, $args, $token );

		if ( is_wp_error( $response ) ) {
			return new \WP_Error( '500', 'Error while updating slots' );
		}

		// Schedule XPath validation for later (API needs time to process)
		$this->schedule_xpath_validation();
		$this->schedule_presized_div_sync();

		/** @var SlotsResponse $response */
		return $response;
	}

	/**
	 * Get page types to test for a specific slot based on its configuration
	 *
	 * @since 3.2.0
	 * @param string $slot_name
	 * @return array<string>
	 */
	private function get_page_types_for_slot( $slot_name ) {
		$slot_page_types_map = array(
			'corner'        => array( 'post', 'page' ),
			'slide-up'      => array( 'post', 'page', 'home' ),
			'rewards'       => array( 'post' ),
			'intext'        => array( 'post' ),
			'top-content'   => array( 'post' ),
			'in-feed'       => array( 'page' ),
			'feed'          => array( 'post' ),
			'top-page'      => array( 'post', 'page', 'home' ),
			'aside-desktop' => array( 'post', 'page', 'home' ),
		);

		if ( isset( $slot_page_types_map[ $slot_name ] ) ) {
			return $slot_page_types_map[ $slot_name ];
		}

		return array();
	}

	/**
	 * Check XPath validity for all active slots across different page types
	 * Optimized to stop testing per device once a slot passes
	 *
	 * @since 3.2.0
	 * @param array<string> $page_types Optional array of page types to check. Default: ['home', 'post', 'page'].
	 * @return array{success: bool, results: array<mixed>, failed_active_config: int} Array containing validation results
	 */
	public function check_slots_xpath( $page_types = array( 'home', 'post', 'page' ) ) {
		$slots_response = $this->get_slots();

		if ( is_wp_error( $slots_response ) ) {
			return array(
				'success'              => false,
				'message'              => 'Failed to retrieve slots configuration: ' . $slots_response->get_error_message(),
				'results'              => array(),
				'failed_active_config' => 0,
			);
		}

		if ( ! isset( $slots_response['slots'] ) || ! is_array( $slots_response['slots'] ) ) {
			return array(
				'success'              => false,
				'message'              => 'Invalid slots configuration received',
				'results'              => array(),
				'failed_active_config' => 0,
			);
		}

		self::cache_enabled_slugs( $slots_response );

		$device_slot_results   = array();
		$all_devices           = array( 'desktop', 'tablet', 'mobile' );
		$failed_active_configs = 0; // Count failed active configs per device

		foreach ( $all_devices as $device ) {
			$device_slot_results[ $device ] = array();
		}

		$remaining_slots_per_device = array();
		foreach ( $all_devices as $device ) {
			$remaining_slots_per_device[ $device ] = array();
			foreach ( $slots_response['slots'] as $slot ) {
				$slot_name       = isset( $slot['name'] ) ? $slot['name'] : '';
				$slot_page_types = $this->get_page_types_for_slot( $slot_name );

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

				$has_device_config = false;
				if ( isset( $slot['configs'] ) && is_array( $slot['configs'] ) ) {
					foreach ( $slot['configs'] as $config ) {
						if ( isset( $config['targets']['devices'] ) && is_array( $config['targets']['devices'] ) ) {
							if ( in_array( $device, $config['targets']['devices'], true ) ) {
								$has_device_config = true;
								break;
							}
						}
					}
				}

				if ( $has_device_config ) {
					$remaining_slots_per_device[ $device ][ $slot_name ] = $slot;
				}
			}
		}

		foreach ( $page_types as $page_type ) {
			$has_remaining_slots = false;
			foreach ( $all_devices as $device ) {
				if ( ! empty( $remaining_slots_per_device[ $device ] ) ) {
					$has_remaining_slots = true;
					break;
				}
			}

			if ( ! $has_remaining_slots ) {
				break;
			}

			$slots_for_page_type = array();
			foreach ( $remaining_slots_per_device as $device => $device_slots ) {
				if ( ! is_array( $device_slots ) ) {
					continue;
				}
				foreach ( $device_slots as $slot_name => $slot ) {
					if ( ! is_string( $slot_name ) ) {
						continue;
					}
					$slot_page_types = $this->get_page_types_for_slot( $slot_name );
					if ( in_array( $page_type, $slot_page_types, true ) ) {
						$slots_for_page_type[ $slot_name ] = $slot;
					}
				}
			}

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

			$this->check_page_type_xpath( $page_type, array_values( $slots_for_page_type ), $device_slot_results, $remaining_slots_per_device );
		}

		foreach ( $all_devices as $device ) {
			if ( ! isset( $remaining_slots_per_device[ $device ] ) || ! is_array( $remaining_slots_per_device[ $device ] ) ) {
				continue;
			}
			foreach ( $remaining_slots_per_device[ $device ] as $slot_name => $slot ) {
				$device_slot_results[ $device ][ $slot_name ] = 'failed';
			}
		}

		// Count failed active configs only once at the end
		foreach ( $all_devices as $device ) {
			if ( $device === 'tablet' || ! isset( $remaining_slots_per_device[ $device ] ) || ! is_array( $remaining_slots_per_device[ $device ] ) ) {
				continue;
			}

			foreach ( $remaining_slots_per_device[ $device ] as $slot_name => $slot ) {
				if ( ! isset( $slot['configs'] ) || ! is_array( $slot['configs'] ) ) {
					continue;
				}

				foreach ( $slot['configs'] as $config ) {
					if ( ! isset( $config['active'] ) || ! $config['active'] ) {
						continue;
					}

					if ( ! isset( $config['targets']['devices'] ) || ! is_array( $config['targets']['devices'] ) ) {
						continue;
					}

					if ( in_array( $device, $config['targets']['devices'], true ) ) {
						++$failed_active_configs;
					}
				}
			}
		}

		$optimized_results = array();
		foreach ( $all_devices as $device ) {
			$optimized_results[ $device ] = $device_slot_results[ $device ];
		}

		return array(
			'success'              => true,
			'results'              => $optimized_results,
			'failed_active_config' => $failed_active_configs,
		);
	}

	/**
	 * Check XPath validity for a specific page type with early stopping optimization
	 *
	 * @since 3.2.0
	 * @param string       $page_type
	 * @param array<mixed> $slots
	 * @param array<mixed> &$device_slot_results
	 * @param array<mixed> &$remaining_slots_per_device
	 * @return void
	 */
	private function check_page_type_xpath( $page_type, $slots, &$device_slot_results, &$remaining_slots_per_device ) {
		$page_url = $this->get_sample_page_url( $page_type );

		if ( ! $page_url ) {
			foreach ( $slots as $slot ) {
				$slot_name = isset( $slot['name'] ) ? $slot['name'] : '';
				if ( ! empty( $slot_name ) ) {
					foreach ( array( 'desktop', 'tablet', 'mobile' ) as $device ) {
						if ( isset( $remaining_slots_per_device[ $device ][ $slot_name ] ) ) {
							$device_slot_results[ $device ][ $slot_name ] = 'error';
							unset( $remaining_slots_per_device[ $device ][ $slot_name ] );
						}
					}
				}
			}
			return;
		}

		$page_content = $this->fetch_page_content( $page_url );

		if ( ! $page_content['success'] || ! isset( $page_content['content'] ) ) {
			return;
		}

		$doc = new DOMDocument();
		libxml_use_internal_errors( true );
		$doc->loadHTML( $page_content['content'] );
		libxml_clear_errors();
		$xpath = new DOMXPath( $doc );

		foreach ( $slots as $slot ) {
			$slot_name = isset( $slot['name'] ) ? $slot['name'] : '';

			if ( ! isset( $slot['configs'] ) || ! is_array( $slot['configs'] ) ) {
				continue;
			}

			foreach ( $slot['configs'] as $config ) {
				if ( ! isset( $config['targets']['devices'] ) || ! is_array( $config['targets']['devices'] ) ) {
					continue;
				}

				$target_devices = $config['targets']['devices'];

				foreach ( array( 'desktop', 'mobile' ) as $device ) {
					if ( ! isset( $remaining_slots_per_device[ $device ][ $slot_name ] ) ) {
						continue;
					}

					if ( ! in_array( $device, $target_devices, true ) ) {
						continue;
					}

					if ( ! isset( $config['injection'] ) || ! is_array( $config['injection'] ) ) {
						continue;
					}

					foreach ( $config['injection'] as $injection ) {
						if ( ! isset( $injection['xpath'] ) || ! is_array( $injection['xpath'] ) ) {
							continue;
						}

						foreach ( $injection['xpath'] as $xpath_expression ) {
							if ( empty( $xpath_expression ) || ! is_string( $xpath_expression ) ) {
								continue;
							}

							if ( strpos( $xpath_expression, '//self::div[@id="hbdbrk-' ) !== false ) {
								$device_slot_results[ $device ][ $slot_name ] = 'success';
								unset( $remaining_slots_per_device[ $device ][ $slot_name ] );
								break 3;
							}

							try {
								/** @var string $xpath_expression */
								$nodes = $xpath->query( $xpath_expression );

								if ( $nodes !== false && $nodes->length > 0 ) {
									$device_slot_results[ $device ][ $slot_name ] = 'success';
									unset( $remaining_slots_per_device[ $device ][ $slot_name ] );
									break 3;
								}
							} catch ( Exception $e ) {
								continue;
							}
						}
					}
				}
			}
		}

		foreach ( $slots as $slot ) {
			$slot_name = isset( $slot['name'] ) && is_string( $slot['name'] ) ? $slot['name'] : '';

			if ( empty( $slot_name ) || ! isset( $slot['configs'] ) || ! is_array( $slot['configs'] ) ) {
				continue;
			}

			foreach ( $slot['configs'] as $config ) {
				if ( ! isset( $config['targets']['devices'] ) || ! is_array( $config['targets']['devices'] ) ) {
					continue;
				}

				$target_devices = $config['targets']['devices'];

				foreach ( array( 'desktop', 'mobile' ) as $device ) {
					if ( ! isset( $remaining_slots_per_device[ $device ][ $slot_name ] ) ) {
						continue;
					}

					if ( ! in_array( $device, $target_devices, true ) ) {
						continue;
					}

					if ( isset( $page_content['content'] ) && $this->check_slot_shortcode_usage( $slot_name, $device, $page_content['content'] ) ) {
						$device_slot_results[ $device ][ $slot_name ] = 'success';
						unset( $remaining_slots_per_device[ $device ][ $slot_name ] );
						break;
					}
				}
			}
		}
	}

	/**
	 * Get a sample URL for testing a specific page type
	 *
	 * @since 3.2.0
	 * @param string $page_type Type of page to get URL for.
	 * @return string|false Sample URL or false if not found
	 */
	private function get_sample_page_url( $page_type ) {
		// Check if WordPress is properly initialized
		global $wp_rewrite;
		if ( ! $wp_rewrite ) {
			return false;
		}

		try {
			switch ( $page_type ) {
				case 'home':
					return home_url( '/' );

				case 'post':
					$recent_post = get_posts(
						array(
							'numberposts' => 1,
							'post_status' => 'publish',
							'post_type'   => 'post',
						)
					);

					if ( ! empty( $recent_post ) ) {
						return get_permalink( $recent_post[0]->ID );
					}
					break;

				case 'page':
					$recent_page = get_posts(
						array(
							'numberposts' => 1,
							'post_status' => 'publish',
							'post_type'   => 'page',
						)
					);

					if ( ! empty( $recent_page ) ) {
						return get_permalink( $recent_page[0]->ID );
					}
					break;

				case 'category':
					$categories = get_categories( array( 'number' => 1 ) );
					if ( ! empty( $categories ) ) {
						return get_category_link( $categories[0]->term_id );
					}
					break;
			}
		} catch ( \Exception $e ) {
			return false;
		}

		return false;
	}

	/**
	 * Fetch content from a URL
	 *
	 * @since 3.2.0
	 * @param string $url URL to fetch.
	 * @return array{success: bool, content?: string, error?: string} Fetch result
	 */
	private function fetch_page_content( $url ) {
		$is_local = ( strpos( $url, '.local' ) !== false );

		$args = array(
			'timeout' => 30,
			'headers' => array(
				'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
			),
		);

		if ( $is_local ) {
			$args['sslverify'] = false;
		}

		$response = wp_remote_get( $url, $args );

		if ( is_wp_error( $response ) ) {
			return array(
				'success' => false,
				'error'   => 'Failed to fetch URL: ' . $response->get_error_message(),
			);
		}

		$status_code = wp_remote_retrieve_response_code( $response );

		if ( $status_code !== 200 ) {
			return array(
				'success' => false,
				'error'   => 'HTTP error: ' . $status_code,
			);
		}

		$content = wp_remote_retrieve_body( $response );

		if ( empty( $content ) ) {
			return array(
				'success' => false,
				'error'   => 'Empty response body',
			);
		}

		return array(
			'success' => true,
			'content' => $content,
		);
	}

	/**
	 * Check if a slot is using shortcode or PHP function as fallback
	 *
	 * @since 3.2.0
	 * @param string $slot_name
	 * @param string $device
	 * @param string $page_content
	 * @return bool
	 */
	private function check_slot_shortcode_usage( $slot_name, $device, $page_content ) {
		// List of slots that support shortcode/PHP function
		$supported_slots = array( 'aside-desktop', 'intext', 'top-content', 'feed', 'in-feed', 'top-page' );

		if ( ! in_array( $slot_name, $supported_slots, true ) ) {
			return false;
		}

		$expected_classes = array();
		if ( $device === 'mobile' ) {
			$expected_classes[] = 'device-mobile';
		} else {
			$expected_classes[] = 'device-desktop';
			$expected_classes[] = 'device-tablet';
		}

		$doc = new DOMDocument();
		libxml_use_internal_errors( true );
		$doc->loadHTML( $page_content );
		libxml_clear_errors();
		$xpath = new DOMXPath( $doc );

		// Search for divs with the correct data-actirise-slot attribute
		$slot_divs = $xpath->query( "//div[@data-actirise-slot='" . $slot_name . "']" );

		if ( $slot_divs === false || $slot_divs->length === 0 ) {
			return false;
		}

		// Check if any of the found divs has the correct device class
		foreach ( $slot_divs as $div ) {
			/** @var \DOMElement $div */
			$class_attr = $div->getAttribute( 'class' );
			if ( ! empty( $class_attr ) ) {
				$classes = explode( ' ', $class_attr );
				foreach ( $expected_classes as $expected_class ) {
					if ( in_array( $expected_class, $classes, true ) ) {
						return true;
					}
				}
			}
		}

		return false;
	}

	/**
	 * Schedule XPath validation to run shortly after slots update
	 *
	 * @since 3.2.0
	 * @return void
	 */
	private function schedule_xpath_validation() {
		wp_schedule_single_event( time() + 30, 'actirise_validate_xpath_after_update' );
	}

	/**
	 * Schedule presized div update to run shortly after slots update
	 *
	 * @since 3.3
	 * @return void
	 */
	private function schedule_presized_div_sync() {
		wp_schedule_single_event( time() + 30, 'actirise_cron_update_presized_div' );
	}

	/**
	 * Validate XPath expressions and store results for notifications
	 * Called by cron after slots update or periodically
	 *
	 * @since 3.2.0
	 * @return void
	 */
	public function validate_xpath_and_notify() {
		$validation_results = $this->check_slots_xpath();

		if ( ! $validation_results['success'] ) {
			return;
		}

		$failed_slots = array(
			'desktop' => array(),
			'mobile'  => array(),
		);

		if ( isset( $validation_results['results'] ) && is_array( $validation_results['results'] ) ) {
			foreach ( $validation_results['results'] as $device => $device_slots ) {
				if ( $device === 'tablet' ) {
					continue;
				}

				if ( ! in_array( $device, array( 'desktop', 'mobile' ), true ) ) {
					continue;
				}

				if ( ! is_array( $device_slots ) ) {
					continue;
				}

				foreach ( $device_slots as $slot_name => $status ) {
					if ( ! in_array( $status, array( 'success' ), true ) ) {
						$failed_slots[ $device ][] = $slot_name;
					}
				}
			}
		}

		$failed_active_config = isset( $validation_results['failed_active_config'] ) ? $validation_results['failed_active_config'] : false;

		Options::update( 'xpath_check_results', $failed_slots );
		Options::update( 'xpath_failed_active_config', $failed_active_config );
	}

	/**
	 * Get latest XPath check results
	 *
	 * @since 3.2.0
	 * @return array{failed_slots: array<mixed>} check results
	 */
	public function get_xpath_check_results() {
		$failed_slots = Options::get( 'xpath_check_results', array() );

		if ( ! is_array( $failed_slots ) ) {
			$failed_slots = array();
		}

		return array(
			'failed_slots' => $failed_slots,
		);
	}
}
