<?php
/**
 * Botcrawl Bot Blocker - License & Trial Manager
 *
 * Single-tier model:
 *  - On first activation a 7-day trial begins automatically (no key required).
 *  - The trial is registered server-side at sera.guru so uninstalling and
 *    reinstalling the plugin does not grant a new trial.
 *  - After the trial expires the plugin suspends enforcement and logging and
 *    shows a license prompt in wp-admin until a valid key is entered.
 *  - A valid annual license key restores full functionality indefinitely
 *    (until the key expires, after which the same suspension applies).
 *
 * All features are identical during the trial and with a paid license.
 * There is no "free" tier and no feature gating beyond the time limit.
 *
 * @package BotcrawlBotBlocker
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class SeraLicense {

	const OPTION_KEY       = 'sera_bot_blocker_license_key';
	const OPTION_DATA      = 'sera_bot_blocker_license_data';
	const TRIAL_OPTION     = 'sera_bot_blocker_trial_start';
	const TRIAL_REMOTE     = 'sera_bot_blocker_trial_remote'; // cached remote status
	const TRIAL_DAYS       = 7;
	const VERIFY_URL       = 'https://sera.guru/api/license/verify';
	const TRIAL_REGISTER   = 'https://sera.guru/api/trpc/trial.register';
	const TRIAL_CHECK      = 'https://sera.guru/api/trpc/trial.check';
	const CRON_HOOK        = 'sera_bot_blocker_license_recheck';
	const CACHE_TTL        = 7 * DAY_IN_SECONDS;
	const FAIL_TTL         = HOUR_IN_SECONDS;

	// -------------------------------------------------------------------------
	// Bootstrap
	// -------------------------------------------------------------------------

	public static function init(): void {
		self::maybe_start_trial();

		// Admin notices.
		add_action( 'admin_notices',         [ __CLASS__, 'maybe_show_notice' ] );
		add_action( 'network_admin_notices', [ __CLASS__, 'maybe_show_notice' ] );

		// AJAX handlers.
		add_action( 'wp_ajax_sera_license_save',       [ __CLASS__, 'ajax_save' ] );
		add_action( 'wp_ajax_sera_license_recheck',    [ __CLASS__, 'ajax_recheck' ] );
		add_action( 'wp_ajax_sera_bb_save_license',    [ __CLASS__, 'ajax_save' ] );
		add_action( 'wp_ajax_sera_bb_recheck_license', [ __CLASS__, 'ajax_recheck' ] );

		// Scheduled recheck (daily).
		add_action( self::CRON_HOOK, [ __CLASS__, 'recheck' ] );
		if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
			wp_schedule_event( time() + DAY_IN_SECONDS, 'daily', self::CRON_HOOK );
		}
	}

	// -------------------------------------------------------------------------
	// Trial management
	// -------------------------------------------------------------------------

	/**
	 * Called on every plugin load. On first run, registers the trial with the
	 * sera.guru server. The server is the authority - if it returns trial_expired,
	 * the local option is set to a past timestamp so trial_active() returns false,
	 * regardless of whether the plugin was freshly installed.
	 */
	public static function maybe_start_trial(): void {
		$local_start = get_site_option( self::TRIAL_OPTION );

		if ( false === $local_start ) {
			// First activation on this site. Register with the server.
			$remote = self::remote_trial_register();
			if ( $remote['status'] === 'trial_expired' ) {
				// Domain already used its trial. Set local start to a past time.
				update_site_option( self::TRIAL_OPTION, time() - ( self::TRIAL_DAYS * DAY_IN_SECONDS ) - 1 );
			} else {
				// trial_started or trial_active - use the server's end date to set local start.
				$ends_at = isset( $remote['trial_ends_at'] ) ? strtotime( $remote['trial_ends_at'] ) : 0;
				if ( $ends_at > 0 ) {
					$start = $ends_at - ( self::TRIAL_DAYS * DAY_IN_SECONDS );
					update_site_option( self::TRIAL_OPTION, $start );
				} else {
					update_site_option( self::TRIAL_OPTION, time() );
				}
			}
			update_site_option( self::TRIAL_REMOTE, $remote );
			return;
		}

		// Subsequent loads: use cached remote status, refresh once per day via cron.
		// If the cached remote status says expired, enforce it regardless of local start.
		$cached = get_site_option( self::TRIAL_REMOTE, [] );
		if ( is_array( $cached ) && isset( $cached['status'] ) && $cached['status'] === 'trial_expired' ) {
			// Ensure local start reflects expiry so trial_active() returns false.
			$elapsed = time() - (int) $local_start;
			if ( $elapsed < self::TRIAL_DAYS * DAY_IN_SECONDS ) {
				update_site_option( self::TRIAL_OPTION, time() - ( self::TRIAL_DAYS * DAY_IN_SECONDS ) - 1 );
			}
		}
	}

	public static function trial_start(): int {
		return (int) get_site_option( self::TRIAL_OPTION, 0 );
	}

	public static function trial_days_remaining(): int {
		$start = self::trial_start();
		if ( $start === 0 ) {
			return 0;
		}
		$elapsed = (int) floor( ( time() - $start ) / DAY_IN_SECONDS );
		return max( 0, self::TRIAL_DAYS - $elapsed );
	}

	public static function trial_active(): bool {
		return self::trial_days_remaining() > 0;
	}

	// -------------------------------------------------------------------------
	// Remote trial API
	// -------------------------------------------------------------------------

	/**
	 * Registers this domain's trial with sera.guru.
	 * Returns an array with 'status' and optionally 'trial_ends_at'.
	 */
	private static function remote_trial_register(): array {
		$body = wp_json_encode( [
			'json' => [
				'domain'     => self::site_domain(),
				'plugin'     => 'botcrawl-bot-blocker',
				'trial_days' => self::TRIAL_DAYS,
			],
		] );

		$response = wp_remote_post( self::TRIAL_REGISTER, [
			'timeout'    => 10,
			'user-agent' => 'SeraBotBlocker/' . BOTCRAWL_BOT_BLOCKER_VERSION,
			'headers'    => [ 'Content-Type' => 'application/json' ],
			'body'       => $body,
		] );

		if ( is_wp_error( $response ) ) {
			// Network failure - assume trial is starting (fail-open for new installs).
			return [ 'status' => 'trial_started', 'trial_ends_at' => null ];
		}

		$code = (int) wp_remote_retrieve_response_code( $response );
		$raw  = json_decode( wp_remote_retrieve_body( $response ), true );

		// tRPC wraps the result in { result: { data: { json: ... } } }
		$data = $raw['result']['data']['json'] ?? $raw['result']['data'] ?? $raw;

		if ( $code === 200 && is_array( $data ) && isset( $data['status'] ) ) {
			return [
				'status'       => sanitize_key( $data['status'] ),
				'trial_ends_at' => sanitize_text_field( $data['trial_ends_at'] ?? '' ),
			];
		}

		// Unexpected response - fail-open.
		return [ 'status' => 'trial_started', 'trial_ends_at' => null ];
	}

	/**
	 * Checks the current trial status from sera.guru.
	 * Called during the daily cron recheck.
	 */
	private static function remote_trial_check(): array {
		$url = add_query_arg( [
			'input' => wp_json_encode( [
				'json' => [
					'domain' => self::site_domain(),
					'plugin' => 'botcrawl-bot-blocker',
				],
			] ),
		], self::TRIAL_CHECK );

		$response = wp_remote_get( $url, [
			'timeout'    => 10,
			'user-agent' => 'SeraBotBlocker/' . BOTCRAWL_BOT_BLOCKER_VERSION,
			'headers'    => [ 'Content-Type' => 'application/json' ],
		] );

		if ( is_wp_error( $response ) ) {
			// Network failure - keep existing cached status.
			$cached = get_site_option( self::TRIAL_REMOTE, [] );
			return is_array( $cached ) ? $cached : [ 'status' => 'trial_active' ];
		}

		$code = (int) wp_remote_retrieve_response_code( $response );
		$raw  = json_decode( wp_remote_retrieve_body( $response ), true );

		$data = $raw['result']['data']['json'] ?? $raw['result']['data'] ?? $raw;

		if ( $code === 200 && is_array( $data ) && isset( $data['status'] ) ) {
			return [
				'status'        => sanitize_key( $data['status'] ),
				'trial_ends_at' => sanitize_text_field( $data['trial_ends_at'] ?? '' ),
			];
		}

		$cached = get_site_option( self::TRIAL_REMOTE, [] );
		return is_array( $cached ) ? $cached : [ 'status' => 'trial_active' ];
	}

	/**
	 * Returns the bare domain (no protocol, no trailing slash) for this site.
	 */
	private static function site_domain(): string {
		$url = home_url();
		$host = wp_parse_url( $url, PHP_URL_HOST );
		return strtolower( $host ?: $url );
	}

	// -------------------------------------------------------------------------
	// License key management
	// -------------------------------------------------------------------------

	public static function get_key(): string {
		return (string) get_site_option( self::OPTION_KEY, '' );
	}

	/**
	 * Returns true when the plugin is authorised to run.
	 * A stored active license key ALWAYS takes precedence over trial state.
	 */
	public static function is_active(): bool {
		// License key takes priority over trial.
		if ( self::is_licensed() ) {
			return true;
		}
		// Fall back to trial.
		return self::trial_active();
	}

	/**
	 * Returns true only when running on a paid license (not trial).
	 */
	public static function is_licensed(): bool {
		$data = self::stored_data();
		if ( ! isset( $data['status'] ) || $data['status'] !== 'active' ) {
			return false;
		}
		// Check local expiry.
		if ( ! empty( $data['expires_at'] ) && $data['expires_at'] !== 'null' ) {
			$expires = strtotime( $data['expires_at'] );
			if ( $expires !== false && $expires < time() ) {
				return false;
			}
		}
		return true;
	}

	public static function stored_data(): array {
		$raw = get_site_option( self::OPTION_DATA, '' );
		if ( ! $raw ) {
			return [];
		}
		$decoded = json_decode( $raw, true );
		return is_array( $decoded ) ? $decoded : [];
	}

	/**
	 * Human-readable status badge for the admin UI.
	 */
	public static function status_badge(): array {
		// License key takes priority over trial.
		if ( self::is_licensed() ) {
			$data       = self::stored_data();
			$expires_at = $data['expires_at'] ?? '';
			if ( $expires_at && $expires_at !== 'null' ) {
				$expires   = strtotime( $expires_at );
				$days_left = $expires ? (int) ceil( ( $expires - time() ) / DAY_IN_SECONDS ) : 0;
				$label     = 'Licensed - Active';
				if ( $days_left <= 30 && $days_left > 0 ) {
					$label = 'Licensed - Renews in ' . $days_left . ' day' . ( $days_left === 1 ? '' : 's' );
				}
				return [ 'status' => 'active', 'label' => $label, 'color' => '#46b450' ];
			}
			return [ 'status' => 'active', 'label' => 'Licensed - Active', 'color' => '#46b450' ];
		}
		// Trial state.
		if ( self::trial_active() ) {
			$remaining = self::trial_days_remaining();
			return [
				'status' => 'trial',
				'label'  => 'Free Trial - ' . $remaining . ' day' . ( $remaining === 1 ? '' : 's' ) . ' remaining',
				'color'  => $remaining <= 2 ? '#d63638' : '#f0a500',
			];
		}
		$data   = self::stored_data();
		$status = $data['status'] ?? 'empty';
		$map    = [
			'expired' => [ 'label' => 'License Expired',       'color' => '#d63638' ],
			'invalid' => [ 'label' => 'Invalid Key',            'color' => '#d63638' ],
			'empty'   => [ 'label' => 'No License Key',         'color' => '#999' ],
			'error'   => [ 'label' => 'Verification Error',     'color' => '#f0a500' ],
		];
		$entry = $map[ $status ] ?? [ 'label' => 'Trial Ended - No License', 'color' => '#d63638' ];
		return array_merge( [ 'status' => $status ?: 'empty' ], $entry );
	}

	// -------------------------------------------------------------------------
	// Verification
	// -------------------------------------------------------------------------

	public static function save_and_verify( string $key ): array {
		$key = sanitize_text_field( trim( $key ) );
		if ( $key === '' ) {
			update_site_option( self::OPTION_KEY, '' );
			update_site_option( self::OPTION_DATA, wp_json_encode( [ 'status' => 'empty', 'message' => 'License key removed.' ] ) );
			return [ 'status' => 'empty', 'message' => 'License key removed.' ];
		}
		$result = self::verify( $key );
		if ( $result['status'] === 'active' ) {
			update_site_option( self::OPTION_KEY, $key );
			// KEY FIX: Immediately end the trial when a valid license is activated.
			update_site_option( self::TRIAL_OPTION, time() - ( self::TRIAL_DAYS * DAY_IN_SECONDS ) - 1 );
		}
		update_site_option( self::OPTION_DATA, wp_json_encode( $result ) );
		return $result;
	}

	public static function recheck(): void {
		// Refresh remote trial status.
		$remote = self::remote_trial_check();
		update_site_option( self::TRIAL_REMOTE, $remote );

		// If server says trial is expired, enforce it locally.
		if ( isset( $remote['status'] ) && $remote['status'] === 'trial_expired' ) {
			$start = self::trial_start();
			if ( $start > 0 ) {
				$elapsed = time() - $start;
				if ( $elapsed < self::TRIAL_DAYS * DAY_IN_SECONDS ) {
					update_site_option( self::TRIAL_OPTION, time() - ( self::TRIAL_DAYS * DAY_IN_SECONDS ) - 1 );
				}
			}
		}

		// Recheck license key if one is stored.
		$key = self::get_key();
		if ( $key === '' ) {
			return;
		}
		$result = self::verify( $key );
		update_site_option( self::OPTION_DATA, wp_json_encode( $result ) );
	}

	private static function verify( string $key ): array {
		if ( $key === '' ) {
			return [ 'status' => 'empty', 'message' => 'No license key provided.' ];
		}

		$response = wp_remote_post( self::VERIFY_URL, [
			'timeout'    => 15,
			'user-agent' => 'SeraBotBlocker/' . BOTCRAWL_BOT_BLOCKER_VERSION,
			'headers'    => [ 'Content-Type' => 'application/json' ],
			'body'       => wp_json_encode( [
				'license_key' => $key,
				'site_url'    => home_url(),
				'plugin'      => 'botcrawl-bot-blocker',
				'version'     => BOTCRAWL_BOT_BLOCKER_VERSION,
			] ),
		] );

		if ( is_wp_error( $response ) ) {
			// Network failure - fail-open: keep existing active state if any.
			$existing = self::stored_data();
			if ( isset( $existing['status'] ) && $existing['status'] === 'active' ) {
				return $existing;
			}
			return [ 'status' => 'error', 'message' => 'Could not reach the license server. Please try again later.' ];
		}

		$code = (int) wp_remote_retrieve_response_code( $response );
		$body = json_decode( wp_remote_retrieve_body( $response ), true );

		if ( $code !== 200 || ! is_array( $body ) || empty( $body['status'] ) ) {
			return [ 'status' => 'error', 'message' => 'Unexpected response from license server.' ];
		}

		return [
			'status'     => sanitize_key( $body['status'] ),
			'message'    => sanitize_text_field( $body['message'] ?? ucfirst( $body['status'] ) ),
			'expires_at' => sanitize_text_field( $body['expires_at'] ?? '' ),
		];
	}

	// -------------------------------------------------------------------------
	// AJAX handlers
	// -------------------------------------------------------------------------

	public static function ajax_save(): void {
		check_ajax_referer( 'sera_license_nonce', 'nonce' );
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( [ 'message' => 'Forbidden.' ], 403 );
		}
		$key    = sanitize_text_field( wp_unslash( $_POST['license_key'] ?? '' ) );
		$result = self::save_and_verify( $key );
		if ( $result['status'] === 'active' ) {
			wp_send_json_success( $result );
		} else {
			wp_send_json_error( $result );
		}
	}

	public static function ajax_recheck(): void {
		check_ajax_referer( 'sera_license_nonce', 'nonce' );
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( [ 'message' => 'Forbidden.' ], 403 );
		}
		self::recheck();
		$badge = self::status_badge();
		if ( $badge['status'] === 'active' || $badge['status'] === 'trial' ) {
			wp_send_json_success( $badge );
		} else {
			wp_send_json_error( $badge );
		}
	}

	// -------------------------------------------------------------------------
	// Admin notice
	// -------------------------------------------------------------------------

	public static function maybe_show_notice(): void {
		// Never show trial notices when a valid license is active.
		if ( self::is_licensed() ) {
			return;
		}
		if ( self::trial_active() ) {
			$remaining = self::trial_days_remaining();
			if ( $remaining <= 2 ) {
				$url = admin_url( 'admin.php?page=bot-blocker-settings#sera-license' );
				printf(
					'<div class="notice notice-warning is-dismissible"><p><strong>Botcrawl Bot Blocker:</strong> Your free trial expires in %d day%s. <a href="%s">Enter your license key</a> to keep protection active. <a href="https://sera.guru/products/botcrawl-bot-blocker" target="_blank">Purchase a license</a></p></div>',
					$remaining,
					$remaining === 1 ? '' : 's',
					esc_url( $url )
				);
			}
			return;
		}

		$url = admin_url( 'admin.php?page=bot-blocker-settings#sera-license' );
		printf(
			'<div class="notice notice-error"><p><strong>Botcrawl Bot Blocker is suspended.</strong> Your 7-day trial has ended. Bot enforcement and logging are paused. <a href="%s"><strong>Enter your license key</strong></a> to restore full protection, or <a href="https://sera.guru/products/botcrawl-bot-blocker" target="_blank">purchase a license ($9.99/year)</a>.</p></div>',
			esc_url( $url )
		);
	}

	// -------------------------------------------------------------------------
	// Settings page UI
	// -------------------------------------------------------------------------

	public static function render_license_section(): void {
		$key      = self::get_key();
		$badge    = self::status_badge();
		$nonce    = wp_create_nonce( 'sera_license_nonce' );
		$licensed = self::is_licensed();
		$trial    = ! $licensed && self::trial_active();
		$active   = self::is_active();
		$data     = self::stored_data();
		$expires_at = $data['expires_at'] ?? '';
		?>
		<div id="sera-license" style="background:#fff;border:1px solid #ddd;border-radius:8px;padding:20px 24px;margin-top:28px;max-width:700px;">
			<h3 style="margin-top:0;display:flex;align-items:center;gap:10px;">
				<svg width="18" height="18" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 2L3 5v5c0 4.4 2.9 8.1 7 9.9 4.1-1.8 7-5.5 7-9.9V5l-7-3z" fill="#7c3aed"/><path d="M7 10l2 2 4-4" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
				Botcrawl Bot Blocker License
				<span style="font-size:12px;font-weight:600;padding:3px 10px;border-radius:20px;background:<?php echo esc_attr( $badge['color'] ); ?>22;color:<?php echo esc_attr( $badge['color'] ); ?>;border:1px solid <?php echo esc_attr( $badge['color'] ); ?>44;">
					<?php echo esc_html( $badge['label'] ); ?>
				</span>
			</h3>

			<?php if ( $licensed ) : ?>
			<p style="color:#555;margin-bottom:16px;">
				Your license is active. All features are enabled.
				<?php if ( $expires_at && $expires_at !== 'null' && $expires_at !== '' ) : ?>
					License valid until: <strong><?php echo esc_html( date( 'F j, Y', strtotime( $expires_at ) ) ); ?></strong>.
					<a href="https://sera.guru/products/botcrawl-bot-blocker" target="_blank">Renew license</a>
				<?php endif; ?>
			</p>
			<?php elseif ( $trial ) : ?>
			<p style="color:#555;margin-bottom:16px;">
				You are on a free trial. Enter your license key before the trial ends to maintain uninterrupted protection.
				<a href="https://sera.guru/products/botcrawl-bot-blocker" target="_blank">Purchase a license ($9.99/year)</a>
			</p>
			<?php elseif ( ! $active ) : ?>
			<p style="color:#d63638;margin-bottom:16px;">
				Your trial has ended and bot protection is currently suspended. Enter a valid license key to restore full protection.
				<a href="https://sera.guru/products/botcrawl-bot-blocker" target="_blank">Purchase a license</a>
			</p>
			<?php else : ?>
			<p style="color:#555;margin-bottom:16px;">Your license is active. All features are enabled.</p>
			<?php endif; ?>

			<table class="form-table" role="presentation" style="margin:0;">
				<tr>
					<th scope="row" style="width:130px;"><label for="sera-license-key-input">License Key</label></th>
					<td>
						<input type="text" id="sera-license-key-input"
							   value="<?php echo esc_attr( $key ); ?>"
							   placeholder="SERA-XXXX-XXXX-XXXX-XXXX"
							   class="regular-text"
							   style="font-family:monospace;" />
						<button type="button" id="sera-license-save-btn" class="button button-primary" style="margin-left:8px;">
							Activate
						</button>
						<?php if ( $key ) : ?>
						<button type="button" id="sera-license-recheck-btn" class="button" style="margin-left:6px;">
							Re-check
						</button>
						<?php endif; ?>
						<span id="sera-license-msg" style="margin-left:10px;font-weight:600;display:none;"></span>
					</td>
				</tr>
			</table>

			<p style="margin-top:12px;color:#666;font-size:13px;">
				One license covers a single WordPress installation (or multisite network).
				<a href="https://sera.guru/products/botcrawl-bot-blocker" target="_blank" style="color:#2271b1;">Renew or purchase at sera.guru - $9.99/year</a>
			</p>
		</div>

		<script>
		(function($){
			var nonce = <?php echo wp_json_encode( $nonce ); ?>;
			function setMsg(msg, ok) {
				$('#sera-license-msg').text(msg).css('color', ok ? '#46b450' : '#d63638').show();
				setTimeout(function(){ $('#sera-license-msg').fadeOut(); }, 5000);
			}
			$('#sera-license-save-btn').on('click', function(){
				var btn = $(this);
				btn.prop('disabled', true).text('Activating...');
				$.post(ajaxurl, {
					action:      'sera_license_save',
					nonce:       nonce,
					license_key: $('#sera-license-key-input').val().trim()
				}, function(res){
					if (res.success) {
						setMsg('License activated. Reloading...', true);
						setTimeout(function(){ location.reload(); }, 1200);
					} else {
						setMsg((res.data && res.data.message ? res.data.message : 'Invalid key.'), false);
					}
				}).fail(function(){
					setMsg('Network error. Please try again.', false);
				}).always(function(){
					btn.prop('disabled', false).text('Activate');
				});
			});
			$('#sera-license-recheck-btn').on('click', function(){
				var btn = $(this);
				btn.prop('disabled', true).text('Checking...');
				$.post(ajaxurl, {
					action: 'sera_license_recheck',
					nonce:  nonce
				}, function(res){
					if (res.success) {
						setMsg((res.data.label || 'License is active.'), true);
					} else {
						setMsg((res.data && res.data.label ? res.data.label : 'License not valid.'), false);
					}
				}).fail(function(){
					setMsg('Network error.', false);
				}).always(function(){
					btn.prop('disabled', false).text('Re-check');
				});
			});
		}(jQuery));
		</script>
		<?php
	}
}
