<?php

namespace Account;

use Analyst\Analyst;
use Analyst\ApiRequestor;
use Analyst\Cache\DatabaseCache;
use Analyst\Collector;
use Analyst\Http\Requests\ActivateRequest;
use Analyst\Http\Requests\DeactivateRequest;
use Analyst\Http\Requests\InstallRequest;
use Analyst\Http\Requests\OptInRequest;
use Analyst\Http\Requests\OptOutRequest;
use Analyst\Http\Requests\UninstallRequest;
use Analyst\Notices\Notice;
use Analyst\Notices\NoticeFactory;
use Analyst\Contracts\TrackerContract;
use Analyst\Contracts\RequestorContract;

/**
 * Class Account
 *
 * This is plugin's account object
 */
class Account implements TrackerContract
{
	/**
	 * Account id
	 *
	 * @var string
	 */
	protected $id;

	/**
	 * Basename of plugin
	 *
	 * @var string
	 */
	protected $path;

	/**
	 * Whether plugin is active or not
	 *
	 * @var bool
	 */
	protected $isInstalled = false;

	/**
	 * Is user sign in for data tracking
	 *
	 * @var bool
	 */
	protected $isOptedIn = false;

	/**
	 * Is user accepted permissions grant
	 * for collection site data
	 *
	 * @var bool
	 */
	protected $isSigned = false;

	/**
	 * Is user ever resolved install modal window?
	 *
	 * @var bool
	 */
	protected $isInstallResolved = false;

	/**
	 * Public secret code
	 *
	 * @var string
	 */
	protected $clientSecret;

	/**
	 * @var AccountData
	 */
	protected $data;

	/**
	 * Base plugin path
	 *
	 * @var string
	 */
	protected $basePluginPath;

	/**
	 * @var RequestorContract
	 */
	protected $requestor;

	/**
	 * @var Collector
	 */
	protected $collector;

	/**
	 * Account constructor.
	 * @param $id
	 * @param $secret
	 * @param $baseDir
	 */
	public function __construct($id, $secret, $baseDir)
	{
		$this->id = $id;
		$this->clientSecret = $secret;

		$this->path = $baseDir;

		$this->basePluginPath = plugin_basename($baseDir);
	}

	/**
	 * @return string
	 */
	public function getPath()
	{
		return $this->path;
	}

	/**
	 * @param string $path
	 */
	public function setPath($path)
	{
		$this->data->setPath($path);

		$this->path = $path;
	}

	/**
	 * @return bool
	 */
	public function isOptedIn()
	{
		return $this->isOptedIn;
	}

	/**
	 * @param bool $isOptedIn
	 */
	public function setIsOptedIn($isOptedIn)
	{
		$this->data->setIsOptedIn($isOptedIn);

		$this->isOptedIn = $isOptedIn;
	}

	/**
	 * Whether plugin is active
	 *
	 * @return bool
	 */
	public function isActive()
	{
		return is_plugin_active($this->path);
	}

	/**
	 * @param string $id
	 */
	public function setId($id)
	{
		$this->id = $id;
	}

	/**
	 * @return string
	 */
	public function getId()
	{
		return $this->id;
	}

	/**
	 * @return bool
	 */
	public function isInstalled()
	{
		return $this->isInstalled;
	}

	/**
	 * @param bool $isInstalled
	 */
	public function setIsInstalled($isInstalled)
	{
		$this->data->setIsInstalled($isInstalled);

		$this->isInstalled = $isInstalled;
	}
  
  protected function verifyNonceAndPerms() {
    
    $capabilities = [
      'activate_plugins',
      'edit_plugins',
      'install_plugins',
      'update_plugins',
      'delete_plugins',
      'manage_network_plugins',
      'upload_plugins'
    ];
    
    // Allow if has any of above permissions
    $hasPerms = false;
    foreach ($capabilities as $i => $cap) {
      if (current_user_can($cap)) {
        $hasPerms = true;
        break;
      }
    }
    
    if ($hasPerms == false) {
      wp_send_json_error(['message' => 'no_permissions']);
      die;
    }
    
    if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field($_POST['nonce']), 'analyst_opt_ajax_nonce')) {
      wp_send_json_error(['message' => 'invalid_nonce']);
      die;
    }
      
  }

	/**
	 * Should register activation and deactivation
	 * event hooks
	 *
	 * @return void
	 */
	public function registerHooks()
	{
		register_activation_hook($this->basePluginPath, [&$this, 'onActivePluginListener']);
		register_uninstall_hook($this->basePluginPath, ['Account\Account', 'onUninstallPluginListener']);

		$this->addFilter('plugin_action_links', [&$this, 'onRenderActionLinksHook']);

		$this->addAjax('analyst_opt_in', [&$this, 'onOptInListener']);
		$this->addAjax('analyst_opt_out', [&$this, 'onOptOutListener']);
		$this->addAjax('analyst_plugin_deactivate', [&$this, 'onDeactivatePluginListener']);
		$this->addAjax('analyst_install', [&$this, 'onInstallListener']);
		$this->addAjax('analyst_skip_install', [&$this, 'onSkipInstallListener']);
		$this->addAjax('analyst_install_verified', [&$this, 'onInstallVerifiedListener']);
	}

	/**
	 * Will fire when admin activates plugin
	 *
	 * @return void
	 */
	public function onActivePluginListener()
	{
		if (!$this->isInstallResolved()) {
			DatabaseCache::getInstance()->put('plugin_to_install', $this->id);
		}

		if (!$this->isAllowingLogging()) return;

		ActivateRequest::make($this->collector, $this->id, $this->path)
			->execute($this->requestor);

		$this->setIsInstalled(true);

		AccountDataFactory::syncData();
	}

	/**
	 * Will fire when admin deactivates plugin
	 *
	 * @return void
	 */
	public function onDeactivatePluginListener()
	{
    $this->verifyNonceAndPerms();
    
		if (!$this->isAllowingLogging()) return;

		$question = isset($_POST['question']) ? sanitize_text_field(stripslashes($_POST['question'])) : null;
        $reason = isset($_POST['reason']) ? sanitize_text_field(stripslashes($_POST['reason'])) : null;

		DeactivateRequest::make($this->collector, $this->id, $this->path, $question, $reason)
			->execute($this->requestor);

		$this->setIsInstalled(false);

		AccountDataFactory::syncData();

		wp_send_json_success();
	}

	/**
	 * Will fire when user opted in
	 *
	 * @return void
	 */
	public function onOptInListener()
	{
    $this->verifyNonceAndPerms();
    
		OptInRequest::make($this->collector, $this->id, $this->path)->execute($this->requestor);

		$this->setIsOptedIn(true);

		AccountDataFactory::syncData();

		wp_die();
	}

	/**
	 * Will fire when user opted out
	 *
	 * @return void
	 */
	public function onOptOutListener()
	{
    $this->verifyNonceAndPerms();
    
		OptOutRequest::make($this->collector, $this->id, $this->path)->execute($this->requestor);

		$this->setIsOptedIn(false);

		AccountDataFactory::syncData();

		wp_send_json_success();
	}

	/**
	 * Will fire when user accept opt-in
	 * at first time
	 *
	 * @return void
	 */
	public function onInstallListener()
	{
    $this->verifyNonceAndPerms();
    
		$cache = DatabaseCache::getInstance();

		// Set flag to true which indicates that install is resolved
		// also remove install plugin id from cache
		$this->setIsInstallResolved(true);
		$cache->delete('plugin_to_install');

		InstallRequest::make($this->collector, $this->id, $this->path)->execute($this->requestor);

		$this->setIsSigned(true);

		$this->setIsOptedIn(true);

		$factory = NoticeFactory::instance();

		$message = sprintf('Please confirm your email by clicking on the link we sent to %s. This makes sure you’re not a bot.', $this->collector->getGeneralEmailAddress());

		$notificationId = uniqid();

		$notice = Notice::make(
			$notificationId,
			$this->getId(),
			$message,
			$this->collector->getPluginName($this->path)
		);

		$factory->addNotice($notice);

		AccountDataFactory::syncData();

		// Set email confirmation notification id to cache
		// se we can extract and remove it when user confirmed email
		$cache->put(
			sprintf('account_email_confirmation_%s', $this->getId()),
			$notificationId
		);

		wp_send_json_success();
	}

	/**
	 * Will fire when user skipped installation
	 *
	 * @return void
	 */
	public function onSkipInstallListener()
	{
    $this->verifyNonceAndPerms();
    
		// Set flag to true which indicates that install is resolved
		// also remove install plugin id from cache
		$this->setIsInstallResolved(true);
		DatabaseCache::getInstance()->delete('plugin_to_install');
	}

	/**
	 * Will fire when user delete plugin through admin panel.
	 * This action will happen if admin at least once
	 * activated the plugin.
	 *
	 * @return void
	 * @throws \Exception
	 */
	public static function onUninstallPluginListener()
	{
		$factory = AccountDataFactory::instance();

		$pluginFile = substr(current_filter(), strlen( 'uninstall_' ));

		$account = $factory->getAccountDataByBasePath($pluginFile);

		// If account somehow is not found, exit the execution
		if (!$account) return;

		$analyst = Analyst::getInstance();

		$collector = new Collector($analyst);

		$requestor = new ApiRequestor($account->getId(), $account->getSecret(), $analyst->getApiBase());

		// Just send request to log uninstall event not caring about response
		UninstallRequest::make($collector, $account->getId(), $account->getPath())->execute($requestor);

		$factory->sync();
	}

	/**
	 * Fires when used verified his account
	 */
	public function onInstallVerifiedListener()
	{
    $this->verifyNonceAndPerms();
    
		$factory = NoticeFactory::instance();

		$notice = Notice::make(
			uniqid(),
			$this->getId(),
			'Thank you for confirming your email.',
			$this->collector->getPluginName($this->path)
		);

		$factory->addNotice($notice);

		// Remove confirmation notification
		$confirmationNotificationId = DatabaseCache::getInstance()->pop(sprintf('account_email_confirmation_%s', $this->getId()));
		$factory->remove($confirmationNotificationId);

		AccountDataFactory::syncData();

		wp_send_json_success();
	}

	/**
	 * Will fire when wp renders plugin
	 * action buttons
	 *
	 * @param $defaultLinks
	 * @return array
	 */
	public function onRenderActionLinksHook($defaultLinks)
	{
		$customLinks = [];

		$customLinks[] = $this->isOptedIn()
			? '<a class="analyst-action-opt analyst-opt-out" analyst-plugin-id="' . $this->getId() . '"  analyst-plugin-signed="' . (int) $this->isSigned() . '">Opt Out</a>'
			: '<a class="analyst-action-opt analyst-opt-in" analyst-plugin-id="' . $this->getId() . '" analyst-plugin-signed="' . (int) $this->isSigned() . '">Opt In</a>';

		// Append anchor to find specific deactivation link
		if (isset($defaultLinks['deactivate'])) {
			$defaultLinks['deactivate'] .= '<span analyst-plugin-id="' . $this->getId() . '" analyst-plugin-opted-in="' . (int) $this->isOptedIn() . '"></span>';
		}

		return array_merge($customLinks, $defaultLinks);
	}

	/**
	 * @return AccountData
	 */
	public function getData()
	{
		return $this->data;
	}

	/**
	 * @param AccountData $data
	 */
	public function setData(AccountData $data)
	{
		$this->data = $data;

		$this->setIsOptedIn($data->isOptedIn());
		$this->setIsInstalled($data->isInstalled());
		$this->setIsSigned($data->isSigned());
		$this->setIsInstallResolved($data->isInstallResolved());
	}

	/**
	 * Resolves valid action name
	 * based on client id
	 *
	 * @param $action
	 * @return string
	 */
	private function resolveActionName($action)
	{
		return sprintf('%s_%s', $action, $this->id);
	}

	/**
	 * Register action for current plugin
	 *
	 * @param $action
	 * @param $callback
	 */
	private function addFilter($action, $callback)
	{
		$validAction = sprintf('%s_%s', $action, $this->basePluginPath);

		add_filter($validAction, $callback, 10);
	}

	/**
	 * Add ajax action for current plugin
	 *
	 * @param $action
	 * @param $callback
	 * @param bool $raw Format action ??
	 */
	private function addAjax($action, $callback, $raw = false)
	{
		$validAction = $raw ? $action : sprintf('%s%s', 'wp_ajax_', $this->resolveActionName($action));

		add_action($validAction, $callback);
	}

	/**
	 * @return bool
	 */
	public function isSigned()
	{
		return $this->isSigned;
	}

	/**
	 * @param bool $isSigned
	 */
	public function setIsSigned($isSigned)
	{
		$this->data->setIsSigned($isSigned);

		$this->isSigned = $isSigned;
	}

	/**
	 * @return RequestorContract
	 */
	public function getRequestor()
	{
		return $this->requestor;
	}

	/**
	 * @param RequestorContract $requestor
	 */
	public function setRequestor(RequestorContract $requestor)
	{
		$this->requestor = $requestor;
	}

	/**
	 * @return string
	 */
	public function getClientSecret()
	{
		return $this->clientSecret;
	}

	/**
	 * @return Collector
	 */
	public function getCollector()
	{
		return $this->collector;
	}

	/**
	 * @param Collector $collector
	 */
	public function setCollector(Collector $collector)
	{
		$this->collector = $collector;
	}

	/**
	 * Do we allowing logging
	 *
	 * @return bool
	 */
	public function isAllowingLogging()
	{
		return $this->isOptedIn;
	}

	/**
	 * @return string
	 */
	public function getBasePluginPath()
	{
		return $this->basePluginPath;
	}

	/**
	 * @return bool
	 */
	public function isInstallResolved()
	{
		return $this->isInstallResolved;
	}

	/**
	 * @param bool $isInstallResolved
	 */
	public function setIsInstallResolved($isInstallResolved)
	{
		$this->data->setIsInstallResolved($isInstallResolved);

		$this->isInstallResolved = $isInstallResolved;
	}
}
