<?php

declare(strict_types=1);

namespace SiteFort\Modules\Vulnerability;

// If this file is called directly, abort.
if ( ! defined( 'ABSPATH' ) ) {
	 exit;
}

use SiteFort\Includes\Interfaces\Module;
use SiteFort\Includes\Capabilities;
use SiteFort\Modules\AuditLog\AuditLog;

/**
 * The Vulnerability Scanner module class.
 *
 * Handles checking installed themes/plugins against vulnerability databases.
 */
class Vulnerability implements Module {
	private $plugin_name;
	private $version;

	public function __construct( string $plugin_name, string $version ) {
		$this->plugin_name = $plugin_name;
		$this->version = $version;
	}

	public function initialize( \SiteFort\Includes\Module_Loader $loader ) {
        $loader->add_action( 'wp_ajax_sitefort_vulnerability_scan', $this, 'handle_ajax_scan' );
        $loader->add_action( 'wp_ajax_sitefort_get_vulnerability_results', $this, 'handle_ajax_get_results' );
        $loader->add_action( 'wp_ajax_sitefort_vulnerability_asset_update', $this, 'handle_ajax_asset_update' );
        $loader->add_action( 'wp_ajax_sitefort_vulnerability_asset_delete', $this, 'handle_ajax_asset_delete' );

        $loader->add_action( 'rest_api_init', $this, 'register_rest_routes' );
	}

    /**
     * Register REST API routes for the Vulnerability module.
     */
    public function register_rest_routes() {
        register_rest_route( 'sitefort/v1', '/vulnerability/abandoned', [
            [
                'methods'             => \WP_REST_Server::READABLE,
                'callback'            => [ $this, 'handle_rest_abandoned' ],
                'permission_callback' => function () {
                    return Capabilities::can_manage_plugin();
                },
                'args'                => [
                    'threshold_days' => [
                        'required'          => false,
                        'type'              => 'integer',
                        'default'           => 365,
                        'minimum'           => 1,
                        'sanitize_callback' => 'absint',
                    ],
                    'include_themes' => [
                        'required'          => false,
                        'type'              => 'boolean',
                        'default'           => true,
                        'sanitize_callback' => 'rest_sanitize_boolean',
                    ],
                ],
            ],
        ] );
    }

    /**
     * REST Handler: Get abandoned plugins and themes.
     *
     * @param \WP_REST_Request $request The REST request object.
     * @return \WP_REST_Response
     */
    public function handle_rest_abandoned( \WP_REST_Request $request ): \WP_REST_Response {
        $threshold_days = (int) $request->get_param( 'threshold_days' );
        $include_themes = (bool) $request->get_param( 'include_themes' );

        if ( ! class_exists( __NAMESPACE__ . '\\AbandonedPluginChecker' ) ) {
            require_once __DIR__ . '/AbandonedPluginChecker.php';
        }

        $checker = new AbandonedPluginChecker( $threshold_days );

        $result = [
            'threshold_days'    => $threshold_days,
            'abandoned_plugins' => $checker->check_all(),
        ];

        if ( $include_themes ) {
            $result['abandoned_themes'] = $checker->check_themes();
        }

        return new \WP_REST_Response( $result );
    }

    /**
     * AJAX Handler: Update a specific asset (plugin or theme).
     */
    public function handle_ajax_asset_update() {
        check_ajax_referer( 'sitefort_nonce', '_ajax_nonce' );

        if ( ! Capabilities::can_manage_plugin() ) {
            wp_send_json_error( [ 'message' => 'Unauthorized' ] );
            return;
        }

        $slug = sanitize_key( (string) ( $_POST['slug'] ?? '' ) );
        $type = sanitize_key( (string) ( $_POST['type'] ?? '' ) );

        if ( empty( $slug ) || empty( $type ) ) {
            wp_send_json_error( [ 'message' => 'Missing slug or type' ] );
            return;
        }

        if ( ! in_array( $type, [ 'plugin', 'theme' ], true ) ) {
            wp_send_json_error( [ 'message' => 'Unsupported asset type.' ] );
            return;
        }

        if ( $type === 'plugin' && ! current_user_can( 'update_plugins' ) ) {
            wp_send_json_error( [ 'message' => 'You do not have permission to update plugins.' ] );
            return;
        }
        if ( $type === 'theme' && ! current_user_can( 'update_themes' ) ) {
            wp_send_json_error( [ 'message' => 'You do not have permission to update themes.' ] );
            return;
        }

        $update_result = $this->perform_asset_update( $slug, $type );

        if ( is_wp_error( $update_result ) ) {
            if ( $this->is_missing_asset_error( $update_result ) ) {
                $remediation = $this->clear_asset_vulnerability_findings( $slug, $type );
                wp_send_json_success( [
                    'message'     => sprintf( '%s not found. Finding removed.', $type === 'theme' ? 'Theme' : 'Plugin' ),
                    'issues'      => $this->get_frontend_issues(),
                    'last_scan'   => $this->get_cached_last_scan(),
                    'remediation' => $remediation,
                ] );
                return;
            }
            wp_send_json_error( [ 'message' => $update_result->get_error_message() ] );
            return;
        }

        $new_version = $this->get_installed_asset_version( $slug, $type );
        $remediation = $this->reconcile_updated_asset_findings( $slug, $type, $new_version );

        $this->record_remediation_event(
            'Vulnerability Asset Updated',
            [
                'asset_type'        => $type,
                'asset_slug'        => $slug,
                'new_version'       => $new_version,
                'resolved_findings' => (int) ( $remediation['resolved'] ?? 0 ),
            ],
            'info',
            'updates'
        );
        
        $flattened = $this->get_frontend_issues();

        wp_send_json_success( [
            'message'     => $this->build_asset_update_message( $slug, $remediation ),
            'new_version' => $new_version,
            'issues'      => $flattened,
            'last_scan'   => $this->get_cached_last_scan(),
            'remediation' => $remediation,
        ] );
    }

    /**
     * AJAX Handler: Delete an inactive vulnerable theme.
     */
    public function handle_ajax_asset_delete() {
        check_ajax_referer( 'sitefort_nonce', '_ajax_nonce' );

        if ( ! Capabilities::can_manage_plugin() ) {
            wp_send_json_error( [ 'message' => 'Unauthorized' ] );
            return;
        }

        $slug = sanitize_key( (string) ( $_POST['slug'] ?? '' ) );
        $type = sanitize_key( (string) ( $_POST['type'] ?? '' ) );

        if ( empty( $slug ) || $type !== 'theme' ) {
            wp_send_json_error( [ 'message' => 'Theme slug is required.' ] );
            return;
        }

        if ( ! current_user_can( 'delete_themes' ) ) {
            wp_send_json_error( [ 'message' => 'You do not have permission to delete themes.' ] );
            return;
        }

        $delete_result = $this->perform_asset_delete( $slug, $type );
        if ( is_wp_error( $delete_result ) ) {
            if ( $this->is_missing_asset_error( $delete_result ) ) {
                $remediation = $this->clear_asset_vulnerability_findings( $slug, $type );
                wp_send_json_success( [
                    'message'     => 'Theme not found. Finding removed.',
                    'issues'      => $this->get_frontend_issues(),
                    'last_scan'   => $this->get_cached_last_scan(),
                    'remediation' => $remediation,
                ] );
                return;
            }
            wp_send_json_error( [ 'message' => $delete_result->get_error_message() ] );
            return;
        }

        $remediation = $this->clear_asset_vulnerability_findings( $slug, $type );

        $this->record_remediation_event(
            'Vulnerability Theme Deleted',
            [
                'asset_type'        => $type,
                'asset_slug'        => $slug,
                'resolved_findings' => (int) ( $remediation['resolved'] ?? 0 ),
            ],
            'warning',
            'themes'
        );

        wp_send_json_success( [
            'message'     => sprintf( 'Successfully deleted theme %s.', $slug ),
            'issues'      => $this->get_frontend_issues(),
            'last_scan'   => $this->get_cached_last_scan(),
            'remediation' => $remediation,
        ] );
    }

    private function find_plugin_file( $slug ) {
        if ( ! function_exists( 'get_plugins' ) ) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }

        // Strict slug policy: plugin directory slug only.
        if ( ! is_string( $slug ) || ! preg_match( '/^[a-z0-9][a-z0-9_-]*$/', $slug ) ) {
            return false;
        }

        $all_plugins = get_plugins();
        $plugins_root = realpath( WP_PLUGIN_DIR );
        if ( $plugins_root === false ) {
            return false;
        }

        foreach ( $all_plugins as $file => $data ) {
            $is_match = dirname( $file ) === $slug || basename( $file, '.php' ) === $slug;
            if ( $is_match ) {
                // Verify the resolved path is within the plugins directory
                $full_path = realpath( WP_PLUGIN_DIR . '/' . $file );
                if ( $full_path && strpos( $full_path, $plugins_root ) === 0 ) {
                    return $file;
                }
            }
        }
        return false;
    }

    /**
     * AJAX Handler: Trigger a vulnerability scan.
     */
    public function handle_ajax_scan() {
        check_ajax_referer( 'sitefort_nonce', '_ajax_nonce' );

        if ( ! Capabilities::can_manage_plugin() ) {
            wp_send_json_error( [ 'message' => 'Unauthorized' ] );
            return;
        }

        // Pre-check: site must be connected before the scan can contact Console.
        $settings = \SiteFort\Modules\Cloud\Service\CloudSettings::get_all();
        if ( empty( $settings['site_token'] ) || empty( $settings['site_secret'] ) ) {
            wp_send_json_error( [
                'code'    => 'not_connected',
                'message' => 'Site is not connected to SiteFort Console.',
            ] );
            return;
        }

        $refresh = $this->refresh_scan_data();
        if ( is_wp_error( $refresh ) ) {
            wp_send_json_error( [
                'code'    => $refresh->get_error_code(),
                'message' => $refresh->get_error_message(),
            ] );
            return;
        }
        
        $flattened = $this->get_frontend_issues();

        wp_send_json_success( [
            'last_scan' => $this->get_cached_last_scan(),
            'issues'    => $flattened
        ] );
    }

    /**
     * AJAX Handler: Get cached vulnerability results.
     * Returns the last scan results without triggering a new scan.
     */
    public function handle_ajax_get_results() {
        check_ajax_referer( 'sitefort_nonce', '_ajax_nonce' );

        if ( ! Capabilities::can_manage_plugin() ) {
            wp_send_json_error( [ 'message' => 'Unauthorized' ] );
            return;
        }

        $flattened = $this->get_frontend_issues();

        wp_send_json_success( [
            'last_scan' => $this->get_cached_last_scan(),
            'issues'    => $flattened
        ] );
    }

    private function get_frontend_issues(): array {
        if ( ! class_exists( 'SiteFort\\Modules\\Scanner\\Repository\\IssueRepository' ) ) {
            $path = dirname( __DIR__ ) . '/Scanner/Repository/IssueRepository.php';
            if ( file_exists( $path ) ) {
                require_once $path;
            }
        }

        if ( ! class_exists( 'SiteFort\\Modules\\Scanner\\Repository\\IssueRepository' ) ) {
            return [];
        }

        if ( ! function_exists( 'get_plugins' ) ) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }

        $installed_plugins = get_plugins();
        $installed_themes  = wp_get_themes();

        $plugin_lookup = [];
        foreach ( $installed_plugins as $plugin_file => $plugin_data ) {
            $slug = dirname( $plugin_file );
            if ( $slug === '.' ) {
                $slug = basename( $plugin_file, '.php' );
            }
            if ( ! is_string( $slug ) || $slug === '' ) {
                continue;
            }

            if ( ! isset( $plugin_lookup[ $slug ] ) ) {
                $plugin_lookup[ $slug ] = [
                    'name'    => (string) ( $plugin_data['Name'] ?? $slug ),
                    'version' => (string) ( $plugin_data['Version'] ?? 'Unknown' ),
                ];
            }
        }

        $rows = \SiteFort\Modules\Scanner\Repository\IssueRepository::getCurrentIssuesByType( 'vulnerability', 5000, 0 );

        if ( ! is_array( $rows ) ) {
            return [];
        }

        $items = [];

        foreach ( $rows as $row ) {
            if ( ! is_array( $row ) ) {
                continue;
            }
            if ( (string) ( $row['type'] ?? '' ) !== 'vulnerability' ) {
                continue;
            }
            if ( ! empty( $row['is_ignored'] ) ) {
                continue;
            }
            if ( (int) ( $row['resolved_at'] ?? 0 ) > 0 ) {
                continue;
            }

            $data = [];
            if ( isset( $row['data'] ) && is_array( $row['data'] ) ) {
                $data = $row['data'];
            }

            $asset = sanitize_key( (string) ( $data['component_type'] ?? 'plugin' ) );
            if ( ! in_array( $asset, [ 'plugin', 'theme', 'core' ], true ) ) {
                $asset = 'plugin';
            }

            $asset_slug = sanitize_key( (string) ( $row['slug'] ?? $data['slug'] ?? '' ) );
            if ( $asset === 'core' ) {
                $asset_slug = 'wordpress';
            }
            if ( $asset_slug === '' ) {
                continue;
            }

            $version = (string) ( $row['version'] ?? $data['version'] ?? 'Unknown' );
            $name = $asset_slug;

            if ( $asset === 'plugin' ) {
                if ( ! isset( $plugin_lookup[ $asset_slug ] ) ) {
                    continue;
                }
                $name = $plugin_lookup[ $asset_slug ]['name'];
                if ( $version === 'Unknown' ) {
                    $version = $plugin_lookup[ $asset_slug ]['version'];
                }
            } elseif ( $asset === 'theme' ) {
                if ( ! isset( $installed_themes[ $asset_slug ] ) ) {
                    continue;
                }
                $name = (string) $installed_themes[ $asset_slug ]->get( 'Name' );
                if ( $version === 'Unknown' ) {
                    $version = (string) $installed_themes[ $asset_slug ]->get( 'Version' );
                }
            } elseif ( $asset === 'core' ) {
                $name = 'WordPress Core';
            }

            $severity_raw = strtolower( (string) ( $row['severity'] ?? 'medium' ) );
            if ( ! in_array( $severity_raw, [ 'critical', 'high', 'medium', 'low' ], true ) ) {
                $severity_raw = 'medium';
            }

            $can_update = ! empty( $data['can_update'] ) && $asset !== 'core';
            $is_active = null;
            if ( $asset === 'theme' ) {
                $is_active = $this->is_theme_in_use( $asset_slug );
            }
            $remediation_preferred = (string) ( $data['remediation_preferred'] ?? ( $asset === 'theme' && $is_active === false ? 'delete' : 'update' ) );
            $fixed_in = (string) ( $row['fixed_in'] ?? $data['fixed_in'] ?? '' );

            $status_reason = '';
            $status_color = '';
            if ( $fixed_in === '' ) {
                $status_reason = 'Unpatched';
                $status_color = 'red';
            } elseif ( ! $can_update ) {
                $status_reason = 'Update unavailable';
                $status_color = 'orange';
            }

            $items[] = [
                'id'            => (string) ( $row['id'] ?? $row['hash'] ?? uniqid( 'vuln_', true ) ),
                'asset'         => $asset,
                'asset_slug'    => $asset_slug,
                'name'          => $name,
                'version'       => $version,
                'fixed_in'      => $fixed_in !== '' ? $fixed_in : 'Not Patched',
                'vnv'           => (string) ( $row['vnv'] ?? $data['vnv'] ?? '' ),
                'severity'      => ucfirst( $severity_raw ),
                'cvss'          => (float) ( $row['cvss'] ?? $data['cvss'] ?? 0 ),
                'cve'           => (string) ( $row['cve'] ?? $data['cve'] ?? '' ),
                'title'         => (string) ( $row['title'] ?? $row['message'] ?? 'Unknown Vulnerability' ),
                'description'   => (string) ( $row['description'] ?? $data['description'] ?? 'No description available.' ),
                'action'        => ( $asset === 'theme' && $is_active === false ) ? 'delete' : ( $can_update ? 'update' : 'manual' ),
                'can_update'    => $can_update,
                'is_active'     => $is_active,
                'remediation_preferred' => $remediation_preferred,
                'status_reason' => $status_reason,
                'status_color'  => $status_color,
            ];
        }

        return $items;
    }

    /**
     * Determine whether a remediation result means the asset is already gone.
     *
     * @param mixed $result
     * @return bool
     */
    private function is_missing_asset_error( $result ): bool {
        return is_wp_error( $result ) && $result->get_error_code() === 'not_found';
    }

    private function reconcile_updated_asset_findings( string $slug, string $type, string $new_version ): array {
        $service = $this->get_remediation_service();
        if ( ! $service ) {
            return $this->empty_remediation_summary( $new_version, 1 );
        }

        return $service->reconcile_updated_asset( $slug, $type, $new_version );
    }

    private function clear_asset_vulnerability_findings( string $slug, string $type ): array {
        $service = $this->get_remediation_service();
        if ( ! $service ) {
            return $this->empty_remediation_summary( '', 0 );
        }

        return $service->clear_asset_findings( $slug, $type );
    }

    private function get_remediation_service() {
        $class = '\SiteFort\Modules\Vulnerability\Services\VulnerabilityRemediationService';
        if ( ! class_exists( $class ) ) {
            $path = __DIR__ . '/Services/VulnerabilityRemediationService.php';
            if ( file_exists( $path ) ) {
                require_once $path;
            }
        }

        return class_exists( $class ) ? new $class() : null;
    }

    private function empty_remediation_summary( string $new_version, int $unverified ): array {
        return [
            'checked'        => 0,
            'resolved'       => 0,
            'still_affected' => 0,
            'unverified'     => $unverified,
            'new_version'    => $new_version,
            'active_count'   => null,
            'resolved_hashes'       => [],
            'updated_hashes'        => [],
            'still_affected_hashes' => [],
            'unverified_hashes'     => [],
        ];
    }

    private function build_asset_update_message( string $slug, array $remediation ): string {
        $resolved = (int) ( $remediation['resolved'] ?? 0 );
        if ( $resolved > 0 ) {
            return sprintf(
                'Successfully updated %s. Resolved %d vulnerability %s.',
                $slug,
                $resolved,
                $resolved === 1 ? 'finding' : 'findings'
            );
        }

        return sprintf( 'Successfully updated %s.', $slug );
    }

    /**
     * Perform the asset update using the WordPress Upgrader API.
     *
     * @param string $slug
     * @param string $type 'plugin' or 'theme'
     * @return bool|\WP_Error True on success, WP_Error on failure.
     */
    private function perform_asset_update( $slug, $type ) {
        if ( ! function_exists( 'get_plugins' ) ) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }
        if ( ! class_exists( '\\Plugin_Upgrader' ) ) {
            require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
        }
        if ( ! function_exists( 'wp_update_plugins' ) || ! function_exists( 'wp_update_themes' ) ) {
            require_once ABSPATH . 'wp-includes/update.php';
        }

        if ( $type === 'plugin' ) {
            $plugin_file = $this->find_plugin_file( $slug );
            if ( ! $plugin_file ) {
                return new \WP_Error( 'not_found', "Plugin not found: {$slug}" );
            }

            ob_start();
            $skin     = new \Automatic_Upgrader_Skin();
            $upgrader = new \Plugin_Upgrader( $skin );
            $result   = $upgrader->upgrade( $plugin_file );
            ob_end_clean();

            if ( is_wp_error( $result ) ) {
                return $result;
            }
            if ( $result !== true ) {
                return new \WP_Error( 'update_failed', "Plugin update failed for: {$slug}" );
            }
            wp_clean_plugins_cache( true );
            wp_update_plugins();
            return true;
        }

        if ( $type === 'theme' ) {
            $themes = wp_get_themes();
            if ( ! isset( $themes[ $slug ] ) ) {
                return new \WP_Error( 'not_found', "Theme not found: {$slug}" );
            }

            ob_start();
            $skin     = new \Automatic_Upgrader_Skin();
            $upgrader = new \Theme_Upgrader( $skin );
            $result   = $upgrader->upgrade( $slug );
            ob_end_clean();

            if ( is_wp_error( $result ) ) {
                return $result;
            }
            if ( $result !== true ) {
                return new \WP_Error( 'update_failed', "Theme update failed for: {$slug}" );
            }
            wp_clean_themes_cache( true );
            wp_update_themes();
            return true;
        }

        return new \WP_Error( 'invalid_type', "Invalid asset type: {$type}" );
    }

    /**
     * Delete a vulnerable asset. Only inactive themes are allowed.
     *
     * @param string $slug
     * @param string $type
     * @return bool|\WP_Error
     */
    private function perform_asset_delete( $slug, $type ) {
        if ( $type !== 'theme' ) {
            return new \WP_Error( 'invalid_type', 'Only theme deletion is supported for vulnerability remediation.' );
        }

        $themes = wp_get_themes();
        if ( ! isset( $themes[ $slug ] ) ) {
            return new \WP_Error( 'not_found', "Theme not found: {$slug}" );
        }

        if ( $this->is_theme_in_use( $slug ) ) {
            return new \WP_Error( 'theme_in_use', 'Active or in-use themes cannot be deleted.' );
        }

        if ( ! function_exists( 'delete_theme' ) ) {
            require_once ABSPATH . 'wp-admin/includes/theme.php';
        }
        if ( ! function_exists( 'wp_update_themes' ) ) {
            require_once ABSPATH . 'wp-includes/update.php';
        }

        $deleted = delete_theme( $slug );
        if ( is_wp_error( $deleted ) ) {
            return $deleted;
        }
        if ( $deleted !== true ) {
            return new \WP_Error( 'delete_failed', "Theme deletion failed for: {$slug}" );
        }

        wp_clean_themes_cache( true );
        wp_update_themes();
        return true;
    }

    private function refresh_scan_data() {
        if ( ! class_exists( '\SiteFort\Modules\Vulnerability\Services\VulnerabilityRefreshService' ) ) {
            $path = __DIR__ . '/Services/VulnerabilityRefreshService.php';
            if ( file_exists( $path ) ) {
                require_once $path;
            }
        }

        if ( ! class_exists( '\SiteFort\Modules\Vulnerability\Services\VulnerabilityRefreshService' ) ) {
            return new \WP_Error( 'scanner_unavailable', 'Vulnerability scanner components are unavailable.' );
        }

        $result = ( new \SiteFort\Modules\Vulnerability\Services\VulnerabilityRefreshService() )->refresh();
        if ( is_wp_error( $result ) ) {
            return $result;
        }

        return true;
    }

    /**
     * Returns the canonical last vulnerability scan timestamp from state.
     */
    private function get_cached_last_scan(): ?int {
        $cached = \SiteFort\Includes\SettingsStore::get_field(
            \SiteFort\Includes\SettingsStore::COL_STATE,
            'vulnerability_results',
            []
        );
        if ( ! is_array( $cached ) || empty( $cached['timestamp'] ) ) {
            return null;
        }

        $timestamp = (int) $cached['timestamp'];
        return $timestamp > 0 ? $timestamp : null;
    }

    /**
     * Resolve current installed version for an updated asset.
     */
    private function get_installed_asset_version( string $slug, string $type ): string {
        if ( $type === 'plugin' ) {
            $plugin_file = $this->find_plugin_file( $slug );
            if ( ! $plugin_file ) {
                return 'Unknown';
            }
            if ( ! function_exists( 'get_plugins' ) ) {
                require_once ABSPATH . 'wp-admin/includes/plugin.php';
            }
            $plugins = get_plugins();
            if ( isset( $plugins[ $plugin_file ]['Version'] ) ) {
                return (string) $plugins[ $plugin_file ]['Version'];
            }
            return 'Unknown';
        }

        if ( $type === 'theme' ) {
            $themes = wp_get_themes();
            if ( isset( $themes[ $slug ] ) ) {
                return (string) $themes[ $slug ]->get( 'Version' );
            }
            return 'Unknown';
        }

        return 'Unknown';
    }

    /**
     * Returns true when the theme is currently used by the site.
     * This includes the active stylesheet and the active parent template.
     */
    private function is_theme_in_use( string $slug ): bool {
        $active_theme = wp_get_theme();
        if ( ! ( $active_theme instanceof \WP_Theme ) ) {
            return false;
        }

        $active_stylesheet = (string) $active_theme->get_stylesheet();
        $active_template = (string) $active_theme->get_template();
        return $slug === $active_stylesheet || $slug === $active_template;
    }

    /**
     * Record vulnerability remediation actions in audit log when available.
     */
    private function record_remediation_event( string $event, array $details, string $severity, string $category ): void {
        if ( class_exists( AuditLog::class ) ) {
            AuditLog::add_log( $event, $details, $severity, $category );
        }
    }
}
