<?php // phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase, WordPress.Files.FileName.InvalidClassFileName
/**
 * Snooze options provider implementation.
 *
 * Resolves the snooze duration options shown to subscribers, sourcing
 * them from QPilot's CustomerPortalSettings (QMC) or from merchant-
 * configured plugin defaults, with caching and a fallback chain.
 *
 * @package Autoship\Services\Snooze\Implementations
 * @since   2.13.0
 */

namespace Autoship\Services\Snooze\Implementations;

use Autoship\Core\CredentialsInterface;
use Autoship\Services\Logging\LoggerInterface;
use Autoship\Services\QPilot\QPilotServiceInterface;
use Autoship\Services\Snooze\Interfaces\SnoozeOptionsProviderInterface;
use Exception;

/**
 * Concrete provider for snooze duration options.
 */
class SnoozeOptionsProvider implements SnoozeOptionsProviderInterface {

	/**
	 * Cache key prefix for the QMC frequencies transient.
	 */
	const CACHE_KEY_PREFIX = 'autoship_snooze_frequencies_';

	/**
	 * Default cache TTL in seconds (1 hour).
	 */
	const DEFAULT_CACHE_TTL = 3600;

	/**
	 * Logger instance.
	 *
	 * @var LoggerInterface
	 */
	private LoggerInterface $logger;

	/**
	 * Credentials reader.
	 *
	 * @var CredentialsInterface
	 */
	private CredentialsInterface $credentials;

	/**
	 * QPilot service client.
	 *
	 * @var QPilotServiceInterface
	 */
	private QPilotServiceInterface $qpilot;

	/**
	 * Constructor.
	 *
	 * @param LoggerInterface        $logger      Logger for API failures.
	 * @param CredentialsInterface   $credentials Credentials reader.
	 * @param QPilotServiceInterface $qpilot      QPilot service client.
	 */
	public function __construct( LoggerInterface $logger, CredentialsInterface $credentials, QPilotServiceInterface $qpilot ) {
		$this->logger      = $logger;
		$this->credentials = $credentials;
		$this->qpilot      = $qpilot;
	}

	/**
	 * Resolve the snooze options for the current site.
	 *
	 * @param bool $refresh Force a fresh fetch, bypassing the cache.
	 *
	 * @return array<int, array<string, mixed>>
	 */
	public function get_options( bool $refresh = false ): array {
		$source  = $this->get_frequency_source();
		$site_id = $this->credentials->get_site_id();

		// Plugin source: return defaults directly, no QMC consultation.
		if ( 'plugin' === $source ) {
			return $this->get_default_options();
		}

		// Without a site, we can't query QMC — fall back.
		if ( null === $site_id ) {
			return $this->get_default_options();
		}

		$cache_key = self::CACHE_KEY_PREFIX . $site_id;

		if ( ! $refresh ) {
			$cached = get_transient( $cache_key );
			if ( false !== $cached && is_array( $cached ) ) {
				return $cached;
			}
		}

		$options = $this->fetch_from_api();

		if ( ! empty( $options ) ) {
			$options = $this->filter_disabled( $options );
			$options = $this->sort_by_order( $options );
		}

		// Only cache an authoritative QMC result. Caching the plugin-default
		// fallback under the QMC key would poison the cache until expiry —
		// later QMC configuration changes would be invisible to subscribers.
		if ( ! empty( $options ) ) {
			$ttl = (int) apply_filters( 'autoship_snooze_frequencies_cache_ttl', self::DEFAULT_CACHE_TTL );
			set_transient( $cache_key, $options, $ttl );

			return $options;
		}

		return $this->get_default_options();
	}

	/**
	 * Plugin default options, sourced from merchant-configured rows or
	 * a hard-coded fallback list when nothing has been saved.
	 *
	 * @return array<int, array<string, mixed>>
	 */
	public function get_default_options(): array {
		$saved = get_option( 'autoship_snooze_plugin_frequencies', array() );

		if ( ! is_array( $saved ) || empty( $saved ) ) {
			return $this->get_hardcoded_defaults();
		}

		$options = array();
		$index   = 0;

		foreach ( $saved as $row ) {
			$type  = isset( $row['type'] ) ? (string) $row['type'] : 'Weeks';
			$every = isset( $row['every'] ) ? (int) $row['every'] : 0;
			$label = isset( $row['label'] ) ? trim( (string) $row['label'] ) : '';

			if ( $every < 1 || ! in_array( $type, array( 'Days', 'Weeks', 'Months' ), true ) ) {
				continue;
			}

			$options[] = array(
				'Duration'     => $every,
				'DurationType' => $type,
				'Label'        => $label,
				'IsDefault'    => 0 === $index,
				'Enabled'      => true,
				'Order'        => $index,
			);
			++$index;
		}

		return $options;
	}

	/**
	 * Invalidate the cached QMC frequencies for the current site.
	 */
	public function clear_cache(): void {
		$site_id = $this->credentials->get_site_id();

		if ( null !== $site_id ) {
			delete_transient( self::CACHE_KEY_PREFIX . $site_id );
		}
	}

	/**
	 * Resolve the configured frequency source.
	 *
	 * @return string Either 'qmc' (default) or 'plugin'.
	 */
	private function get_frequency_source(): string {
		$source = get_option( 'autoship_snooze_frequency_source', 'qmc' );

		if ( ! in_array( $source, array( 'qmc', 'plugin' ), true ) ) {
			$source = 'qmc';
		}

		return (string) apply_filters( 'autoship_snooze_frequency_source', $source );
	}

	/**
	 * Fetch the snooze options from QPilot's CustomerPortalSettings endpoint.
	 *
	 * @return array<int, array<string, mixed>> Normalized options or empty array on failure.
	 */
	private function fetch_from_api(): array {
		try {
			$settings = $this->qpilot->get_customer_portal_settings();

			$snooze_options = isset( $settings['SnoozeOptions'] ) && is_array( $settings['SnoozeOptions'] )
				? $settings['SnoozeOptions']
				: array();

			if ( empty( $snooze_options ) ) {
				return array();
			}

			return $this->normalize_api_options( $snooze_options );
		} catch ( Exception $e ) {
			$this->logger->error(
				'Autoship Snooze',
				sprintf(
					'%d Failed to fetch snooze options from QPilot API. Additional Details: %s',
					$e->getCode(),
					$e->getMessage()
				)
			);

			return array();
		}
	}

	/**
	 * Project the raw API items into the internal option shape.
	 *
	 * @param array $snooze_options Raw API response items (PascalCase keys
	 *                              as returned by `/api/Sites/{id}/CustomerPortalSettings`).
	 *
	 * @return array<int, array<string, mixed>>
	 */
	private function normalize_api_options( array $snooze_options ): array {
		$normalized = array();

		foreach ( $snooze_options as $option ) {
			$option       = is_object( $option ) ? (array) $option : $option;
			$normalized[] = array(
				'Duration'     => (int) ( $option['Duration'] ?? 0 ),
				'DurationType' => $option['DurationType'] ?? 'Weeks',
				'IsDefault'    => (bool) ( $option['IsDefault'] ?? false ),
				'Enabled'      => (bool) ( $option['Enabled'] ?? true ),
				'Order'        => (int) ( $option['Order'] ?? 0 ),
			);
		}

		return $normalized;
	}

	/**
	 * Drop options that are explicitly disabled.
	 *
	 * @param array $options Options to filter.
	 *
	 * @return array<int, array<string, mixed>>
	 */
	private function filter_disabled( array $options ): array {
		$filtered = array_filter(
			$options,
			static function ( $option ) {
				return ! isset( $option['Enabled'] ) || true === $option['Enabled'];
			}
		);

		return array_values( $filtered );
	}

	/**
	 * Sort options by their Order field.
	 *
	 * @param array $options Options to sort.
	 *
	 * @return array<int, array<string, mixed>>
	 */
	private function sort_by_order( array $options ): array {
		usort(
			$options,
			static function ( $a, $b ) {
				return ( $a['Order'] ?? 0 ) - ( $b['Order'] ?? 0 );
			}
		);

		return $options;
	}

	/**
	 * Hard-coded default options (used when nothing is configured anywhere).
	 *
	 * @return array<int, array<string, mixed>>
	 */
	private function get_hardcoded_defaults(): array {
		return array(
			array(
				'Duration'     => 1,
				'DurationType' => 'Weeks',
				'IsDefault'    => true,
				'Enabled'      => true,
				'Order'        => 0,
			),
			array(
				'Duration'     => 2,
				'DurationType' => 'Weeks',
				'IsDefault'    => false,
				'Enabled'      => true,
				'Order'        => 1,
			),
			array(
				'Duration'     => 1,
				'DurationType' => 'Months',
				'IsDefault'    => false,
				'Enabled'      => true,
				'Order'        => 2,
			),
			array(
				'Duration'     => 2,
				'DurationType' => 'Months',
				'IsDefault'    => false,
				'Enabled'      => true,
				'Order'        => 3,
			),
			array(
				'Duration'     => 3,
				'DurationType' => 'Months',
				'IsDefault'    => false,
				'Enabled'      => true,
				'Order'        => 4,
			),
		);
	}
}
