<?php

namespace LLAR\Core;

use Exception;
use LLAR\Core\Http\Http;

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

class CloudApp
{
	/**
	 * @var null|string
	 */
	private $id = null;

	/**
	 * @var null|string
	 */
	private $api = null;

	/**
	 * @var array
	 */
	private $config = array();

	/**
	 * @var array
	 */
	private $login_errors = array();

	/**
	 * @var null
	 */
	public $last_response_code = null;

	/**
	 * @var string|null
	 */
	public $last_error_message = null;

	/**
	 * @var array
	 */
	private $stats_cache = array();

	/**
	 * App constructor.
	 * @param array $config
	 */
	public function __construct( array $config )
	{
		if ( empty( $config ) ) {
			return false;
		}

		$this->id = 'app_' . $config['id'];
		$this->api = $config['api'];
		$this->config = $config;
	}

	/**
	 * @param $error
	 * @return bool
	 */
	public function add_error( $error )
	{
		if ( ! $error ) {
			return false;
		}

		$this->login_errors[] = $error;
	}

	/**
	 * @return array
	 */
	public function get_errors()
	{
		return $this->login_errors;
	}

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

	/**
	 * @return array
	 */
	public function get_config()
	{
		return $this->config;
	}

	/**
	 * @param $link
	 * @return false[]
	 */
	public static function setup( $link )
	{
		$return = array(
			'success' => false,
		);

		if ( empty( $link ) ) {
			return $return;
		}

		$link = 'https://' . $link;

		$domain = parse_url( home_url( '/' ) );
		$link = add_query_arg( 'domain', $domain['host'], $link );

		$plugin_data = get_plugin_data( LLA_PLUGIN_DIR . 'limit-login-attempts-reloaded.php' );
		$link = add_query_arg( 'version', $plugin_data['Version'], $link );

		$setup_response = Http::get( $link );

		$response_data = isset( $setup_response['data'] ) ? $setup_response['data'] : '{}';
		$setup_response_body = json_decode( $response_data, true );

		if ( ! empty( $setup_response['error'] ) ) {

			$return['error'] = $setup_response['error'];

		} elseif( $setup_response['status'] === 200 ) {

			$return['success'] = true;
			$return['app_config'] = $setup_response_body;

		} else {

			$return['error'] = ( ! empty( $setup_response_body['message'] ) )
								? $setup_response_body['message']
								: __( 'The endpoint is not responding. Please contact your app provider to settle that.', 'limit-login-attempts-reloaded' );

			$return['response_code'] = $setup_response['status'];
		}

		return $return;
	}

	public static function activate_license_key( $setup_code )
	{
		$link = strrev( $setup_code );
		$setup_result = self::setup( $link );

		if ( $setup_result['success'] ) {

			if ( $setup_result['app_config'] ) {

				Helpers::cloud_app_update_config( $setup_result['app_config'], true );

				Config::update( Config::OPTION_ACTIVE_APP, 'custom' );
				Config::update( 'app_setup_code', $setup_code );

				$setup_result['app_config']['messages']['setup_success'] =
					! empty( $setup_result['app_config']['messages']['setup_success'] )
					? $setup_result['app_config']['messages']['setup_success']
					: __( 'The app has been successfully imported.', 'limit-login-attempts-reloaded' );

				return $setup_result;
			}
		} else {

			return $setup_result;
		}

		return $setup_result;
	}

	/**
	 * @return bool|mixed
	 * @throws Exception
	 */
	public function stats()
	{
		if ( empty( $this->stats_cache ) ) {
			$this->stats_cache = $this->request( 'stats' );
		}

		return $this->stats_cache;
	}

	/**
	 * @return bool|mixed
	 */
	public static function stats_global()
	{
		$response = Http::get( 'https://api.limitloginattempts.com/v1/global-stats' );

		if ( $response['status'] !== 200 ) {
			return false;
		}

		return json_decode( $response['data'], true );
	}

	/**
	 * @param $data
	 *
	 * @return bool|mixed
	 * @throws Exception
	 */
	public function acl_check( $data )
	{
		$this->prepare_settings( 'acl', $data );

		return $this->request( 'acl', 'post', $data );
	}

	/**
	 * @param $data
	 *
	 * @return bool|mixed
	 * @throws Exception
	 */
	public function acl( $data )
	{
		return $this->request( 'acl', 'get', $data );
	}

    /**
     * @return bool|mixed
     * @throws Exception
     */
    public function info()
    {
        $info = $this->request_info();

        if ( ! $info && ! defined( 'LLAR_FAILED_INFO_NOTICE_SHOWN' ) && $this->is_info_network_failure() ) {
            define( 'LLAR_FAILED_INFO_NOTICE_SHOWN', true );
            $this->render_info_network_failure_notice();
        }

        return $info;
    }

    /**
     * Whether the last /info call failed before reaching the Cloud API (transport / DNS / timeout).
     *
     * @return bool
     */
    public function is_info_network_failure()
    {
        if ( 0 < (int) $this->last_response_code ) {
            return false;
        }

        return true;
    }

    /**
     * @return bool|mixed
     * @throws Exception
     */
    private function request_info()
    {
        if ( ! $this->api ) {
            throw new Exception( 'Cloud API endpoint is not configured.' );
        }

        $headers   = array();
        $headers[] = "{$this->config['header']}: {$this->config['key']}";

        $response = Http::get(
            $this->api . '/info',
            array(
                'headers' => $headers,
            )
        );

        $this->last_response_code = ! empty( $response['status'] ) ? $response['status'] : 0;
        $this->last_error_message = ! empty( $response['error'] ) ? $response['error'] : null;

        $info = false;
        if ( ! empty( $response['data'] ) ) {
            $decoded = json_decode( $response['data'], true );
            if ( is_array( $decoded ) && ! empty( $decoded ) ) {
                $info = Helpers::sanitize_stripslashes_deep( $decoded );
            }
        }

        if ( 200 !== (int) $this->last_response_code ) {
            error_log( 'LLAR: CloudApp info request failed: ' . $this->api . '/info ' . $this->last_response_code );
        }

        return $info;
    }

    /**
     * Admin notice when /info could not reach the Cloud API at all.
     */
    private function render_info_network_failure_notice()
    {
        $details = array();

        if ( null !== $this->last_response_code ) {
            $details[] = sprintf(
                /* translators: %d: HTTP response code. */
                __( 'Code: %d', 'limit-login-attempts-reloaded' ),
                intval( $this->last_response_code )
            );
        }

        if ( ! empty( $this->last_error_message ) ) {
            $details[] = sprintf(
                /* translators: %s: technical error reason. */
                __( 'Reason: %s', 'limit-login-attempts-reloaded' ),
                esc_html( $this->get_readable_error_reason( $this->last_error_message ) )
            );
        }

        $message = __( 'Oops, it looks like the site is having a temporary network issue.', 'limit-login-attempts-reloaded' );

        if ( ! empty( $details ) ) {
            $message .= ' (' . implode( '; ', $details ) . ')';
        }

        $refresh_label = __( 'Click here to refresh the page', 'limit-login-attempts-reloaded' );

        echo '<div class="notice notice-error" style="display: block;"><p>' . $message . '</p>';
        echo '<p><a href="javascript:void(0);" onclick="window.location.reload();" class="button button-primary">' . esc_html( $refresh_label ) . '</a></p></div>';
    }

	/**
	 * @param $data
	 *
	 * @return bool|mixed
	 * @throws Exception
	 */
	public function acl_create( $data )
	{
		return $this->request( 'acl/create', 'post', $data );
	}

	/**
	 * @param $data
	 *
	 * @return bool|mixed
	 * @throws Exception
	 */
	public function acl_delete( $data )
	{
		return $this->request( 'acl/delete', 'post', $data );
	}

	/**
	 * @return bool|mixed
	 * @throws Exception
	 */
	public function country()
	{
		return $this->request( 'country', 'get' );
	}

	/**
	 * @param $data
	 * @return bool|mixed
	 * @throws Exception
	 */
	public function country_add( $data )
	{
		return $this->request( 'country/add', 'post', $data );
	}

	/**
	 * @param $data
	 * @return bool|mixed
	 * @throws Exception
	 */
	public function country_remove( $data )
	{
		return $this->request( 'country/remove', 'post', $data );
	}

	/**
	 * @param $data
	 *
	 * @return bool|mixed
	 * @throws Exception
	 */
	public function country_rule( $data )
	{
		return $this->request( 'country/rule', 'post', $data );
	}

	/**
	 * @param $data
	 *
	 * @return bool|mixed
	 * @throws Exception
	 */
	public function lockout_check( $data )
	{
		$this->prepare_settings( 'lockout', $data );

		return $this->request( 'lockout', 'post', $data );
	}

	/**
	 * @param int $limit
	 * @param string $offset
	 *
	 * @return bool|mixed
	 * @throws Exception
	 */
	public function log($limit = 25, $offset = '')
	{
		$data = array();

		$data['limit'] = $limit;
		$data['offset'] = $offset;
		$data['is_short'] = 1;

		return $this->request( 'log', 'get', $data );
	}


	/**
	 * @param int $limit
	 * @param string $offset
	 *
	 * @return bool|mixed
	 * @throws Exception
	 */
	public function get_login($limit = 25, $offset = '')
	{
		$data = array();

		$data['limit'] = $limit;
		$data['offset'] = $offset;
		$data['is_short'] = 1;

		return $this->request( 'login', 'get', $data );
	}


	/**
	 * @param int $limit
	 * @param string $offset
	 *
	 * @return bool|mixed
	 * @throws Exception
	 */
	public function get_lockouts($limit = 25, $offset = '')
	{
		$data = array();

		$data['limit'] = $limit;
		$data['offset'] = $offset;

		return $this->request( 'lockout', 'get', $data );
	}

	/**
	 * Prepare settings for API request
	 *
	 * @param $method
	 */
	public function prepare_settings( $method, &$data )
	{
		$settings = array();

		if ( ! empty( $this->config['settings'] ) ) {

			foreach ( $this->config['settings'] as $setting_name => $setting_data ) {

				if ( in_array( $method, $setting_data['methods'] ) ) {
					$settings[$setting_name] = $setting_data['value'];
				}
			}
		}

		if ( $settings ) {
			$data['settings'] = $settings;
		}
	}

	/**
	 * @param $method
	 * @param string $type
	 * @param null $data
	 * @return bool|mixed
	 * @throws Exception
	 */
	public function request( $method, $type = 'get', $data = null )
	{
		if ( ! $method ) {
			throw new Exception( 'You must specify API method.' );
		}

		$headers = array();
		$headers[] = "{$this->config['header']}: {$this->config['key']}";

		$response = Http::$type( $this->api . '/' . $method, array(
			'data'      => $data,
			'headers'   => $headers
		) );

		$this->last_response_code = !empty( $response['status'] ) ? $response['status'] : 0;
		$this->last_error_message = ! empty( $response['error'] ) ? $response['error'] : null;

		if ( 200 !== $response['status'] ) {
			error_log( 'LLAR: CloudApp request failed: ' . $this->api . '/' . $method . ' ' . $this->last_response_code );
			return false;
		}

		$decoded = json_decode( $response['data'], true );

		return Helpers::sanitize_stripslashes_deep( $decoded );
	}

	/**
	 * Tries to keep technical reason readable and avoid leaking full URL.
	 *
	 * @param string $error_message
	 * @return string
	 */
	private function get_readable_error_reason( $error_message ) {
		$reason = trim( (string) $error_message );

		if ( false !== strpos( $reason, 'Failed to open stream:' ) ) {
			$parts = explode( 'Failed to open stream:', $reason, 2 );
			if ( ! empty( $parts[1] ) ) {
				$reason = trim( $parts[1] );
			}
		}

		if ( 220 < strlen( $reason ) ) {
			$reason = substr( $reason, 0, 217 ) . '...';
		}

		return $reason;
	}
}
