<?php

namespace WeDevs\AppBridge\Core;

use WeDevs\AppBridge\Core\Contracts\Hookable;
use WeDevs\AppBridge\Core\Contracts\LicenseBackend;
use WeDevs\AppBridge\Core\License\AppseroBackend;

/**
 * Base license manager for all integrations.
 *
 * Concrete subclasses supply the credentials and the admin URL for the
 * license settings page. All activate / deactivate / notice logic lives
 * here so each integration gets identical behaviour with zero duplication.
 *
 * Transport
 * ---------
 * WHERE a license is validated is delegated to a {@see LicenseBackend}.
 * The default is {@see AppseroBackend} (Appsero SaaS). Integrations whose
 * product is sold from our own EDD Software Licensing store override
 * `make_backend()` to return an {@see \WeDevs\AppBridge\Core\License\EddSlBackend}.
 *
 * Lifecycle
 * ---------
 * `init_license()` is called from `__construct()` so that `get_license()`
 * is safe to call before `register_hooks()` fires (e.g. from
 * `DokanIntegration::rest_controllers()` during service registration).
 */
abstract class AbstractLicenseManager implements Hookable {

	/**
	 * License transport backend.
	 *
	 * @var LicenseBackend
	 */
	private $backend;

	// ── Required by subclasses ─────────────────────────────────────────────

	/**
	 * Appsero product hash ID.
	 *
	 * Required for the default Appsero backend. Integrations using a
	 * non-Appsero backend may return an empty string.
	 */
	abstract protected function appsero_hash_id(): string;

	/**
	 * Human-readable plugin name passed to Appsero (e.g. 'Pocket Store').
	 */
	abstract protected function plugin_name(): string;

	/**
	 * Absolute path to the plugin's main file (used by Appsero for updates).
	 */
	abstract protected function plugin_file(): string;

	/**
	 * Admin URL of the License settings page for use in notice links.
	 */
	abstract protected function license_page_url(): string;

	/**
	 * Human-readable Mobile App name passed for appsero license verification (e.g. 'Dokan Vendor App').
	 */
	abstract protected function get_mobile_app_name(): string;

	// ── Lifecycle ──────────────────────────────────────────────────────────

	public function __construct() {
		$this->init_license();

		add_filter( 'app_bridge_license_for_' . $this->get_mobile_app_name(), [ $this, 'get_license_key' ] );
		add_filter( 'app_bridge_rest_' . $this->get_mobile_app_name() . '_license_verification_cache_data', [ $this, 'get_license_verification_cache_data' ], 10, 2 );
		add_action( 'app_bridge_rest_' . $this->get_mobile_app_name() . '_license_verification_data_generated', [ $this, 'handle_license_verification' ], 10, 3 );

		// Clear cache on license status change to ensure REST API responses are up-to-date.
		// The deactivated key is passed explicitly because the stored key is wiped
		// before these fire, so the cache key can no longer be derived from storage.
		add_action( 'app_bridge_license_activated', [ $this, 'clear_verification_cache_data' ], 10, 2 );
		add_action( 'app_bridge_license_deactivated', [ $this, 'clear_verification_cache_data' ], 10, 2 );
	}

	/**
	 * Return cached license verification data for the current mobile app, if any.
	 *
	 * @param array  $data            Verification data resolved so far (passed through for other apps).
	 * @param string $mobile_app_name Mobile app the verification is being resolved for.
	 * @return array|null Cached verification data, or the incoming $data when this app does not match.
	 */
	public function get_license_verification_cache_data( $data, string $mobile_app_name ) {
		if ( $mobile_app_name !== $this->get_mobile_app_name() ) {
			return $data;
		}

		return get_transient( $this->get_verification_data_cache_key() );
	}

	/**
	 * Cache the license verification data.
	 *
	 * @param array $verification_data
	 * @param string $mobile_app_name
	 * @param integer $ttl
	 * @return void
	 */
	public function handle_license_verification( array $verification_data, string $mobile_app_name, int $ttl ): void {
		if ( $mobile_app_name !== $this->get_mobile_app_name() ) {
			return;
		}

		$cache_key = $this->get_verification_data_cache_key();

		set_transient( $cache_key, $verification_data, $ttl - HOUR_IN_SECONDS );
	}

	/**
	 * Generate a unique cache key for storing license verification results.
	 *
	 * @param string|null $license_key Optional key to scope the cache to. Falls
	 *                                 back to the stored key when null. Pass it
	 *                                 explicitly when the stored key has already
	 *                                 been wiped (e.g. during deactivation).
	 * @return string The cache key.
	 */
	protected function get_verification_data_cache_key( ?string $license_key = null ): string {
		$license_key = null === $license_key ? $this->get_license_key() : $license_key;
		return 'app_bridge_' . $this->get_mobile_app_name() . '_license_verification_' . md5( $license_key );
	}

	/**
	 * Clear cached license verification data when license status changes.
	 *
	 * @param string      $mobile_app_name
	 * @param string|null $license_key Key whose cache should be cleared. When
	 *                                 null the stored key is used.
	 * @return void
	 */
	public function clear_verification_cache_data( string $mobile_app_name, ?string $license_key = null ): void {
		if ( $mobile_app_name === $this->get_mobile_app_name() ) {
			delete_transient( $this->get_verification_data_cache_key( $license_key ) );
		}
	}

	/**
	 * Register Hooks
	 *
	 * @return void
	 */
	public function register_hooks(): void {
		add_action( 'admin_notices', [ $this, 'show_license_notice' ] );
	}

	/**
	 * Initialise the license transport backend.
	 *
	 * Called from the constructor so that `get_license()` is safe to call
	 * before `register_hooks()` fires.
	 */
	private function init_license(): void {
		$this->backend = $this->make_backend();
	}

	/**
	 * Build the license transport backend for this integration.
	 *
	 * Defaults to the Appsero SaaS backend. Integrations whose product is
	 * sold from our own EDD Software Licensing store override this to
	 * return an {@see \WeDevs\AppBridge\Core\License\EddSlBackend}.
	 */
	protected function make_backend(): LicenseBackend {
		return new AppseroBackend(
			$this->appsero_hash_id(),
			$this->plugin_name(),
			$this->plugin_file(),
			$this->option_key()
		);
	}

	// ── Public API ─────────────────────────────────────────────────────────

	/**
	 * Return the license backend.
	 *
	 * Exposes `is_valid()` and `get_license()` for callers that previously
	 * consumed the Appsero License object directly
	 * (e.g. `$manager->get_license()->is_valid()`).
	 *
	 * @return LicenseBackend
	 */
	public function get_license() {
		return $this->backend;
	}

	/**
	 * WordPress option key used to persist license data.
	 */
	protected function option_key(): string {
		return 'appsero_' . $this->get_mobile_app_name() . '_license';
	}

	/**
	 * Get the stored license key from the database.
	 *
	 * @return string The license key, or empty string if not set.
	 */
	public function get_license_key(): string {
		$stored = $this->backend->get_license();
		return $stored['key'] ?? '';
	}

	/**
	 * Activate a license and persist the result via the backend.
	 *
	 * @param  string $license_key
	 * @return array{success: bool, message: string}
	 */
	public function activate( string $license_key ): array {
		$result = $this->backend->activate( $license_key );

		if ( empty( $result['success'] ) ) {
			return [
				'success' => false,
				'message' => $result['message'] ?? __( 'Unknown error occurred.', 'app-bridge' ),
			];
		}

		do_action( 'app_bridge_license_activated', $this->get_mobile_app_name(), $license_key );

		return [
			'success' => true,
			'message' => $result['message'] ?? __( 'License activated successfully.', 'app-bridge' ),
		];
	}

	/**
	 * Deactivate the stored license via the backend.
	 *
	 * @return array{success: bool, message: string}
	 */
	public function deactivate(): array {
		$key    = $this->get_license_key();
		$result = $this->backend->deactivate( $key );

		// Always clear stored data on deactivation attempt.
		update_option(
            $this->option_key(), [
				'key' => '',
				'status' => 'deactivate',
			], false
        );

		if ( empty( $result['success'] ) ) {
			do_action( 'mobile_app_license_deactivation_failed', $this->get_mobile_app_name(), $key, $result['api_error'] ?? '' );

			return [
				'success' => false,
				'message' => $result['message'] ?? __( 'Unknown error occurred.', 'app-bridge' ),
			];
		}

		do_action( 'app_bridge_license_deactivated', $this->get_mobile_app_name(), $key );

		return [
			'success' => true,
			'message' => $result['message'] ?? __( 'License deactivated successfully.', 'app-bridge' ),
		];
	}

	// ── Admin notices ──────────────────────────────────────────────────────

	/**
	 * Show an admin notice based on current license state:
	 *
	 * - No key            → notice-warning  (prompt to activate)
	 * - Key but invalid   → notice-error    (expired / revoked)
	 * - Expiring ≤ 7 days → notice-warning  (prompt to renew)
	 * - Valid & healthy   → no notice
	 */
	public function show_license_notice(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		$url         = $this->license_page_url();
		$stored      = $this->backend->get_license();
		$has_key     = ! empty( $stored['key'] );
		$is_valid    = $this->backend->is_valid();
		$expiry_days = $stored['expiry_days'] ?? null;

		if ( ! $has_key ) {
			$this->render_notice(
				'warning',
				sprintf(
					/* translators: %s: admin URL */
					__( 'Please <a href="%1$s">activate</a> your <strong>%2$s</strong> license key to enable all features and receive updates.', 'app-bridge' ),
					esc_url( $url ),
					esc_html( $this->plugin_name() )
				)
			);
			return;
		}

		if ( ! $is_valid ) {
			$this->render_notice(
				'error',
				sprintf(
					/* translators: %s: admin URL */
					__( 'Your <strong>%1$s</strong> license has expired or is invalid. Premium features are disabled. <a href="%2$s">Renew your license</a>.', 'app-bridge' ),
					esc_html( $this->plugin_name() ),
					esc_url( $url )
				)
			);
			return;
		}

		// false = perpetual license; skip expiry warning.
		if ( false !== $expiry_days && null !== $expiry_days && (int) $expiry_days <= 7 && (int) $expiry_days > 0 ) {
			$this->render_notice(
				'warning',
				sprintf(
					// translators: 1. Plugin name, 2. Number of days until expiry, 3. Admin URL.
					__( 'Your <strong>%1$s</strong> license expires in %2$d day(s). <a href="%3$s">Renew now</a> to avoid interruption.', 'app-bridge' ),
					esc_html( $this->plugin_name() ),
					(int) $expiry_days,
					esc_url( $url )
				)
			);
		}
	}

	/**
	 * Render a dismissible admin notice.
	 *
	 * @param string $type    'warning' | 'error'
	 * @param string $message HTML message (links and <strong> already allowed).
	 */
	private function render_notice( string $type, string $message ): void {
		printf(
			'<div class="notice notice-%s is-dismissible"><p>%s</p></div>',
			esc_attr( $type ),
			wp_kses(
                $message, [
					'a' => [ 'href' => [] ],
					'strong' => [],
				]
            )
		);
	}
}
