<?php
namespace WPD_Platform\Services;

use WPD_Platform\Singleton;

if (!defined('ABSPATH')) {
    exit(); // Exit if accessed directly.
}

/**
 * LogService class
 *
 * Manages the logging process and related operations
 *
 * @package WPD_Platform\Services
 */
class LogService extends Singleton
{
    /**
     * Whitelisted options to monitor for changes.
     */
    private $whitelistOptions = array(
        'blogname',
        'blogdescription',
        'siteurl',
        'home',
        'admin_email',
    );

    public function __construct()
    {

        $this->setupHooks();
    }

    /**
     * Log update action.
     *
     * @param string $objectName Plugin/Theme/User name.
     * @param string $action Action performed (installed, updated, disabled, activated, deleted, registered).
     * @param string|null $version Optional version number of the plugin/theme.
     * @param string $objectType The type of object ('Options', 'Plugins', 'Themes', 'Users').
     * @param mixed|null $oldValue Optional old value before the update.
     * @param mixed|null $newValue Optional new value after the update.
     */
    public function logUpdateAction($objectName, $action, $version = null, $objectType = 'Plugins', $oldValue = null, $newValue = null)
    {
        // Retrieve current log data
        $updateLog = get_option('dol_update_log', array());

        // Build the log entry
        $logEntry = array(
            'timestamp' => current_time('timestamp'),
            'action' => sanitize_text_field($action),
            'object_type' => sanitize_text_field($objectType),
            'object_name' => sanitize_text_field($objectName),
        );

        if ($version) {
            $logEntry['version'] = sanitize_text_field($version);
        }

        if ($oldValue !== null) {
            $logEntry['old_value'] = maybe_serialize($oldValue);
        }

        if ($newValue !== null) {
            $logEntry['new_value'] = maybe_serialize($newValue);
        }

        // Append to the existing log data
        $updateLog[] = $logEntry;

        // Trim the oldest entries if necessary
        if (count($updateLog) > 500) {
            $updateLog = array_slice($updateLog, -500);
        }

        // Update the option with the new combined log data
        update_option('dol_update_log', $updateLog, false);
    }


    /**
     * Set up hooks for logging updates, activations, deletions, and option changes.
     */
    private function setupHooks()
    {
        // Log plugin installations or updates
        add_action('upgrader_process_complete', array($this, 'hooksPluginInstallOrUpdate'), 10, 2);

        // Log theme activation
        add_action('switch_theme', array($this, 'logThemeActivation'), 10, 2);

        // Log plugin activation
        add_action('activated_plugin', array($this, 'hooksActivatedPlugin'));

        // Log plugin deactivation
        add_action('deactivated_plugin', array($this, 'hooksDeactivatedPlugin'));

        // Log plugin deletion
        add_action('deleted_plugin', array($this, 'hooksDeletePlugin'));

        // Track specific option updates
        add_action('updated_option', array($this, 'hooksUpdatedOption'), 10, 3);

        // Log administrator profile updates and registrations
        add_action('profile_update', array($this, 'hooksProfileUpdate'), 10);
        add_action('user_register', array($this, 'hooksUserRegister'), 10);
    }

    /**
     * Log plugin installations and updates.
     *
     * @param Plugin_Upgrader $upgrader Plugin upgrader instance.
     * @param array $extra Array of extra arguments related to the action.
     */
    public function hooksPluginInstallOrUpdate($upgrader, $extra)
    {
        if (!isset($extra['type']) || $extra['type'] !== 'plugin') {
            return;
        }

        if ($extra['action'] === 'install') {
            $path = $upgrader->plugin_info();
            if (!$path) {
                return;
            }

            // Get plugin data after installation
            $data = get_plugin_data($upgrader->skin->result['local_destination'] . '/' . $path, true, false);

            // Log installation with the plugin name and version
            $this->logUpdateAction($data['Name'], 'installed', $data['Version'], 'Plugins');
        }

        if ($extra['action'] === 'update') {
            $slugs = array();
            if (isset($extra['bulk']) && $extra['bulk'] === true) {
                $slugs = $extra['plugins'];
            } else {
                $pluginSlug = isset($upgrader->skin->plugin) ? $upgrader->skin->plugin : $extra['plugin'];
                if (empty($pluginSlug)) {
                    return;
                }
                $slugs = array($pluginSlug);
            }

            // Loop through each slug and log the update
            foreach ($slugs as $slug) {
                $data = get_plugin_data(WP_PLUGIN_DIR . '/' . $slug, true, false);
                $this->logUpdateAction($data['Name'], 'updated', $data['Version'], 'Plugins');
            }
        }
    }

    /**
     * Log theme activation.
     *
     * @param string $newName New theme directory name.
     * @param WP_Theme $newTheme New WP_Theme object.
     */
    public function logThemeActivation($newName, $newTheme)
    {
        // Log the theme activation action with the new theme directory name
        $this->logUpdateAction($newName, 'activated', null, 'Themes');
    }

    /**
     * Log plugin activation.
     *
     * @param string $pluginName Plugin file path.
     */
    public function hooksActivatedPlugin($pluginName)
    {
        $pluginPath = WP_PLUGIN_DIR . '/' . $pluginName;
        $data = get_plugin_data($pluginPath, true, false);

        // Log the plugin activation with its name and version
        $this->logUpdateAction($data['Name'], 'activated', $data['Version'], 'Plugins');
    }

    /**
     * Log plugin deactivation.
     *
     * @param string $pluginName Plugin file path.
     */
    public function hooksDeactivatedPlugin($pluginName)
    {
        $pluginPath = WP_PLUGIN_DIR . '/' . $pluginName;
        $data = get_plugin_data($pluginPath, true, false);

        // Log the plugin deactivation with its name and version
        $this->logUpdateAction($data['Name'], 'deactivated', $data['Version'], 'Plugins');
    }

    /**
     * Log plugin deletion.
     *
     * @param string $pluginFile Plugin file path.
     */
    public function hooksDeletePlugin($pluginFile)
    {
        // Extract the plugin slug from the file path
        $pluginSlug = dirname($pluginFile);

        // Log the plugin deletion action with the plugin slug
        $this->logUpdateAction($pluginSlug, 'deleted', null, 'Plugins');
    }

    /**
     * Log changes to whitelisted options.
     *
     * @param string $option The name of the updated option.
     * @param mixed $oldValue The old value of the option.
     * @param mixed $newValue The new value of the option.
     */
    public function hooksUpdatedOption($option, $oldValue, $newValue)
    {
        // Check if the updated option is in the whitelist
        if (!in_array($option, $this->whitelistOptions)) {
            return;
        }

        // Log the option update
        $this->logOptionChange($option, $oldValue, $newValue);
    }

    /**
     * Insert a log entry for the updated option.
     *
     * @param string $optionName The name of the option.
     * @param mixed $oldValue The old value of the option.
     * @param mixed $newValue The new value of the option.
     */
    private function logOptionChange($optionName, $oldValue, $newValue)
    {
        // Retrieve the current log data
        $updateLog = get_option('dol_update_log', array());

        // Add new option change log entry
        $updateLog[] = array(
            'timestamp' => current_time('timestamp'),
            'action' => 'updated',
            'object_type' => 'Options',
            'object_name' => sanitize_text_field($optionName),
            'old_value' => maybe_serialize($oldValue),
            'new_value' => maybe_serialize($newValue),
        );

        // Trim the oldest entries if necessary
        if (count($updateLog) > 500) {
            $updateLog = array_slice($updateLog, -500);
        }

        // Update the option with the new combined log data
        update_option('dol_update_log', $updateLog, false);
    }

    /**
     * Log when an administrator's profile is updated.
     *
     * @param int $userId The ID of the updated user.
     */
    public function hooksProfileUpdate($userId)
    {
        $user = get_user_by('id', $userId);
        if (in_array('administrator', $user->roles)) {
            $this->logUpdateAction($user->user_nicename, ' profile details were updated', null, 'Admin Activity');
        }
    }

    /**
     * Log when an administrator user is registered.
     *
     * @param int $userId The ID of the newly registered user.
     */
    public function hooksUserRegister($userId)
    {
        $user = get_user_by('id', $userId);
        if (in_array('administrator', $user->roles)) {
            $this->logUpdateAction($user->user_nicename, ' was registered as admin', null, 'Admin Activity');
        }
    }
}
