<?php
namespace SabaiApps\Directories\Component\System;

use SabaiApps\Directories\Exception;
use SabaiApps\Directories\Application;
use SabaiApps\Directories\Component\Form\Form;

/**
 * AbstractUpdater class for handling package update checks and license validation.
 * Provides methods for retrieving update info, validating license keys, and managing key storage.
 */
class AbstractUpdater {

	const URL = 'https://directoriespro.com/updates';
	protected static $_instance;
	protected $_application;

	/**
	 * Constructor.
	 *
	 * @param Application $application
	 */
	public function __construct( Application $application ) {
		$this->_application = $application;
	}

	/**
	 * Gets the singleton instance of the updater.
	 *
	 * @param Application $application
	 * @return AbstractUpdater
	 */
	public static function getInstance( Application $application ) {
		if ( ! isset( static::$_instance ) ) {
			static::$_instance = new static( $application );
		}
		return static::$_instance;
	}

	/**
	 * Retrieves update information for a package.
	 *
	 * @param string $package
	 * @param string $licenseType
	 * @param string $licenseKey
	 * @param bool   $force
	 * @param bool   $ignoreFalseCache
	 * @return array
	 * @throws Exception\RuntimeException
	 */
	public function getInfo( $package, $licenseType, $licenseKey, $force = false, $ignoreFalseCache = false ) {
		$info = $this->_getInfoCache();
		if ( $force
			|| ! isset( $info[ $package ] )
			|| ( $ignoreFalseCache && false === $info[ $package ] )
		) {
			$platform = $this->_application->getPlatform();
			$params   = array(
				'action'          => 'info',
				'package'         => $package,
				'license_type'    => $licenseType,
				'license_key'     => $licenseKey,
				'site_version'    => $platform->getSiteVersion(),
				'site_url'        => $platform->getSiteUrl(),
				'package_version' => $platform->getPackageVersion( $package ),
				'version_pref'    => defined( 'DRTS_UPDATER_VERSION_PREF' ) ? DRTS_UPDATER_VERSION_PREF : '',
			);
			if ( ! is_array( $info ) ) {
				$info = array();
			}
			try {
				$response = $platform->remotePost( self::URL, $params );
				if ( ! $decoded = json_decode( $response, true ) ) {
					throw new Exception\RuntimeException( 'Failed decoding package info.' );
				}
				$info[ $package ] = $decoded;
				$this->_setInfoCache( $info );
			} catch ( \Exception $e ) {
				$info[ $package ] = false;
				$this->_setInfoCache( $info );
				throw $e;
			}
		}

		return $info[ $package ];
	}

	/**
	 * Gets cached update info if not expired.
	 *
	 * @return array|null
	 */
	protected function _getInfoCache() {
		$platform = $this->_application->getPlatform();
		if ( ( ! $timeout = $platform->getOption( 'core_updater_package_info_timeout' ) )
			|| $timeout < time()
		) {
			return;
		}

		return $platform->getOption( 'core_updater_package_info' );
	}

	/**
	 * Sets the update info cache and timeout.
	 *
	 * @param array $info
	 */
	protected function _setInfoCache( $info ) {
		// Use option instead of cache so that the cache may not be cleared by the user
		$this->_application->getPlatform()->setOption( 'core_updater_package_info', $info, false )
			->setOption( 'core_updater_package_info_timeout', time() + 36000, false ); // cache 10 hours
	}

	/**
	 * Clears the update info cache.
	 */
	protected function _clearInfoCache() {
		$this->_application->getPlatform()->deleteOption( 'core_updater_package_info' )
			->deleteOption( 'core_updater_package_info_timeout' );
	}

	/**
	 * Validates the license key for a package.
	 *
	 * @param string $package
	 * @return bool
	 */
	public function validate( $package ) {
		if ( ! $this->isKeyRequired( $package ) ) {
			return true;
		}

		$platform    = $this->_application->getPlatform();
		$option_name = md5( $platform->getSiteUrl() . $package );
		if ( $platform->getOption( $option_name, false ) ) {
			if ( ( $last_ts = $platform->getOption( '_' . $option_name ) )
				&& $last_ts > time() - 604800 // last attempt is less than a week old
			) {
				return true;
			}

			// Clear last time attempt value so that a new attempt can bem made
			$platform->deleteOption( '_' . $option_name );
		}

		$license_keys = $this->getKeys();
		if ( ! isset( $license_keys[ $package ]['value'] )
			|| ( ! $license_key = trim( $license_keys[ $package ]['value'] ) )
		) {
			return false;
		}

		// Do not attempt if last attempt is less than 10 min old
		if ( ( $last_ts = $platform->getOption( '_' . $option_name ) )
			&& $last_ts > time() - 600
		) {
			return false;
		}

		// Update last attempt timestamp cache
		$platform->setOption( '_' . $option_name, time(), false );

		try {
			$info = $this->getInfo( $package, $license_keys[ $package ]['type'], $license_key, true );
			if ( empty( $info['download_link'] ) ) {
				throw new Exception\RuntimeException( 'Invalid download link' );
			}
		} catch ( \Exception $e ) {
			error_log( $e->getMessage() );
			if ( ! file_exists( $platform->getSiteAssetsDir() . '/' . $option_name . '.txt' ) ) {
				return false;
			}
		}

		$platform->setOption( $option_name, true, false );
		return true;
	}

	/**
	 * Resets the license key validation cache for a package.
	 *
	 * @param string $package
	 */
	public function reset( $package ) {
		$platform = $this->_application->getPlatform();
		$platform->deleteOption( '_' . md5( $platform->getSiteUrl() . $package ) );
	}

	/**
	 * Gets all stored license keys.
	 *
	 * @return array
	 */
	public function getKeys() {
		return $this->_application->getPlatform()->getOption( 'license_keys', array() );
	}

	/**
	 * Sets license keys for packages.
	 *
	 * @param array $keys
	 * @throws Exception\RuntimeException
	 */
	public function setKeys( array $keys ) {
		$current = $this->getKeys();
		foreach ( array_keys( $keys ) as $package ) {
			if ( is_array( $keys[ $package ] ) ) {
				if ( ! isset( $keys[ $package ]['value'] ) ) {
					throw new Exception\RuntimeException( 'Invalid key' );
				}
			} elseif ( is_string( $keys[ $package ] ) ) {
				$keys[ $package ] = array(
					'value' => $keys[ $package ],
				);
			} else {
				throw new Exception\RuntimeException( 'Invalid key' );
			}
			$keys[ $package ] += array( 'type' => 'envato' );
			if ( ! isset( $current[ $package ] )
				|| $keys[ $package ]['value'] !== $current[ $package ]
			) {
				$this->reset( $package );
			}
		}
		$this->_application->getPlatform()->setOption( 'license_keys', $keys, false );
	}

	/**
	 * Checks if a license key is required for a package.
	 *
	 * @param string $package
	 * @return bool
	 */
	public function isKeyRequired( $package ) {
		return (bool) strpos( $package, '-pro' );
	}

	protected function _licenseKeySettingsForm( array $parents = array() ) {
		$form = array();
		if ( $plugins = $this->_application->getPlatform()->getSabaiPlugins( true, true ) ) {
			$keys = $this->getKeys();
			foreach ( array_keys( $plugins ) as $plugin_name ) {
				if ( ! $this->isKeyRequired( $plugin_name ) ) {
					continue;
				}

				$form[ $plugin_name ]    = array(
					'#type'          => 'textfield',
					'#min_length'    => 36,
					'#max_length'    => 36,
					'#regex'         => '/^[a-z0-9-]+$/',
					'#default_value' => ! $this->_application->isDemoMode() && isset( $keys[ $plugin_name ]['value'] ) ? $keys[ $plugin_name ]['value'] : null,
					'#title'         => $plugins[ $plugin_name ]['Name'],
					'#size'          => 30,
					'#horizontal'    => true,
					'#placeholder'   => 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
					'#weight'        => strpos( $plugins[ $plugin_name ]['Name'], '-' ) ? 1 : 0,
				);
				$plugins[ $plugin_name ] = $plugin_name;
			}
		}
		if ( ! empty( $form ) ) {
			$form['#title']     = __( 'License Keys', 'directories-pro' );
			$form['#submit'][0] = array(
				array( array( $this, '_submitForm' ), array( $parents, $plugins ) ),
			);
		}

		return $form;
	}

	public function _submitForm( Form $form, $parents, $plugins ) {
		$this->setKeys( $form->getValue( $parents ) );
	}
}
