<?php

namespace Velis\User;

use Exception as Exception;
use Velis\App;
use Velis\Cache\ItemPool;
use Velis\Exception as VelisException;
use Velis\Model\DataObject;
use Velis\Model\Sanitizable;
use WhichBrowser\Parser as WhichBrowserParser;

/**
 * User tracking log entry
 * @author Olek Procki <olo@velis.pl>
 */
class TrackerEntry extends DataObject implements Sanitizable
{
    const MAX_LIFETIME = 60;


    /**
     * Set true to skip logging by log() method
     * @var bool
     */
    protected static $_disabled = false;


    /**
     * Set true to respons logging
     * @var bool
     */
    protected static $_logResponse = false;


    /**
     * Set to true to log response when status != 200
     * @var bool
     */
    protected static $_logErrorResponse = false;

    /**
     * Returns related table name
     * @return string
     */
    protected function _getTableName()
    {
        return 'acl.user_tracking_tab';
    }

    /**
     * Logs tracker entry
     * @return bool
     * @throws VelisException
     */
    public static function log()
    {
        try {
            if (self::$_disabled || App::isConsole()) {
                return false;
            }

            $saveEntry = false;
            $request = App::getService('request');

            if ($request->isLogin()) {
                unset(App::$registry['filter']['password']);
            }
            if (App::$user && App::$user->isLogged() && session_id()) {
                $saveEntry = true;
            } elseif ($request->isLogin() && App::$config->settings->trackFailureLogin) {
                unset(App::$registry['filter']['password']);
                $saveEntry = true;
            } elseif ($request->isPublicAction()) {
                $saveEntry = true;
            }

            if (!$saveEntry) {
                return false;
            }

            $entry = new self();

            if (session_id() && (App::$user->id() || App::$session->personId)) {
                if (self::hasField('user_id')) {
                    $entry['user_id'] = App::$user->id();
                } elseif (self::hasField('person_id')) {
                    $entry['person_id'] = App::$user->id() ?: App::$session->personId;
                }
            }

            if (App::$user->getSourceUser() && App::$user->id() != App::$user->getSourceUser()->id()) {
                $entry['source_user_id'] = App::$user->getSourceUser()->id();
            }

            $entry['session_id']     = session_id();
            $entry['ip_address']     = $_SERVER['REMOTE_ADDR'];
            $entry['url']            = mb_substr($_SERVER['REQUEST_URI'], 0, 255);
            $entry['timer']          = microtime(true) - SCRIPT_START;
            $entry['params']         = null;
            $entry['http_method']    = $_SERVER['REQUEST_METHOD'];

            if (count(App::$registry['filter']->getArrayCopy())) {
                $entry['params'] = mb_substr(print_r(App::$registry['filter']->getArrayCopy(), true), 0, 10000);
            } else {
                // log raw json body for API requests
                if (strpos($request->getContentType(), 'application/json') !== false) {
                    if ($rawBody = $request->getJsonRawBody()) {
                        $rawBody = (array)$rawBody;
                        if (isset($rawBody['password'])) {
                            unset($rawBody['password']);
                        }
                        $entry['params'] = mb_substr(print_r($rawBody, true), 0, 10000);
                    }
                }
            }
            $entry['status_code'] = App::getService('response')->getStatusCode();

            if (App::$throttling) {
                $entry['throttling_time'] = App::$throttling->interval;
            }
            if (
                self::$_logResponse
                || (self::$_logErrorResponse && $entry['status_code'] && $entry['status_code'] != 200)
            ) {
                $entry['response'] = App::getService('response')->getContent();
            }

            if (self::hasField('user_agent')) {
                if (class_exists(WhichBrowserParser::class)) {
                    $userPlatform = new WhichBrowserParser($_SERVER['HTTP_USER_AGENT'], ['cache' => new ItemPool()]);

                    if (App::getService('request')->getHeader('Version-Code')) {
                        $entry['user_agent'] = App::getService('request')->getHeader('Version-Code');
                    } else {
                        $entry['user_agent'] = $userPlatform->browser->toString();
                    }
                    if (self::hasField('os_version')) {
                        $entry['os_version'] = $userPlatform->os->toString();
                    }
                } else {
                    if (App::getService('request')->getHeader('Version-Code')) {
                        $entry['user_agent'] = App::getService('request')->getHeader('Version-Code');
                    } else {
                        $entry['user_agent'] = self::getBrowser();
                    }
                }
            }
            $entry->add();

            self::$_disabled = true;

            if (App::$config->settings->userTrackingCleanMethod !== 'cron' && time() % 50 == 0) {
                self::cleanup();
            }

            return true;
        } catch (Exception $e) {
            VelisException::raise('An error occurred during saving user tracking: ' . $e->getMessage(), $e->getCode(), $e);
        }

        return false;
    }

    /**
     * Disables user tracking
     * @param bool $disabled
     */
    public static function disable($disabled = true)
    {
        self::$_disabled = $disabled;
    }

    /**
     * Sets response logging
     * @param bool $logResponse
     */
    public static function logResponse($logResponse = true)
    {
        self::$_logResponse = $logResponse;
    }


    /**
     * Set response error logging
     * @param bool $logResponse
     */
    public static function logErrorResponse($logResponse = true)
    {
        self::$_logErrorResponse = $logResponse;
    }

    /**
     * Removes old entries from user tracking log.
     *
     * @param int|null $ageDays
     */
    public static function cleanup(?int $ageDays = null)
    {
        $ageDays = self::getAgeDays($ageDays);

        $query = "DELETE FROM acl.user_tracking_tab WHERE CURRENT_DATE - :days::integer > date_added";
        self::$_db->execDML($query, ['days' => $ageDays]);
    }

    /**
     * Firstly, try the input $ageDays value - if it's null, take value from the settings: UserTrackingTTL,
     * if it's also null, then take value from the constant: MAX_LIFETIME.
     */
    public static function getAgeDays(?int $ageDays = null): int
    {
        // if $ageDays is null, assign UserTrackingTTL value from settings
        $ageDays ??= App::$config->settings->UserTrackingTTL;

        // if $ageDays is still null, assign MAX_LIFETIME value from constant
        $ageDays ??= self::MAX_LIFETIME;

        return $ageDays;
    }


    /**
     * {@inheritDoc}
     */
    public static function getList($page = 1, $params = null, $order = null, $limit = self::ITEMS_PER_PAGE, $fields = null)
    {
        self::$_listConditions       = null;
        self::$_listParams           = null;
        self::$_countDatasource      = null;
        self::$_listDatasource       = null;

        return parent::getList($page, $params, $order, $limit, $fields);
    }


    /**
     * Get browser info
     * @param string $browser [name of browser]
     * @return string [name and version of browser]
     */
    public static function getBrowser($browser = null)
    {
        $trident = [
            '3' => '7',
            '4' => '8',
            '5' => '9',
            '6' => '10',
            '7' => '11',
        ];

        $browsers = [
            'opera'     => ['OPR/'],
            'ie'        => ['MSIE ', 'Trident/', 'Edge/'],
            'chrome'    => ["Chrome/"],
            'firefox'   => ['Firefox/'],
            'safari'    => ['Version/'],
        ];

        if ($browser) {
            foreach ($browsers[$browser] as $string) {
                if ($math = (int)explode($string, $_SERVER['HTTP_USER_AGENT'])[1]) {
                    $name = $browser;
                    break;
                }
            }
        } else {
            foreach ($browsers as $bName => $type) {
                foreach ($type as $string) {
                    if ($math = (int)explode($string, $_SERVER['HTTP_USER_AGENT'])[1]) {
                        $name = $bName;
                        if ($name == 'safari' && preg_match('/Safari\/([0-9.]+)/', $_SERVER['HTTP_USER_AGENT'], $mathes)) {
                            $math .= ' (' . $mathes[1] . ')';
                        }
                        break 2;
                    }
                }
            }
        }

        if ($name == "Trident/") {
            $math = $trident[(int)$math];
            $name = "MSIE ";
        }

        if ($browser && !$math) {
            return false;
        } elseif ($browser && $math) {
            return $math;
        }

        return ucfirst($name) . ' ' . $math;
    }
}
