<?php
/**
 *
 * @package    Expandable_Paywall
 * @subpackage Expandable_Paywall/public
 * @author     Pramadillo <support@pramadillo.com>
 */

namespace Emoxie\Expandable_Paywall;

use WP_User;

/**
 * Class Expandable_Paywall_Public
 *
 * Represents the public-facing functionality of the Expandable Paywall plugin.
 */
class Expandable_Paywall_Public {

	private string $template_directory;
	private $included_categories;
	protected int $default_word_count = 55;
	protected string $default_more_text;
	private $roles_that_bypass_paywall;

	public function __construct() {
		$this->template_directory        = EXPANDABLE_PAYWALL_PATH . 'public/templates/';
		$this->included_categories       = Helper::protected_categories();
		$this->default_more_text         = '';  //This needs to be declared since we have a typed property of string.
		$this->roles_that_bypass_paywall = Helper::roles_that_bypass_paywall();
	}

	/**
	 * Retrieves an array of roles that bypass the paywall.
	 *
	 * It merges the roles from the $roles_that_bypass_paywall property of the current object
	 * with the roles returned by the `authentication_role_non_paid` method of the Helper class.
	 *
	 * @return array|null An array of roles that bypass the paywall, or null if no roles are found.
	 */
	public function roles_that_bypass_paywall(): ?array {
		return array_merge( $this->roles_that_bypass_paywall, (array) Helper::authentication_role_non_paid() );
	}

	public function enqueue_styles() {
		wp_register_style( EXPANDABLE_PAYWALL_SLUG . '-styles', EXPANDABLE_PAYWALL_URL . 'public/css/expandable-paywall-public.css', [], EXPANDABLE_PAYWALL_VERSION );
	}

	public function enqueue_scripts() {
		wp_register_script( EXPANDABLE_PAYWALL_SLUG . '-scripts', EXPANDABLE_PAYWALL_URL . 'public/js/expandable-paywall-public.js', [ 'jquery' ], EXPANDABLE_PAYWALL_VERSION, false );
		wp_localize_script( EXPANDABLE_PAYWALL_SLUG . '-scripts', 'ep', [
			'ajax_url'       => admin_url( 'admin-ajax.php' ),
			'login_nonce'    => wp_create_nonce( 'expandable_paywall_nonce' ),
			'register_nonce' => wp_create_nonce( 'expandable_paywall_register_nonce' ),
			'site_url'       => get_site_url(),
		] );

	}

	/**
	 * init function which is executed on the WordPress init hook
	 */
	public function init(): void {
		$this->register_shortcodes();

		$activePaywall = Helper::active_paywall();
		$showLocks     = get_option( EXPANDABLE_PAYWALL_SLUG . '_paywall_show_locks' );

		if ( $activePaywall === 'basic' && ! is_admin() ) {
			add_action( 'template_redirect', [ $this, 'process_post' ] );
		}

		if ( $showLocks && $activePaywall !== 'none' ) {
			add_filter( 'the_title', [ $this, 'add_locks_to_post_title' ], 10, 2 );
		}
	}

	/**
	 * Register all core shortcodes available.
	 */
	public function register_shortcodes(): void {
		add_shortcode( 'expandable-paywall-login', [ $this, 'shortcode_login' ] );
		add_shortcode( 'expandable-paywall-logout', [ $this, 'shortcode_logout' ] );
		add_shortcode( 'expandable-paywall-register', [ $this, 'shortcode_register' ] );
		add_shortcode( 'expandable-paywall-protect', [ $this, 'shortcode_protect' ] );
	}

	/**
	 * The core Login Form shortcode which then runs the action `expandable-paywall_shortcode_login`
	 * @return false|string
	 */
	public function shortcode_login(): bool|string {
		wp_enqueue_script( EXPANDABLE_PAYWALL_SLUG . '-scripts' );
		ob_start();
		do_action( EXPANDABLE_PAYWALL_SLUG . '_shortcode_login' );

		return ob_get_clean();
	}

	public function shortcode_logout(): void {
		if ( ! is_admin() ) {
			add_action( 'wp_logout', function () {
				wp_safe_redirect( site_url() );
				exit;
			} );
			wp_logout();
		}
	}

	public function shortcode_protect( $atts, $content = "" ): bool|string {
		if ( empty( $content ) ) {
			return false;
		}
		ob_start();
		if ( ! Helper::is_authenticated() ) {
			$excerpt = ( isset( $atts['show_excerpt'] ) && 'false' == $atts['show_excerpt'] ) ? '' : $content;
			echo $this->show_pay_wall( $excerpt );
		} else {
			echo $content;
		}

		return ob_get_clean();
	}


	public function forgot_password_link(): string {
		return '<a href="' . esc_url( Helper::forgot_password_url() ) . '">' . esc_html_x( 'Lost Password', 'expandable-paywall' ) . '</a>';
	}

	/**
	 * The core Register form shortcode which then runs the action `expandable-paywall_shortcode_register`
	 * @return false|string
	 */
	public function shortcode_register(): bool|string {
		wp_enqueue_script( EXPANDABLE_PAYWALL_SLUG . '-scripts' );

		ob_start();
		do_action( EXPANDABLE_PAYWALL_SLUG . '_shortcode_register' );

		return ob_get_clean();
	}

	/**
	 * Handles displaying the shortcode login form
	 * Gets called by the action expandable-paywall_shortcode_login
	 */
	public function render_shortcode_login(): void {
		if ( ! apply_filters( EXPANDABLE_PAYWALL_SLUG . '_override_core_login_shortcode', false ) ) { //This is necessary otherwise when add-ons tap into action it would produce double forms.

			if ( ! is_admin() ) { // Restrict this code from executing in admin which could cause issues with Gutenberg and similar editors
				if ( ! is_user_logged_in() ) {
					$forgot_password = Helper::forgot_password_url();
					require_once locate_template( EXPANDABLE_PAYWALL_TEMPLATE_PATH . 'templates/login.php' ) ?: ( $this->template_directory . 'login.php' );
				} else {
					do_action( EXPANDABLE_PAYWALL_SLUG . '_shortcode_login_already_logged_in' );
					echo apply_filters( EXPANDABLE_PAYWALL_SLUG . '_shortcode_login_already_logged_in', 'You are already logged in.' );
				}

			}
		}
	}

	/**
	 * Handles displaying the shortcode register form
	 * Gets called by the action expandable-paywall_shortcode_register
	 */
	public function render_shortcode_register(): void {
		if ( ! apply_filters( EXPANDABLE_PAYWALL_SLUG . '_override_core_register_shortcode', false ) ) { //This is necessary otherwise when add-ons tap into action it would produce double forms.
			if ( ! is_admin() ) { // Restrict this code from executing in admin which could cause issues with Gutenberg and similar editors
				if ( ! is_user_logged_in() ) {
					wp_enqueue_style( EXPANDABLE_PAYWALL_SLUG . '-styles' );
					wp_enqueue_script( EXPANDABLE_PAYWALL_SLUG . '-scripts' );
					require_once locate_template( EXPANDABLE_PAYWALL_TEMPLATE_PATH . 'templates/register.php' ) ?: ( $this->template_directory . 'register.php' );
				} else {
					do_action( EXPANDABLE_PAYWALL_SLUG . '_shortcode_register_already_logged_in' );
					echo apply_filters( EXPANDABLE_PAYWALL_SLUG . '_shortcode_register_already_logged_in', 'You are already logged in.' );
				}
			}
		}
	}

	/**
	 * Ajax callback function for register used with shortcode
	 */
	public function ajax_register(): void {

		if ( check_ajax_referer( 'expandable_paywall_register_nonce', 'nonce' ) ) {

			$exists = $this->does_email_exist( sanitize_email( $_REQUEST['user_login'] ) );
			if ( $exists ) {
				$email_exists_message = _x( 'Sorry your email already exists, please try to login instead!', 'Ajax Register Duplicate Email Message', 'expandable-paywall' );
				wp_send_json( apply_filters( EXPANDABLE_PAYWALL_SLUG . '_registration_email_exists_message', $email_exists_message ), 409 );
				die();
			}

			if ( $this->register( $_REQUEST ) ) {
				do_action( EXPANDABLE_PAYWALL_SLUG . '_registration_successful' );
				$success_message = _x( 'Your registration was successful!  You can now <a href="/login">log in</a>.', 'Ajax Register Successful Message', 'expandable-paywall' );
				wp_send_json( apply_filters( EXPANDABLE_PAYWALL_SLUG . '_registration_successful_text', $success_message ), 200 );

			} else {
				do_action( EXPANDABLE_PAYWALL_SLUG . '_registration_failed' );
				$error_message = _x( 'Sorry there was an error.  Please try again.', 'Ajax Register Failed/Error Message', 'expandable-paywall' );
				wp_send_json( apply_filters( EXPANDABLE_PAYWALL_SLUG . '_registration_error_text', $error_message ), 500 );
			}

			die();

		}
	}

	/**
	 * Ajax callback function for login used with shortcode
	 */
	public function ajax_login(): void {
		if ( check_ajax_referer( 'expandable_paywall_nonce', 'nonce' ) ) {
			$args = [
				'user_login'    => sanitize_text_field( $_REQUEST['user_login'] ),
				'user_password' => sanitize_text_field( $_REQUEST['user_pass'] ),
				'remember'      => true,
			];
			$user = wp_signon( $args, is_ssl() );

			if ( is_wp_error( $user ) ) {
				wp_send_json_error( stripslashes( $user->get_error_message() ), 403 );
			} else {
				wp_send_json_success( 'success', 200 );
			}
		}
		die();
	}

	/**
	 * @param $email
	 *
	 * @return bool
	 */
	public function does_email_exist( $email ): bool {
		return (bool) get_user_by( 'email', $email );
	}

	/**
	 * Display the paywall content for restricted users.
	 *
	 * @param string $content The original content to be displayed.
	 *
	 * @return string The modified content with paywall.
	 */
	public function show_pay_wall( $content ): string {
		wp_enqueue_style( EXPANDABLE_PAYWALL_SLUG . '-styles' );
		$show_fade         = get_option( EXPANDABLE_PAYWALL_SLUG . '_paywall_show_fade' );
		$no_access_message = get_option( EXPANDABLE_PAYWALL_SLUG . '_paywall_basic_paywall_content', _x( '<h3>Thank you for reading!</h3> To continue reading this article you must be registered and logged into this site. If you already have an account please <a href="{{login_url}}">log in now</a>, otherwise you can <a href="{{register_url}}">click here</a> to register for new account.', 'Basic Paywall No Access Message', 'expandable-paywall' ) );
		$no_access_message = str_replace( [ '{{register_url}}', '{{login_url}}' ], [ wp_registration_url(), wp_login_url( $this->current_url() ) ], $no_access_message );

		$content_html = '';

		if ( apply_filters( 'expandable_paywall_show_excerpt', true ) ) {
			do_action( 'expandable_paywall_before_excerpt' );

			if ( $show_fade ) {
				$content_html .= '<div class="restricted-content">';
			}

			$content_html .= wpautop( wp_trim_words( $content, (int) apply_filters( 'emoxie_reg_wall_word_length', $this->default_word_count ), apply_filters( 'emoxie_reg_wall_more_text', $this->default_more_text ) ) );

			if ( $show_fade ) {
				$content_html .= '</div>';
			}

			do_action( 'expandable_paywall_after_excerpt' );
		}

		do_action( 'expandable_paywall_before_reg_wall' );

		$content_html .= '<div class="emoxie-pay-wall">';
		$content_html .= $no_access_message;
		$content_html .= '</div>';

		do_action( 'expandable_paywall_after_reg_wall' );

		return $content_html;
	}


	/**
	 * Output structured data for the current post.
	 *
	 * @return void
	 */
	public function output_structured_data(): void {
		if ( ! is_singular( 'post' ) ) {
			return;
		}

		global $post;
		$return = <<<EOT
    <div class="expandable-paywall-premium-content" style="display:none;" itemtype="https://schema.org/Article">
          <meta itemprop="name" content="$post->post_title" />
          <meta itemprop="headline" content="$post->post_title" />
          <meta itemprop="isAccessibleForFree" content="false"/>
          <meta itemprop="publicAccess" content="true" />
          <meta itemprop="description" content="$post->post_excerpt" />
          <div itemprop="hasPart" itemscope itemtype="https://schema.org/WebPageElement">
                <meta itemprop="isAccessibleForFree" content="false"/>
                <meta itemprop="cssSelector" content=".expandable-paywall-premium-content"/>
          </div>
          $post->post_content
    </div>
    EOT;

		echo $return;
	}

	/**
	 * Attached to the template_redirect function in $this->init
	 * @return void
	 */
	public function process_post(): void {
		if ( $this->should_show_paywall() ) {
			add_filter( 'the_content', [ $this, 'show_pay_wall' ] );
		}
	}


	/**
	 * Adds lock icon to the post title if the post meets the conditions.
	 *
	 * @param string $title The post title.
	 * @param int    $post_id The post ID.
	 *
	 * @return string The modified post title.
	 */
	public function add_locks_to_post_title( $title, $post_id ): string {
		$post_type             = get_post_type( $post_id );
		$lock_icon             = '&#x1F512;';
		$in_loop_and_type      = in_the_loop() && in_array( $post_type, Helper::post_types() );
		$in_category_and_state = in_category( $this->included_categories, $post_id ) && ! is_admin() && ! is_single();
		$should_add_icon       = $in_loop_and_type && $in_category_and_state;
		$lock_icon             = apply_filters( EXPANDABLE_PAYWALL_SLUG . '_locked_icon', $lock_icon );

		return ( $should_add_icon && ! str_contains( $title, $lock_icon ) ) ? $lock_icon . ' ' . $title : $title;
	}


	/**
	 * Check if the user should show the paywall.
	 *
	 * @param int|null $post_id Optional. The ID of the post. If not provided, the current post ID will be used.
	 *
	 * @return bool Returns true if the paywall should be shown, false otherwise.
	 */
	protected function should_show_paywall( $post_id = null ): bool {

		if ( is_null( $post_id ) ) {
			$post_id = get_the_ID();
		}

		if ( is_search() ) {
			return false;
		}

		$external_bypass  = apply_filters( EXPANDABLE_PAYWALL_SLUG . '_external_bypass_paywall', false, $post_id );
		$show_on_archives = apply_filters( EXPANDABLE_PAYWALL_SLUG . '_show_paywall_on_archives', false, $post_id );

		$is_authenticated = Helper::is_authenticated();

		if ( $this->bypass_paywall() || $external_bypass ) {
			return false;
		}

		if ( $is_authenticated ) {
			Logger::log( 'should_show_paywall_is_authenticated', 'DO NOT SHOW PAYWALL - User is authenticated' );

			return false;
		}

		$post_type = get_post_type( $post_id );

		if ( ! in_array( $post_type, Helper::post_types() ) ) {
			Logger::log( 'should_show_paywall_post_type', 'DO NOT SHOW PAYWALL - Is not part of paywall post types' );

			return false;
		}

		if ( $show_on_archives && is_archive() ) {
			Logger::log( 'should_show_paywall_on_archives_and_is_archive', 'SHOW PAYWALL - Show on Archives filter is true and is_archive() is true' );

			return true;
		}

		if ( $this->post_is_in_descendant_category( $this->included_categories ) && ! is_archive() ) {
			Logger::log( 'should_show_paywall_is_in_category', 'SHOW PAYWALL - Post is in a paywall category' );

			return true;
		}


		return false;
	}


	/**
	 * Determines if a post belongs to a descendant category.
	 *
	 * @param int|array $cats A single or multiple category IDs or slugs.
	 * @param int|null  $post_id The ID of the post to check. Defaults to current post ID.
	 *
	 * @return bool Returns true if the post belongs to a descendant category, false otherwise.
	 */

	private function post_is_in_descendant_category( int|array $cats, $post_id = null ): bool {
		$post_id = $post_id ?: get_the_ID();

		// collect the term ids in an array
		$cat_ids = [];
		foreach ( (array) $cats as $cat ) {
			$cat_ids[]   = (int) $cat;
			$descendants = get_term_children( (int) $cat, 'category' );
			if ( ! empty( $descendants ) ) {
				$cat_ids = array_merge( $cat_ids, $descendants );
			}
		}

		// use a single in_category call to check if post is in any of the categories
		if ( in_category( $cat_ids, $post_id ) ) {
			return true;
		}

		return false;
	}


	/**
	 * @param $type
	 *
	 * @return array
	 */
	private function determine_wordpress_role( $type ): array {

		return [
			'role' => Helper::authentication_role_non_paid(),
		];
	}


	/**
	 * Register a new user.
	 *
	 * @param array $data The user data to register.
	 *                    - 'first_name' (string) The first name of the user.
	 *                    - 'last_name' (string) The last name of the user.
	 *                    - 'user_login' (string) The login username of the user.
	 *                    - 'user_pass' (string) The password of the user.
	 *
	 * @return WP_User|bool Returns the newly created user object on success, or false on failure.
	 */
	private function register( $data ): WP_User|bool {
		$first_name = sanitize_text_field( $data['first_name'] );
		$last_name  = sanitize_text_field( $data['last_name'] );
		$username   = sanitize_email( $data['user_login'] );
		$password   = $data['user_pass'];

		$user = $this->create_wordpress_user( $username, $password, $first_name, $last_name );

		if ( is_wp_error( $user ) ) {
			return false;
		}

		return $user;

	}

	/**
	 * Create a WordPress user.
	 *
	 * @param string      $username The email address used as the username for the new user.
	 * @param string      $password The password for the new user.
	 * @param string|null $first_name Optional. The first name of the new user. Default is null.
	 * @param string|null $last_name Optional. The last name of the new user. Default is null.
	 * @param string      $type Optional. The type of user to create. Default is 'non-paid'.
	 *
	 * @return WP_User|bool Returns a WP_User object if a new user is created, false otherwise.
	 */
	private function create_wordpress_user( $username, $password, $first_name = null, $last_name = null, string $type = 'non-paid' ): WP_User|bool {

		$user = get_user_by( 'email', $username );
		if ( ! $user ) {
			$user_data = [
				'first_name' => $first_name,
				'last_name'  => $last_name,
				'user_email' => $username,
				'user_login' => $username,
				'user_pass'  => $password,
			];

			$role = $this->determine_wordpress_role( $type );

			$new_user_data = array_merge( $user_data, $role );
			$new_user_id   = wp_insert_user( $new_user_data ); // A new user has been created

			// Load the new user info
			$user = new WP_User ( $new_user_id );
			do_action( 'expandable_paywall_after_create_wordpress_user', $user );

		}

		return $user;
	}


	/**
	 * @return string
	 */
	public function current_url(): string {
		$protocol = is_ssl() ? 'https://' : 'http://';

		return $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
	}

	/**
	 * Check if the user should bypass the paywall.
	 *
	 * @return bool Returns true if the user has bypassed the paywall, false otherwise.
	 */
	private function bypass_paywall(): bool {
		global $post;

		// Get user submitted bypass code
		$user_submitted_bypass_code = get_query_var( 'bypass' );

		// Check if the user submitted a bypass code
		if ( empty( $user_submitted_bypass_code ) ) {
			return false;
		}

		// Get the current post bypass code only when user has submitted a bypass code
		$current_post_bypass_code = get_post_meta( $post->ID, 'bypass_code', true );

		// No Bypass Code set for the article
		if ( is_null( $current_post_bypass_code ) ) {
			return false;
		}

		// Check the user submitted bypass code against the current post bypass code
		if ( $user_submitted_bypass_code === $current_post_bypass_code ) {
			Logger::log( 'bypass_paywall', 'DO NOT SHOW PAYWALL - Bypass Code used' );

			return true;
		} else {
			Logger::log( 'bypass_paywall', 'SHOW PAYWALL - Bypass Code used but is incorrect for post' );

			return false;
		}
	}

	/**
	 * Adds a query variable to bypass certain functionality
	 *
	 * @param array $query_vars The existing query variables
	 *
	 * @return array The updated query variables with the bypass variable added
	 */
	public function add_bypass_query_vars( $query_vars ): array {
		return array_merge( $query_vars, [ 'bypass' ] );
	}


	//This is a placeholder, do not remove.
	public function after_wp_user_registration() {
		return;
	}


}