<?php

namespace CleantalkSP\SpbctWP\Variables;

use CleantalkSP\Variables\Server;
use CleantalkSP\SpbctWP\Helpers\IP;
use CleantalkSP\Common\TT;
use WP_REST_Request;

class AltSessions
{
    /**
     * Allowed alt cookies and their types (adapted from apbct)
     */
    private static $allowed_alt_cookies = [
        'spbc_admin_logged_in' => 'hash',
        'spbc_cookies_test' => 'json',
        'spbc_is_logged_in' => 'hash',
        'spbc_log_id' => 'int',
        'spbc_secfw_ip_wl' => 'hash',
        'spbc_timer' => 'int',
    ];
    /**
     * Sessions life time
     */
    const SESSION__LIVE_TIME = 86400;

    /**
     * The chance to run cleanup (for each request) from old entries in percents
     */
    const SESSION__CHANCE_TO_CLEAN = 10;

    public static $sessions_already_cleaned = false;

    public static function getID()
    {
        $id = Ip::get('real')
              . Server::getString('HTTP_USER_AGENT')
              . Server::getString('HTTP_ACCEPT_LANGUAGE');

        return hash('sha256', $id);
    }

    /**
     * Write alternative cookie to database
     *
     * @param string $name
     * @param string $value
     *
     * @return bool
     */
    public static function set($name, $value)
    {
        self::cleanFromOld();

        // Check if the cookie is allowed
        if (empty($name) || !isset(self::$allowed_alt_cookies[$name])) {
            return false;
        }

        switch (self::$allowed_alt_cookies[$name]) {
            case 'int':
                $value = (int)$value;
                break;
            case 'bool':
                $value = (bool)$value;
                break;
            case 'string':
                $value = (string)$value;
                break;
            case 'json':
                if (is_array($value) || is_object($value)) {
                    $value = json_encode($value);
                }
                if (!is_string($value) || json_decode($value) === null) {
                    return false;
                }
                break;
            case 'url':
                if (!filter_var($value, FILTER_VALIDATE_URL)) {
                    return false;
                }
                break;
            case 'hash':
                if (!preg_match('/^[a-f0-9]{32,128}$/', $value)) {
                    return false;
                }
                break;
            default:
                return false;
        }

        global $wpdb;
        $session_id = self::getID();
        $q = $wpdb->prepare(
            'INSERT INTO ' . SPBC_TBL_SESSIONS . '
				(id, name, value, last_update)
				VALUES (%s, %s, %s, %s)
			ON DUPLICATE KEY UPDATE
				value = %s,
				last_update = %s',
            $session_id,
            $name,
            $value,
            date('Y-m-d H:i:s'),
            $value,
            date('Y-m-d H:i:s')
        );

        return (bool)$wpdb->query($q);
    }

    /**
     * @param null|\WP_REST_Request $request
     *
     * @return void Sends json to the JS handler
     * @psalm-suppress PossiblyUnusedMethod
     */
    public static function setFromRemote($request = null)
    {
        // Nonce check (for REST/AJAX)
        if ($request instanceof \WP_REST_Request) {
            $nonce = $request->get_header('x_wp_nonce');
            $action = 'wp_rest';
            $cookies_to_set = $request->get_param('cookies');
        } else {
            $nonce = isset($_POST['_ajax_nonce']) ? $_POST['_ajax_nonce'] : null;
            $action = 'ct_secret_stuff';
            $cookies_to_set = isset($_POST['cookies']) ? $_POST['cookies'] : null;
        }

        if (!$cookies_to_set) {
            wp_send_json([
                'success' => false,
                'error' => 'AltSessions: No cookies data provided.'
            ]);
        }

        if (empty($nonce)) {
            wp_send_json([
                'success' => false,
                'error' => 'AltSessions: No nonce provided.'
            ]);
        }
        if (!wp_verify_nonce($nonce, $action)) {
            wp_send_json([
                'success' => false,
                'error' => 'AltSessions: Nonce verification failed. Please reload the page and try again.'
            ]);
        }

        // Remove double slashes
        $cookies_to_set = str_replace('\\', '', $cookies_to_set);

        // Decode JSON
        try {
            $cookies_array = json_decode($cookies_to_set, true);
        } catch (\Exception $e) {
            wp_send_json([
                'success' => false,
                'error' => 'AltSessions: Internal JSON error: ' . json_last_error_msg()
            ]);
        }

        // Convert array of arrays to object
        if (is_array($cookies_array) && isset($cookies_array[0]) && is_array($cookies_array[0])) {
            $prepared_cookies_array = array();
            foreach ($cookies_array as $cookie) {
                if (is_array($cookie) && isset($cookie[0]) && isset($cookie[1])) {
                    $prepared_cookies_array[$cookie[0]] = $cookie[1];
                }
            }
            $cookies_array = $prepared_cookies_array;
        }

        // Validate against allowed cookies
        foreach ($cookies_array as $name => $value) {
            if (!array_key_exists($name, self::$allowed_alt_cookies)) {
                unset($cookies_array[$name]);
                continue;
            }
            switch (self::$allowed_alt_cookies[$name]) {
                case 'int':
                    $cookies_array[$name] = (int)$value;
                    break;
                case 'bool':
                    $cookies_array[$name] = (bool)$value;
                    break;
                case 'string':
                    $cookies_array[$name] = (string)$value;
                    break;
                case 'json':
                    if (!is_string($value) || json_decode($value) === null) {
                        unset($cookies_array[$name]);
                    }
                    break;
                case 'url':
                    if (!filter_var($value, FILTER_VALIDATE_URL)) {
                        unset($cookies_array[$name]);
                    }
                    break;
                case 'hash':
                    if (!preg_match('/^[a-f0-9]{32,128}$/', $value)) {
                        unset($cookies_array[$name]);
                    }
                    break;
                default:
                    unset($cookies_array[$name]);
            }
        }

        if (is_null($cookies_array)) {
            wp_send_json([
                'success' => false,
                'error' => 'AltSessions: Internal JSON error: ' . json_last_error_msg()
            ]);
        }

        // Merge with old values if needed (not implemented here, but can be added)
        foreach ($cookies_array as $name => $value) {
            Cookie::set($name, $value);
        }

        wp_send_json(['success' => true]);
    }

    public static function get($name)
    {
        self::cleanFromOld();

        // Bad incoming data
        if ( ! $name ) {
            return;
        }

        global $wpdb;

        $session_id = self::getID();
        $result     = $wpdb->get_row(
            $wpdb->prepare(
                'SELECT value
				FROM `' . SPBC_TBL_SESSIONS . '`
				WHERE id = %s AND name = %s;',
                $session_id,
                $name
            ),
            ARRAY_A
        );

        return isset($result['value']) ? $result['value'] : '';
    }

    public static function cleanFromOld()
    {
        global $wpdb;

        if ( ! self::$sessions_already_cleaned && rand(0, 100) < self::SESSION__CHANCE_TO_CLEAN ) {
            self::$sessions_already_cleaned = true;

            $wpdb->query(
                $wpdb->prepare(
                    'DELETE FROM ' . SPBC_TBL_SESSIONS . '
                    WHERE last_update < NOW() - INTERVAL %d SECOND
                    LIMIT 100000;',
                    self::SESSION__LIVE_TIME
                )
            );
        }
    }

    /**
     * Clear all data in Alt sessions table
     *
     * @return bool|int|\mysqli_result|resource|null
     * @psalm-suppress PossiblyUnusedMethod
     */
    public static function wipe()
    {
        global $wpdb;

        // @psalm-suppress WpdbUnsafeMethodsIssue
        return $wpdb->query('TRUNCATE TABLE ' . SPBC_TBL_SESSIONS . ';');
    }
}
