<?php

namespace TotalTheme;

defined( 'ABSPATH' ) || exit;

/**
 * License Manager.
 */
class License_Manager {

	/**
	 * Stores the site URL.
	 */
	private $site_url = null;

	/**
	 * Stores the site license.
	 */
	private $license = null;

	/**
	 * Stores the site license status.
	 */
	private $license_status = null;

	/**
	 * Is license network enabled.
	 */
	private $is_license_network_actived = null;

	/**
	 * Is license enabled in staging mode.
	 */
	private $is_staging_activation = null;

	/**
	 * Check if this is a dev environment.
	 */
	private $is_dev_environment = null;

	/**
	 * Instance.
	 */
	private static $instance = null;

	/**
	 * Create or retrieve the instance of our class.
	 */
	public static function instance() {
		if ( null === static::$instance ) {
			static::$instance = new self();
		}
		return static::$instance;
	}

	/**
	 * Constructor.
	 */
	private function __construct() {}

	/**
	 * Return the site url used for the license.
	 */
	public function get_site_url(): string {
		if ( null === $this->site_url ) {
			$url = $this->is_license_network_actived() ? network_site_url() : site_url();
			if ( $url && is_string( $url ) ) {
				$this->site_url = trim( $url );
			} else {
				$this->site_url = '';
			}
		}
		return $this->site_url;
	}

	/**
	 * Return the site url for use with the activation and deactivation function.
	 * 
	 * This works differently from get_site_url because of the added is_network_admin() check.
	 */
	public function get_site_url_for_api() {
		$url = ( is_multisite() && is_network_admin() ) ? network_site_url() : site_url();
		if ( $url ) {
			return rawurlencode( $url );
		}
	}

	/**
	 * Checks if a license is valid.
	 * 
	 * license is version 4 UUID with the format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
	 * where x is any hexadecimal digit and y is one of 8, 9, A, or B.
	 */
	public function is_license_valid( string $license = '' ): bool {
		if ( ! $license ) {
			$license = $this->get_license();
			if ( ! $license ) {
				return false;
			}
		}
		$pattern = '/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i';
		return (bool) preg_match( $pattern, $license );
	}

	/**
	 * Retrieve the license from the database.
	 */
	private function get_license_from_db() {
		$license = get_option( 'totaltheme_license', 'not-set' );
		if ( 'not-set' === $license ) {
			$license = get_option( 'active_theme_license' ); // deprecated
			if ( $license ) {
				$updated = update_option( 'totaltheme_license', sanitize_text_field( $license ) );
				if ( true === $updated ) {
					delete_option( 'active_theme_license' );
				}
			}
		}
		return $license;
	}

	/**
	 * Update dp license.
	 */
	private function update_license_in_db( string $license ): void {
		update_option( 'totaltheme_license', sanitize_text_field( $license ) );
	}

	/**
	 * Remove license from the database.
	 */
	private function delete_license_from_db( bool $delete_status = true ): bool {
		$deleted = delete_option( 'totaltheme_license' );
		if ( $deleted ) {
			delete_option( 'totaltheme_license_is_staging' );
			if ( $delete_status ) {
				$this->delete_temporary_license_status();
			}
		}
		delete_option( 'active_theme_license' ); // deprecated
		return $deleted;
	}

	/**
	 * Get theme license.
	 */
	public function get_license() {
		if ( null === $this->license ) {
			$license = $this->get_license_from_db();
			$this->is_license_network_actived = false;
			if ( ! $license && is_multisite() && ! is_main_site() ) {
				$license = $this->get_network_license();
			}
			$this->license = $license ? sanitize_text_field( $license ) : '';
		}
		return $this->license;
	}

	/**
	 * Gets the network saved theme license.
	 */
	public function get_network_license() {
		if ( is_multisite() ) {
			switch_to_blog( get_main_site_id() );
				$license = $this->get_license_from_db();
				if ( $license ) {
					$this->is_license_network_actived = true;
				}
			restore_current_blog();
			return $license;
		}
	}

	/**
	 * Checks if the license is network activated.
	 */
	public function is_license_network_actived(): bool {
		if ( null !== $this->is_license_network_actived ) {
			return (bool) $this->is_license_network_actived;
		}
		$this->get_license();
		return $this->is_license_network_actived;
	}

	/**
	 * Returns temporary license status.
	 */
	private function get_temporary_license_status() {
		return get_transient( 'totaltheme_license_status' );
	}

	/**
	 * Set a temporary license status.
	 */
	private function set_temporary_license_status( string $status, int $expires = 0 ): bool {
		if ( ! $expires ) {
			$expires = 10 * MINUTE_IN_SECONDS;
		}
		return (bool) set_transient( 'totaltheme_license_status', $status, $expires );
	}

	/**
	 * Deletes the temporary license status.
	 */
	private function delete_temporary_license_status(): bool {
		return (bool) delete_transient( 'totaltheme_license_status' );
	}

	/**
	 * Check if the license is active.
	 */
	public function is_license_active( $force_check = false ): bool {
		$license_status = $this->get_license_status( $force_check );
		return (
			'active' === $license_status
			|| ( 'staging' === $license_status && $this->is_dev_environment() )
		);
	}

	/**
	 * Returns the label for a license status.
	 */
	public function get_license_status_label( string $status ): string {
		switch ( $status ) {
			case 'staging':
				return esc_html__( 'Active (Staging)', 'total' );
			case 'invalid_staging':
				return esc_html__( 'Invalid (Staging)', 'total' );
				break;
			case 'active':
				return esc_html__( 'Active', 'total' );
			case 'duplicate':
				return esc_html__( 'Invalid: Already in Use', 'total' );
			case 'invalid':
				return esc_html__( 'Invalid', 'total' );
			case 'pending':
				return esc_html__( 'Pending', 'total' );
			case 'inactive':
			case 'unregistered':
			default:
				return esc_html__( 'Not Active', 'total' );
		}
	}

	/**
	 * Get license status.
	 *
	 * inactive         : No license.
	 * active           : Registered and functioning correctly.
	 * staging          : Active in staging mode - aka, domain not registered with the API.
	 * invalid          : Not properly formatted license.
	 * invalid_stagging : Active in staging mode but not on a staging site.
	 * unregistered     : Not registered.
	 * duplicate        : Already registered elsewhere.
	 * pending          : License registration is pending.
	 */
	public function get_license_status( bool $force_check = false ): string {
		if ( ! $force_check && null !== $this->license_status ) {
			return $this->license_status;
		}
		if ( ! $this->get_license() ) {
			return $this->license_status = 'inactive';
		}
		if ( ! $this->is_license_valid() ) {
			return $this->license_status = 'invalid';
		}
		if ( $force_check ) {
			$this->delete_temporary_license_status();
			delete_transient( 'totaltheme_is_license_registered' );
		}
		if ( $this->is_staging_activation() ) {
			if ( $this->is_dev_environment() ) {
				return $this->license_status = 'staging';
			} else {
				if ( $this->is_license_registered( $force_check ) ) {
					delete_option( 'totaltheme_license_is_staging' );
					return $this->license_status = 'active';
				} else {
					return $this->license_status = 'invalid_staging';
				}
			}
		}
		$is_registered = $this->is_license_registered( $force_check );
		$transient_check = $this->get_temporary_license_status();
		if ( false !== $transient_check ) {
			return $this->license_status = $transient_check;
		}
		$this->license_status = $is_registered ? 'active' : 'unregistered';
		return $this->license_status;
	}

	/**
	 * Check if it's a staging registration.
	 */
	public function is_staging_activation(): bool {
		if ( null === $this->is_staging_activation ) {
			$this->is_staging_activation = false;
			if ( $this->is_license_network_actived() ) {
				if ( function_exists( 'get_blog_option' ) ) {
					$this->is_staging_activation = (bool) get_blog_option( get_main_site_id(), 'totaltheme_license_is_staging' );
				}
			} else {
				$this->is_staging_activation = (bool) get_option( 'totaltheme_license_is_staging' );
			}
		}
		return $this->is_staging_activation;
	}

	/**
	 * Check if the license is registered.
	 */
	private function is_license_registered( bool $force_check = false ): bool {
		$license = $this->get_license();
		if ( ! $license || ! $this->is_license_valid( $license ) ) {
			return false;
		}
		if ( ! $force_check ) {
			$transient_check = get_transient( 'totaltheme_is_license_registered' );
			if ( false !== $transient_check ) {
				return 'yes' === $transient_check;
			}
		}
		// It's true by default incase there are issues with the API - @todo if fails make transient time check sooner?
		$check    = true;
		$response = wp_remote_get( "https://my.totalwptheme.com/wp-json/twpt-license-manager/check/{$license}" );
		if ( ! is_wp_error( $response ) ) {
			$response_code = wp_remote_retrieve_response_code( $response );
			if ( 200 === (int) $response_code ) {
				$body   = json_decode( wp_remote_retrieve_body( $response ) );
				$status = $body->status ?? '';
				$domain = $body->domain ?? '';
				if ( 'inactive' === $status ) {
					$this->delete_license_from_db();
					$check = false;
				} elseif ( $domain && is_string( $domain ) && ! $this->is_domain_match( $domain ) ) {
					$network_license = ( is_multisite() && ! is_main_site() ) ? $this->get_network_license() : '';
					if ( $network_license === $license ) {
						$check = true;
					} else {
						if ( $this->is_dev_environment() ) {
							update_option( 'totaltheme_license_is_staging', 1, false );
							$check = true;
						} else {
							$this->set_temporary_license_status( 'duplicate', MONTH_IN_SECONDS + 60 );
							$check = false;
						}
					}
				}
			}
		}
		set_transient( 'totaltheme_is_license_registered', $check ? 'yes' : 'no', MONTH_IN_SECONDS );
		return $check;
	}

	/**
	 * Activate License.
	 */
	public function activate_license( $license, $is_staging = false ): array {
		if ( ! $this->is_license_valid( $license ) ) {
			return [
				'message'       => esc_html__( 'This license code is not valid.', 'total' ),
				'messageClass'  => 'notice-error',
				'licenseStatus' => $this->get_license_status_label( 'inactive' ),
			];
		}
		$request_args = [
			'body' => [
				'key'    => $license,
				'domain' => $this->get_site_url_for_api(),
			],
		];
		if ( $is_staging ) {
			if ( ! $this->is_dev_environment() ) {
				return [
					'message'       => esc_html__( 'The current domain does not appear to be a valid staging environment.', 'total' ),
					'messageClass'  => 'notice-error',
					'licenseStatus' => $this->get_license_status_label( 'inactive' ),
				];
			}
			$request_args['body']['is_staging'] = 1;
		}
		$remote_response = wp_safe_remote_post( 'https://my.totalwptheme.com/wp-json/twpt-license-manager/activate/', $request_args );
		$response = [];
		if ( is_wp_error( $remote_response ) ) {
			$response['message'] = $remote_response->get_error_message();
		} else {
			$remote_response_code      = (int) wp_remote_retrieve_response_code( $remote_response );
			$response['response_code'] = $remote_response_code;
			$response['messageClass']  = 'notice-error';
			if ( 200 === $remote_response_code ) {
				$body = json_decode( wp_remote_retrieve_body( $remote_response ) );
				if ( ! empty( $body->activated ) || ( $is_staging && ! empty( $body->success ) ) ) {
					$response['success'] = true;
					if ( $is_staging ) {
						$response_status = 'staging';
						update_option( 'totaltheme_license_is_staging', 1, false );
					} else {
						$response_status = 'active';
					}
					$response['licenseStatus'] = $this->get_license_status_label( $response_status );
					$this->update_license_in_db( $license );
					$this->delete_temporary_license_status();
					set_transient( 'totaltheme_is_license_registered', 'yes', MONTH_IN_SECONDS );
				} elseif ( ! empty( $body->error ) ) {
					switch ( $body->error ) {
						case 'api_error':
							$response['message'] = esc_html__( 'The license code is not properly formated or couldn\'t be validated by the Envato API.', 'total' );
							break;
						case 'wrong_product':
							$response['message'] = esc_html__( 'This license code is for a different product.', 'total' );
							break;
						case 'invalid':
							$response['message'] = esc_html__( 'This license code is not valid.', 'total' );
							break;
						case 'invalid_staging_domain':
							$response['message'] = esc_html__( 'The current domain does not appear to be a valid staging environment.', 'total' );
							break;
						case 'duplicate':
							$response['message'] = esc_html__( 'This license is currently in use. To deactivate it, click the "Manage Licenses" link beneath the form and remove it via the "License Manager".', 'total' );
							break;
						default:
							$response['message'] = esc_html( $body->error );
							break;
					}
				} else {
					$response['message'] = esc_html__( 'Something wen\'t wrong, please try again.', 'total' );
				}
			} else {
				$response['message'] = $this->get_response_code_message( $remote_response_code );
			}
		}
		return $response;
	}

	/**
	 * Deactivate License.
	 */
	public function deactivate_license( $license, $license_status ): array {
		if ( 'staging' === $license_status || ! $this->is_license_valid( $license ) || 'active' !== $license_status ) {
			$this->delete_license_from_db();
			return [
				'success'       => true,
				'licenseStatus' => $this->get_license_status_label( 'inactive' ),
			];
		}
		$network_license = ( is_multisite() && ! is_main_site() ) ? $this->get_network_license() : '';
		if ( $network_license && $network_license === $license ) {
			$this->delete_license_from_db();
			return [
				'success'       => true,
				'licenseStatus' => $this->get_license_status_label( 'inactive' ),
			];
		}
		$remote_response = wp_safe_remote_post( 'https://my.totalwptheme.com/wp-json/twpt-license-manager/deactivate/', [
			'body' => [
				'key'    => $license,
				'domain' => $this->get_site_url_for_api(),
			],
		] );
		$response = [];
		if ( is_wp_error( $remote_response ) ) {
			$response['message'] = $remote_response->get_error_message();
		} else {
			$remote_response_code = (int) wp_remote_retrieve_response_code( $remote_response );
			$response['response_code'] = $remote_response_code;
			if ( 200 === $remote_response_code ) {
				$body = json_decode( wp_remote_retrieve_body( $remote_response ) );
				if ( ! empty( $body->deactivated ) ) {
					$this->delete_license_from_db();
					$response['success']       = true;
					$response['licenseStatus'] = $this->get_license_status_label( 'inactive' );
				} elseif ( ! empty( $body->error ) ) {
					$response['message'] = esc_html( $body->error );
				} else {
					$response['message'] = esc_html( 'Something wen\'t wrong, please try again.', 'total' );
				}
			} else {
				$response['message'] = $this->get_response_code_message( $remote_response_code );
			}
		}
		return $response;
	}

	/**
	 * Returns response code message.
	 */
	private function get_response_code_message( int $code ): string {
		switch ( $code ) {
			case 301:
				return esc_html__( 'Error 301: Firewall blocking access.', 'total' );
				break;
			case 403:
				return  esc_html__( 'Error 403: Your server has been blocked by our firewall for security reasons.', 'total' );
				break;
			case 404:
				return esc_html__( 'Error 404: The API is down or your theme is outdated or it has been modified and is using the wrong URL.', 'total' );
				break;
			case 405:
				return esc_html__( 'Error 405: Method Not Allowed.', 'total' );
				break;
			default:
				return esc_html__( 'Can not connect to the verification server at this time. Please make sure outgoing connections are enabled on your server and try again. If it still does not work please wait a few minutes and try again.', 'total' );
			break;
		}
		return sprintf(
			/* translators: %s is a server error code number */
			esc_html__( 'Error Code %s', 'total' ),
			$code
		);
	}

	/**
	 * Helper to cache and return true.
	 */
	private function set_dev_environment( bool $value ): bool {
		return $this->is_dev_environment = $value;
	}

	/**
	 * Normalize a URL for license comparison.
	 */
	private function normalize_url( string $url ): string {
		// Remove protocol and trailing slashes, convert to lowercase
		$url = strtolower( rtrim( preg_replace( '#^https?://#', '', $url ), '/' ) );

		// Remove www. prefix if present
		if ( str_starts_with( $url, 'www.' ) ) {
			$url = substr( $url, 4 );
		}

		return $url;
	}

	/**
	 * Check if a given domain matches the current site URL, ignoring protocol and trailing slashes.
	 */
	public function is_domain_match( $domain ): bool {
		if ( ! $domain ) {
			return false;
		}
		$site_url = $this->get_site_url();
		if ( $domain === $site_url ) {
			return true;
		}
		return $this->normalize_url( $domain ) === $this->normalize_url( $site_url );
	}

	/**
	 * Checks if currently a dev environment.
	 */
	public function is_dev_environment(): bool {
		if ( null !== $this->is_dev_environment ) {
			return $this->is_dev_environment;
		}

		$host = $this->get_site_url();

		if ( ! $host ) {
			return $this->set_dev_environment( false );
		}

		$host = wp_parse_url( $host, PHP_URL_HOST );

		if ( ! $host ) {
			return $this->set_dev_environment( false );
		}

		// IP address check (IPv4 and IPv6)
		if ( filter_var( $host, FILTER_VALIDATE_IP ) ) {
			return $this->set_dev_environment( true );
		}

		$host = strtolower( $host );

		// Exact hosts
		$exact_hosts = [
			'local',
			'dev',
			'wp',
			'test',
			'example',
			'localhost',
			'invalid',
		];

		if ( in_array( $host, $exact_hosts, true ) ) {
			return $this->set_dev_environment( true );
		}

		// Regex patterns for subdomains / providers
		$patterns = [
			// Simple subdomain patterns
			'/^dev(\.|-).*/', // dev.example.com, dev-test.example.com
			'/^exampledev(\.|-).*/', // exampledev.example.com
			'/^local(\.|-).*/', // local.example.com
			'/^test(\.|-).*/', // test.example.com
			'/^staging-?[0-9]+/', // staging2.example.com, staging-2.example.com
			'/^stage(\.|-).*/', // stage.example.com, stage-test.example.com

			// Pantheon, Kinsta, WP Engine, Flywheel, Dreamhost etc.
			'/^dev-.*\.pantheonsite\.io$/', // dev-example.pantheonsite.io
			'/^test-.*\.pantheonsite\.io$/', // test-example.pantheonsite.io
			'/^staging-.*\.kinsta\.com$/', // staging-example.kinsta.com
			'/^staging-.*\.kinsta\.cloud$/', // staging-example.kinsta.cloud
			'/^stg-.*\.kinsta\.cloud$/', // stg-example.kinsta.cloud
			'/^env-.*\.kinsta\.cloud$/', // env-example.kinsta.cloud
			'/.*\.myftpupload\.com$/', // example.myftpupload.com
			'/.*\.cloudwaysapps\.com$/', // example.cloudwaysapps.com
			'/.*-dev\.ksysweb\.com$/', // example-dev.ksysweb.com
			'/.*-stg\.ksysweb\.com$/', // example-stg.ksysweb.com
			'/.*stg\.wpengine\.com$/',  // examplestg.wpengine.com
			'/.*dev\.wpengine\.com$/', // exampledev.wpengine.com
			'/.*stg\.wpenginepowered\.com$/', // examplestg.wpenginepowered.com
			'/.*dev\.wpenginepowered\.com$/', // exampledev.wpenginepowered.com
			'/^staging-[a-z0-9]+-.*\.wpcomstaging\.com$/', // staging-ab12-example.wpcomstaging.com
			'/.*\.flywheelstaging\.com$/', // example.flywheelstaging.com
			'/^autopilot-.*\.pantheonsite\.io$/', // autopilot-example.pantheonsite.io
			'/.*\.stage\.site$/', // example.stage.site
			'/.*\.dream\.press$/', // example.dream.press

			// Catch-all local/dev TLDs
			'/\.dev$/', // example.dev
			'/\.local$/', // example.local
			'/\.wp$/', // example.wp
			'/\.test$/', // example.test
			'/\.example$/', // example.example
			'/\.localhost$/', // example.localhost
			'/\.invalid$/', // example.invalid
			'/\.staging$/', // example.staging
		];

		foreach ( $patterns as $pattern ) {
			if ( preg_match( $pattern, $host ) ) {
				return $this->set_dev_environment( true );
			}
		}

		return $this->set_dev_environment( false );
	}

	/**
	 * Prevent cloning.
	 */
	private function __clone() {}

	/**
	 * Prevent unserializing.
	 */
	public function __wakeup() {
		trigger_error( 'Cannot unserialize a Singleton.', E_USER_WARNING);
	}

}
