<?php

namespace CleantalkSP\SpbctWP\FileEditorDisabler;

class FileEditorDisabler
{
    public static $config_path = '';
    /**
     * Returns the path to wp-config.php.
     * Uses get_home_path() if available, otherwise uses ABSPATH.
     * @return string
     */
    private static function getWpConfigPath()
    {
        $home_option_path = '';
        if (function_exists('get_home_path')) {
            $home_option_path = get_home_path() . 'wp-config.php';
        }

        $config_path = '';

        $abs_path = ABSPATH . 'wp-config.php';
        $abs_path_prepared = str_replace('\\', '/', ABSPATH) . 'wp-config.php';

        if (@file_exists($home_option_path)) {
            $config_path = $home_option_path;
        } elseif (@file_exists($abs_path)) {
            $config_path = $abs_path;
        } elseif (@file_exists($abs_path_prepared)) {
            $config_path = $abs_path_prepared;
        }
        return $config_path;
    }

    /**
     * Checks if wp-config.php is readable and writable.
     * @return bool
     */
    private static function isConfigAccessible()
    {
        self::$config_path = empty(self::$config_path) ? self::getWpConfigPath() : self::$config_path;
        if (empty(self::$config_path) || !file_exists(self::$config_path)) {
            if (!defined('SPBC_WPCONFIG_ERROR')) {
                define('SPBC_WPCONFIG_ERROR', 'config_not_found');
            }
            return false;
        }

        if (!is_writable(self::$config_path)) {
            if (!defined('SPBC_WPCONFIG_ERROR')) {
                define('SPBC_WPCONFIG_ERROR', 'config_write_error');
            }
            return false;
        }

        return true;
    }

    /**
     * Syncs the DISALLOW_FILE_EDIT constant in wp-config.php based on the settings.
     * If 'misc_disable_file_editor' is set to 1 or 2, it adds DISALLOW_FILE_EDIT.
     * If 'misc_disable_file_editor' is not set or set to 0, it removes DISALLOW_FILE_EDIT.
     * @param array $settings
     */
    public static function syncDisallowFileEditBySettings(&$settings, $critical_count = null)
    {
        global $spbc;

        if (is_multisite() && !is_network_admin()) {
            $settings['misc_disable_file_editor'] = get_site_option('spbc_network_misc_disable_file_editor', 0);
        }
        if (!self::isConfigAccessible()) {
            return false;
        }
        $config = file_get_contents(self::$config_path);
        $block_exists = self::hasDisallowFileEditBlock($config);

        if ($critical_count !== null && isset($settings['misc_disable_file_editor'])) {
            $save_settings = false;

            // Turn on auto-mode if the critique is > 0 and the mode was 0
            if (
                $critical_count > 0 &&
                $settings['misc_disable_file_editor'] == 0
            ) {
                $save_settings = true;
                $settings['misc_disable_file_editor'] = 2;
            }

            // DISABLE auto mode if critical == 0 and there was mode 2
            if (
                $critical_count == 0 &&
                $settings['misc_disable_file_editor'] == 2
            ) {
                $save_settings = true;
                $settings['misc_disable_file_editor'] = 0;
            }

            if ($save_settings === true && method_exists($spbc, 'save')) {
                $spbc->settings = $settings;
                $spbc->save('settings');
            }
        }

        $enabled =
            isset($settings['misc_disable_file_editor']) &&
            ($settings['misc_disable_file_editor'] == 1 || $settings['misc_disable_file_editor'] == 2);

        if ($enabled && $block_exists) {
            return true;
        }
        if (!$enabled && !$block_exists) {
            return true;
        }

        if ($enabled) {
            return self::addDisallowFileEdit();
        } else {
            return self::removeDisallowFileEdit();
        }
    }

    /**
     * Creates a backup wp-config.php in /cleantalk_backups/wp-config-{random_hash[7]}.php
     * @param string $wp_config_path
     */
    private static function backupWpConfig($wp_config_path)
    {
        $backup_dir = defined('SPBC_PLUGIN_DIR')
            ? rtrim(SPBC_PLUGIN_DIR, '/\\') . '/cleantalk_backups'
            : rtrim(plugin_dir_path(__FILE__), '/\\') . '/cleantalk_backups';

        if (!is_dir($backup_dir)) {
            mkdir($backup_dir, 0755, true);
        }

        $hash = substr(md5(microtime() . rand()), 0, 7);
        $backup_path = $backup_dir . '/wp-config-' . $hash . '.php';
        if (copy($wp_config_path, $backup_path)) {
            self::cleanupBackups($backup_dir);
            return $backup_path;
        }
        return false;
    }

    /**
     * Cleans up old backups in /cleantalk_backups, keeping only the latest $max_files backups.
     * @param string $backup_dir
     * @param int $max_files
     */
    private static function cleanupBackups($backup_dir, $max_files = 10)
    {
        if (!is_dir($backup_dir)) {
            return;
        }
        $files = glob($backup_dir . '/wp-config-*.php');
        if (!$files || count($files) <= $max_files) {
            return;
        }

        usort($files, function ($a, $b) {
            return filemtime($b) - filemtime($a);
        });

        $to_delete = array_slice($files, $max_files);
        foreach ($to_delete as $file) {
            @unlink($file);
        }
    }

    /**
     * Adds DISALLOW_FILE_EDIT to wp-config.php between the comments, CleanTalk Security Features.
     * @param string $wp_config_path
     * @return bool
     */
    private static function addDisallowFileEdit()
    {
        if (!self::isConfigAccessible()) {
            return false;
        }

        $config = file_get_contents(self::$config_path);
        if (self::hasDisallowFileEditConstant($config)) {
            return true;
        }

        $search_part = "/* That's all, stop editing!";
        $insert = <<<EOD
/* CleanTalk Security Features */
define( 'DISALLOW_FILE_EDIT', true );
/* CleanTalk Security Features */
EOD;

        $lines = explode("\n", $config);
        $new_lines = [];
        $inserted = false;

        foreach ($lines as $line) {
            $new_lines[] = $line;
            if (!$inserted && strpos($line, $search_part) !== false) {
                $new_lines[] = $insert;
                $inserted = true;
            }
        }

        if ($inserted) {
            self::backupWpConfig(self::$config_path);
            file_put_contents(self::$config_path, implode("\n", $new_lines));
            return true;
        }

        return false;
    }

    /**
     * Removes DISALLOW_FILE_EDIT from wp-config.php between the comments, CleanTalk Security Features.
     * @return bool
     */
    private static function removeDisallowFileEdit()
    {
        if (!self::isConfigAccessible()) {
            return false;
        }

        $config = file_get_contents(self::$config_path);

        $pattern = '/\n?\/\* CleanTalk Security Features \*\/\ndefine\( \'DISALLOW_FILE_EDIT\', true \);\n\/\* CleanTalk Security Features \*\/\n?/';
        $new_config = preg_replace($pattern, "\n", $config, 1);
        $new_config = preg_replace("/\n{3,}/", "\n\n", $new_config);

        if ($new_config !== $config) {
            self::backupWpConfig(self::$config_path);
            file_put_contents(self::$config_path, $new_config);
            return true;
        }

        return false;
    }

    /**
     * Checks if DISALLOW_FILE_EDIT is defined anywhere in wp-config.php
     * @param string $config
     * @return bool
     */
    private static function hasDisallowFileEditConstant($config)
    {
        return strpos($config, "DISALLOW_FILE_EDIT") !== false;
    }

    /**
     * Checks if the CleanTalk DISALLOW_FILE_EDIT block exists in wp-config.php
     * @param string $config
     * @return bool
     */
    private static function hasDisallowFileEditBlock($config)
    {
        return strpos($config, "/* CleanTalk Security Features */") !== false
               && strpos($config, "DISALLOW_FILE_EDIT") !== false;
    }

    /**
     * @param string $error_type
     * @return string
     */
    public static function getErrorMessage($error_type)
    {
        global $spbc;

        $error_messages = [
            'config_not_found' => sprintf(
                __(
                    "config file not found [%s] - check your WordPress installation.",
                    'security-malware-firewall'
                ),
                esc_html(self::$config_path)
            ),
            'config_write_error' => sprintf(
                __(
                    "can't write wp-config.php [%s] - set correct permissions or set/remove definition manually: \"define('DISALLOW_FILE_EDIT', true)\"",
                    'security-malware-firewall'
                ),
                esc_html(self::$config_path)
            ),
            'config_common_error' => sprintf(
                __(
                    "can't find or rewrite wp-config.php [%s]",
                    'security-malware-firewall'
                ),
                esc_html(self::$config_path)
            ),
            'unknown' => __(
                "unknown error",
                'security-malware-firewall'
            ),
        ];

        $message = isset($error_messages[$error_type])
            ? $error_messages[$error_type]
            : $error_messages['unknown'];

        return sprintf(
            '%s, "%s" feature: %s',
            $spbc->data["wl_brandname"],
            'Disable File Editor',
            $message
        );
    }
}
