<?php

namespace Duplicator\Utils\UsageStatistics;

use DUP_DB;
use DUP_LITE_Plugin_Upgrade;
use DUP_Log;
use DUP_Package;
use DUP_PackageStatus;
use Duplicator\Libs\Snap\SnapDB;
use Duplicator\Libs\Snap\SnapJson;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Libs\Snap\SnapWP;
use ReflectionClass;
use stdClass;
use wpdb;

class PluginData
{
    const PLUGIN_DATA_OPTION_KEY = 'duplicator_plugin_data_stats';
    const IDENTIFIER_CHARS       = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.,;=+&';

    const PLUGIN_STATUS_ACTIVE   = 'active';
    const PLUGIN_STATUS_INACTIVE = 'inactive';

    /**
     * @var ?self
     */
    private static $instance = null;

    /**
     * @var int
     */
    private $lastSendTime = 0;

    /**
     * @var string
     */
    private $identifier = '';

    /**
     * @var string
     */
    private $plugin = 'dup-lite';

    /**
     * @var string
     */
    private $pluginStatus = self::PLUGIN_STATUS_ACTIVE;

    /**
     * @var int
     */
    private $buildCount = 0;

    /**
     * @var int
     */
    private $buildLastDate = 0;

    /**
     * @var int
     */
    private $buildFailedCount = 0;

    /**
     * @var int
     */
    private $buildFailedLastDate = 0;

    /**
     * @var float
     */
    private $siteSizeMB = 0;

    /**
     * @var int
     */
    private $siteNumFiles = 0;

    /**
     * @var float
     */
    private $siteDbSizeMB = 0;

    /**
     * @var int
     */
    private $siteDbNumTables = 0;

    /**
     * Class constructor
     */
    private function __construct()
    {
        if (($data = get_option(self::PLUGIN_DATA_OPTION_KEY)) !== false) {
            $data    = json_decode($data, true);
            $reflect = new ReflectionClass(__CLASS__);
            $props   = $reflect->getProperties();
            foreach ($props as $prop) {
                if (isset($data[$prop->getName()])) {
                    $prop->setAccessible(true);
                    $prop->setValue($this, $data[$prop->getName()]);
                }
            }
        } else {
            $this->identifier = self::generateIdentifier();
            $this->save();
        }
    }

    /**
     * Get instance
     *
     * @return self
     */
    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    /**
     * Save plugin data
     *
     * @return bool True if data has been saved, false otherwise
     */
    public function save()
    {
        $values = get_object_vars($this);
        return update_option(self::PLUGIN_DATA_OPTION_KEY, SnapJson::jsonEncodePPrint($values));
    }

    /**
     * Get identifier
     *
     * @return string
     */
    public function getIdentifier()
    {
        return $this->identifier;
    }

    /**
     * Update from migrate data
     *
     * @param StdClass $data Migration data
     *
     * @return bool
     */
    public function updateFromMigrateData(stdClass $data)
    {
        $save = false;
        if (
            isset($data->ustatIdentifier) &&
            strlen($data->ustatIdentifier) > 0 &&
            $data->ustatIdentifier !== $this->identifier
        ) {
            $this->identifier = $data->ustatIdentifier;
            $save             = true;
        }

        return ($save ? $this->save() : true);
    }

    /**
     * Return usage tracking data
     *
     * @return array<string, mixed>
     */
    public function getDataToSend()
    {
        $result = $this->getBasicInfos();
        $result = array_merge($result, $this->getPluginInfos());
        $result = array_merge($result, $this->getSiteInfos());
        $result = array_merge($result, $this->getManualPackageInfos());
        $result = array_merge($result, $this->getSettingsInfos());

        $rules = array(
            'api_version'      => 'string|max:7', // 1.0
            'identifier'       => 'string|max:44',
            // BASIC INFO
            'plugin_version'   => 'string|max:25',
            'php_version'      => 'string|max:25',
            'wp_version'       => 'string|max:25',
            // PLUGIN INFO
            'pinstall_version' => '?string|max:25',
            // SITE INFO
            'servertype'       => 'string|max:25',
            'db_engine'        => 'string|max:25',
            'db_version'       => 'string|max:25',
            'timezoneoffset'   => 'string|max:10',
            'locale'           => 'string|max:10',
            'themename'        => 'string|max:255',
            'themeversion'     => 'string|max:25',
        );

        return StatsUtil::sanitizeFields($result, $rules);
    }

    /**
     * Get disable tracking data
     *
     * @return array<string, mixed>
     */
    public function getDisableDataToSend()
    {
        $result = $this->getBasicInfos();

        $rules = array(
            'api_version'    => 'string|max:7', // 1.0
            'identifier'     => 'string|max:44',
            // BASIC INFO
            'plugin_version' => 'string|max:25',
            'php_version'    => 'string|max:25',
            'wp_version'     => 'string|max:25',
        );

        return StatsUtil::sanitizeFields($result, $rules);
    }

    /**
     * Set status
     *
     * @param string $status Status: active, inactive or uninstalled
     *
     * @return void
     */
    public function setStatus($status)
    {
        if ($this->pluginStatus === $status) {
            return;
        }

        switch ($status) {
            case self::PLUGIN_STATUS_ACTIVE:
            case self::PLUGIN_STATUS_INACTIVE:
                $this->pluginStatus = $status;
                $this->save();
                break;
        }
    }

    /**
     * Get status
     *
     * @return string Enum: self::PLUGIN_STATUS_ACTIVE, self::PLUGIN_STATUS_INACTIVE or self::PLUGIN_STATUS_UNINSTALLED
     */
    public function getStatus()
    {
        return $this->pluginStatus;
    }

    /**
     * Add paackage build count and date for manual and schedule build
     *
     * @param DUP_Package $package Package
     *
     * @return void
     */
    public function addPackageBuild(DUP_Package $package)
    {
        if ($package->Status == DUP_PackageStatus::COMPLETE) {
            $this->buildCount++;
            $this->buildLastDate = time();
        } else {
            $this->buildFailedCount++;
            $this->buildFailedLastDate = time();
        }

        $this->save();
    }

    /**
     * Set site size
     *
     * @param int $size      Site size in bytes
     * @param int $numFiles  Number of files
     * @param int $dbSize    Database size in bytes
     * @param int $numTables Number of tables
     *
     * @return void
     */
    public function setSiteSize($size, $numFiles, $dbSize, $numTables)
    {
        $this->siteSizeMB      = round(((int) $size) / 1024 / 1024, 2);
        $this->siteNumFiles    = (int) $numFiles;
        $this->siteDbSizeMB    = round(((int) $dbSize) / 1024 / 1024, 2);
        $this->siteDbNumTables = (int) $numTables;
        $this->save();
    }

    /**
     * Update last send time
     *
     * @return void
     */
    public function updateLastSendTime()
    {
        $this->lastSendTime = time();
        $this->save();
    }

    /**
     * Get last send time
     *
     * @return int
     */
    public function getLastSendTime()
    {
        return $this->lastSendTime;
    }

    /**
     * Get basic infos
     *
     * @return array<string, mixed>
     */
    protected function getBasicInfos()
    {
        return array(
            'api_version'    => CommStats::API_VERSION,
            'identifier'     => $this->identifier,
            'plugin'         => $this->plugin,
            'plugin_status'  => $this->pluginStatus,
            'plugin_version' => DUPLICATOR_VERSION,
            'php_version'    => SnapUtil::getVersion(phpversion(), 3),
            'wp_version'     => get_bloginfo('version'),
        );
    }

    /**
     * Return plugin infos
     *
     * @return array<string, mixed>
     */
    protected function getPluginInfos()
    {
        if (($installInfo = DUP_LITE_Plugin_Upgrade::getInstallInfo()) === false) {
            $installInfo = array(
                'version' => null,
                'time'    => null,
            );
        }

        return array(
            'pinstall_date'    => ($installInfo['time'] == null ? null : date('Y-m-d H:i:s', $installInfo['time'])),
            'pinstall_version' => ($installInfo['version'] == null ? null : $installInfo['version']),
            'license_type'     => StatsUtil::getLicenseType(),
            'license_status'   => StatsUtil::getLicenseStatus(),
        );
    }

    /**
     * Return site infos
     *
     * @return array<string, mixed>
     */
    protected function getSiteInfos()
    {
        /** @var wpdb $wpdb */
        global $wpdb;

        $theme_data = wp_get_theme();

        return array(
            'servertype'      => StatsUtil::getServerType(),
            'db_engine'       => SnapDB::getDBEngine($wpdb->dbh), // @phpstan-ignore-line
            'db_version'      => DUP_DB::getVersion(),
            'is_multisite'    => is_multisite(),
            'sites_count'     => count(SnapWP::getSitesIds()),
            'user_count'      => SnapWp::getUsersCount(),
            'timezoneoffset'  => get_option('gmt_offset'), /** @todo evaluate use wp or server timezone offset */
            'locale'          => get_locale(),
            'am_family'       => StatsUtil::getAmFamily(),
            'themename'       => $theme_data->get('Name'),
            'themeversion'    => $theme_data->get('Version'),
            'site_size_mb'    => ($this->siteSizeMB == 0 ? null : $this->siteSizeMB),
            'site_num_files'  => ($this->siteNumFiles == 0 ? null : $this->siteNumFiles),
            'site_db_size_mb' => ($this->siteDbSizeMB == 0 ? null : $this->siteDbSizeMB),
            'site_db_num_tbl' => ($this->siteDbNumTables == 0 ? null : $this->siteDbNumTables),
        );
    }

    /**
     * Return manal package infos
     *
     * @return array<string, mixed>
     */
    protected function getManualPackageInfos()
    {
        return array(
            'packages_build_count'                         => $this->buildCount,
            'packages_build_last_date'                     => ($this->buildLastDate == 0 ? null : date('Y-m-d H:i:s', $this->buildLastDate)),
            'packages_build_failed_count'                  => $this->buildFailedCount,
            'packages_build_failed_last_date'              => ($this->buildFailedLastDate == 0 ? null : date('Y-m-d H:i:s', $this->buildFailedLastDate)),
            'packages_count'                               => DUP_Package::getNumCompletePackages(),
        );
    }

    /**
     * Return granular permissions infos
     *
     * @return array<string, mixed>
     */
    protected function getSettingsInfos()
    {
        return array(
            'settings_archive_build_mode' => StatsUtil::getArchiveBuildMode(),
            'settings_db_build_mode'      => StatsUtil::getDbBuildMode(),
            'settings_usage_enabled'      =>  StatsBootstrap::isTrackingAllowed(),
        );
    }

    /**
     * Return unique identifier
     *
     * @return string
     */
    protected static function generateIdentifier()
    {
        $maxRand = strlen(self::IDENTIFIER_CHARS) - 1;

        $result = '';
        for ($i = 0; $i < 44; $i++) {
            $result .= substr(self::IDENTIFIER_CHARS, wp_rand(0, $maxRand), 1);
        }

        return $result;
    }
}
