<?php

declare(strict_types=1);

namespace Duplicator\Addons\ScheduleAddon;

use Duplicator\Addons\ScheduleAddon\Core\LicenseSchedule;
use Duplicator\Addons\ScheduleAddon\Models\ScheduleEntity;
use Duplicator\Addons\ScheduleAddon\Models\SchedulePackageRelation;
use Duplicator\Package\AbstractPackage;
use Duplicator\Package\Create\BuildComponents;
use VendorDuplicator\Amk\JsonSerialize\JsonSerialize;

/**
 * Tracks schedule-specific usage statistics.
 *
 * Persisted in its own WP option, following the same pattern as core PluginData.
 * Hooks into build events to track scheduled builds, and into
 * duplicator_usage_stats_plugin_data to output the data.
 */
class ScheduleUsageStats
{
    const OPTION_KEY = 'dupli_opt_schedule_usage_stats';

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

    /** @var int */
    private $buildCount = 0;
    /** @var int */
    private $buildLastDate = 0;
    /** @var int */
    private $buildFailedCount = 0;
    /** @var int */
    private $buildFailedLastDate = 0;
    /** @var int */
    private $buildCompFullCount = 0;
    /** @var int */
    private $buildCompDbOnlyCount = 0;
    /** @var int */
    private $buildCompMdOnlyCount = 0;
    /** @var int */
    private $buildCompCustomCount = 0;
    /** @var int */
    private $buildCompCustomOnlyActiveCount = 0;

    /**
     * Class constructor
     */
    private function __construct()
    {
        if (($data = get_option(self::OPTION_KEY)) !== false) {
            JsonSerialize::unserializeToObj($data, $this);
        }
    }

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

        return self::$instance;
    }

    /**
     * Register hooks
     *
     * @return void
     */
    public static function register(): void
    {
        add_action('duplicator_package_transfer_completed', [self::class, 'onPackageBuild']);
        add_action('duplicator_build_fail', static function (AbstractPackage $package): void {
            self::onPackageBuild($package);
        });
        add_filter('duplicator_usage_stats_plugin_data', [self::class, 'addPluginData'], 10, 2);
        add_filter('duplicator_usage_stats_sanitize_rules', [self::class, 'addSanitizeRules']);
    }

    /**
     * Track build stats for scheduled packages
     *
     * Hooked to duplicator_package_transfer_completed and duplicator_build_fail.
     * Only tracks packages linked to a schedule.
     *
     * @param AbstractPackage $package The package
     *
     * @return void
     */
    public static function onPackageBuild(AbstractPackage $package): void
    {
        $execType = SchedulePackageRelation::getExecutionType($package->getId());
        if ($execType === SchedulePackageRelation::EXEC_TYPE_MANUAL) {
            return;
        }

        $instance = self::getInstance();

        if ($package->getStatus() == AbstractPackage::STATUS_COMPLETE) {
            $instance->buildCount++;
            $instance->buildLastDate = time();

            switch (BuildComponents::getActionFromComponents($package->components)) {
                case BuildComponents::COMP_ACTION_ALL:
                    $instance->buildCompFullCount++;
                    break;
                case BuildComponents::COMP_ACTION_DB:
                    $instance->buildCompDbOnlyCount++;
                    break;
                case 'media':
                    $instance->buildCompMdOnlyCount++;
                    break;
                case 'custom':
                    if (
                        array_intersect(
                            $package->components,
                            [
                                BuildComponents::COMP_PLUGINS_ACTIVE,
                                BuildComponents::COMP_THEMES_ACTIVE,
                            ]
                        )
                    ) {
                        $instance->buildCompCustomOnlyActiveCount++;
                    } else {
                        $instance->buildCompCustomCount++;
                    }
                    break;
            }
        } else {
            $instance->buildFailedCount++;
            $instance->buildFailedLastDate = time();
        }

        $instance->save();
    }

    /**
     * Add schedule build stats to usage stats payload
     *
     * @param array<string, mixed> $data       Current data
     * @param object               $pluginData PluginData instance
     *
     * @return array<string, mixed>
     */
    public static function addPluginData(array $data, object $pluginData): array
    {
        $instance = self::getInstance();

        $data['schedules_build_count']                         = $instance->buildCount;
        $data['schedules_build_last_date']                     = $instance->buildLastDate == 0
            ? null
            : date('Y-m-d H:i:s', $instance->buildLastDate);
        $data['schedules_build_failed_count']                  = $instance->buildFailedCount;
        $data['schedules_build_failed_last_date']              = $instance->buildFailedLastDate == 0
            ? null
            : date('Y-m-d H:i:s', $instance->buildFailedLastDate);
        $data['schedules_build_comp_full_count']               = $instance->buildCompFullCount;
        $data['schedules_build_comp_dbonly_count']             = $instance->buildCompDbOnlyCount;
        $data['schedules_build_comp_mdonly_count']             = $instance->buildCompMdOnlyCount;
        $data['schedules_build_comp_custom_count']             = $instance->buildCompCustomCount;
        $data['schedules_build_comp_custom_only_active_count'] = $instance->buildCompCustomOnlyActiveCount;

        return array_merge($data, self::getScheduleConfigInfos());
    }

    /**
     * Return per-schedule configuration stats (frequency and enabled/disabled breakdown)
     *
     * @return array<string, mixed>
     */
    private static function getScheduleConfigInfos(): array
    {
        $result = [
            'can_use_adv_schedules'    => LicenseSchedule::canHourly(),
            'schedules_hourly_count'   => 0,
            'schedules_daily_count'    => 0,
            'schedules_weekly_count'   => 0,
            'schedules_monthly_count'  => 0,
            'schedules_disabled_count' => 0,
            'schedules_enabled_count'  => 0,
        ];

        if (($schedules = ScheduleEntity::getAll()) === false) {
            $schedules = [];
        }

        foreach ($schedules as $schedule) {
            if (!$schedule->isActive()) {
                $result['schedules_disabled_count']++;
                continue;
            }

            $result['schedules_enabled_count']++;
            switch ($schedule->repeat_type) {
                case ScheduleEntity::REPEAT_HOURLY:
                    $result['schedules_hourly_count']++;
                    break;
                case ScheduleEntity::REPEAT_DAILY:
                    $result['schedules_daily_count']++;
                    break;
                case ScheduleEntity::REPEAT_MONTHLY:
                    $result['schedules_monthly_count']++;
                    break;
                case ScheduleEntity::REPEAT_WEEKLY:
                    $result['schedules_weekly_count']++;
                    break;
            }
        }

        return $result;
    }

    /**
     * Add sanitize rules for schedule stats fields
     *
     * @param array<string, string> $rules Current rules
     *
     * @return array<string, string>
     */
    public static function addSanitizeRules(array $rules): array
    {
        $rules['schedules_build_last_date']        = '?string|max:25';
        $rules['schedules_build_failed_last_date'] = '?string|max:25';

        return $rules;
    }

    /**
     * Save to database
     *
     * @return bool
     */
    private function save(): bool
    {
        return update_option(self::OPTION_KEY, JsonSerialize::serialize($this, JSON_PRETTY_PRINT));
    }
}
