<?php

namespace Duplicator\Utils\UsageStatistics;

use Duplicator\Package\AbstractPackage;
use Duplicator\Core\Upgrade\UpgradePlugin;
use Duplicator\Core\UniqueId;
use Duplicator\Installer\Models\MigrateData;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Libs\Snap\SnapWP;
use Duplicator\Libs\WpUtils\WpDbUtils;
use Duplicator\Package\Create\BuildComponents;
use Duplicator\Package\DupPackage;
use Duplicator\Package\PackageUtils;
use VendorDuplicator\Amk\JsonSerialize\JsonSerialize;
use wpdb;

class PluginData
{
    const PLUGIN_DATA_OPTION_KEY = 'dupli_opt_plugin_data_stats';

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

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

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

    /**
     * @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 int
     */
    private $packagesBuildCompFullCount = 0;

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

    /**
     * @var int
     */
    private $usedRecoveryCount = 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) {
            JsonSerialize::unserializeToObj($data, $this);
        } else {
            $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()
    {
        return update_option(self::PLUGIN_DATA_OPTION_KEY, JsonSerialize::serialize($this, JSON_PRETTY_PRINT));
    }

    /**
     * Update from migrate data
     *
     * @param MigrateData $data Migration data
     *
     * @return bool
     */
    public function updateFromMigrateData(MigrateData $data)
    {
        $save = false;
        if ($data->recoveryMode) {
            $this->usedRecoveryCount++;
            $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());

        /** @var array<string, mixed> */
        $result = apply_filters('duplicator_usage_stats_plugin_data', $result, $this);

        $rules = [
            'api_version'      => 'string|max:7',
            'identifier'       => 'string|max:44',
            'plugin_version'   => 'string|max:25',
            'php_version'      => 'string|max:25',
            'wp_version'       => 'string|max:25',
            'pinstall_version' => '?string|max:25',
            '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',
        ];

        /** @var array<string, string> */
        $rules = apply_filters('duplicator_usage_stats_sanitize_rules', $rules);

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

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

        $rules = [
            '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): void
    {
        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 package build count and date
     *
     * @param AbstractPackage $package Backup
     *
     * @return void
     */
    public function addPackageBuild(AbstractPackage $package): void
    {
        if ($package->getStatus() == AbstractPackage::STATUS_COMPLETE) {
            $this->buildCount++;
            $this->buildLastDate = time();

            $action = BuildComponents::getActionFromComponents($package->components);
            switch ($action) {
                case BuildComponents::COMP_ACTION_ALL:
                    $this->packagesBuildCompFullCount++;
                    break;
                case BuildComponents::COMP_ACTION_DB:
                    $this->packagesBuildCompDbOnlyCount++;
                    break;
            }

            do_action('duplicator_usage_stats_package_build', $package, $action);
        } 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): void
    {
        $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(): void
    {
        $this->lastSendTime = time();
        $this->save();
    }

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

    /**
     * Get packages build completed full count
     *
     * @return int
     */
    public function getPackagesBuildCompFullCount(): int
    {
        return $this->packagesBuildCompFullCount;
    }

    /**
     * Get packages build comp dbonly count
     *
     * @return int
     */
    public function getPackagesBuildCompDbOnlyCount(): int
    {
        return $this->packagesBuildCompDbOnlyCount;
    }

    /**
     * Get used recovery count
     *
     * @return int
     */
    public function getUsedRecoveryCount(): int
    {
        return $this->usedRecoveryCount;
    }

    /**
     * Get basic infos
     *
     * @return array<string, mixed>
     */
    protected function getBasicInfos(): array
    {
        return [
            'api_version'    => CommStats::API_VERSION,
            'identifier'     => UniqueId::getInstance()->getIdentifier(),
            'plugin'         => (string) apply_filters('duplicator_usage_stats_plugin_identifier', 'dup-lite'),
            '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(): array
    {
        $installInfo = UpgradePlugin::getInstallInfo();

        return [
            'pinstall_date'    => (isset($installInfo['time']) ? date('Y-m-d H:i:s', $installInfo['time']) : null),
            'pinstall_version' => ($installInfo['version'] ?? null),
            'license_type'     => (string) apply_filters('duplicator_usage_stats_license_type', 'unlicensed'),
            'license_status'   => (string) apply_filters('duplicator_usage_stats_license_status', 'unknown'),
        ];
    }

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

        $theme_data = wp_get_theme();

        return [
            'servertype'      => StatsUtil::getServerType(),
            'db_engine'       => WpDbUtils::getDbEngine(),
            'db_version'      => WpDbUtils::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 Backup infos
     *
     * @return array<string, mixed>
     */
    protected function getManualPackageInfos(): array
    {
        return [
            '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'                  => PackageUtils::getNumCompletePackages([DupPackage::getType()]),
        ];
    }

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