<?php

declare(strict_types=1);

namespace SiteFort\Modules\Scanner;

use SiteFort\Modules\Scanner\Service\IssueType;
use SiteFort\Modules\Scanner\Service\ScanLogger;
use SiteFort\Modules\Scanner\Repository\IssueRepository;
use SiteFort\Modules\Vulnerability\Services\AffectedVersionMatcher;

if ( ! defined( 'ABSPATH' ) ) { exit; }

/**
 * Adapts Cloud\VulnerabilitySync results for scanner issue storage.
 */
class VulnerabilityScanner  {

    private bool $should_sync;

    public function __construct( bool $should_sync = true ) {
        $this->should_sync = $should_sync;
    }

    private function ensure_affected_version_matcher_loaded(): bool {
        if ( class_exists( AffectedVersionMatcher::class ) ) {
            return true;
        }

        $path = dirname( __DIR__ ) . '/Vulnerability/Services/AffectedVersionMatcher.php';
        if ( file_exists( $path ) ) {
            require_once $path;
        }

        return class_exists( AffectedVersionMatcher::class );
    }

    /**
     * Get the scanner title.
     * 
     * @return string
     */
    public function get_title() {
        return 'Vulnerability Scanner';
    }

    /**
     * Run the vulnerability scan (sync).
     *
     * @return array List of ScanResult arrays
     */
    public function run() {
        if ( class_exists( ScanLogger::class ) ) {
            ScanLogger::log( "Checking for known vulnerabilities..." );
        }

        if ( ! $this->ensure_affected_version_matcher_loaded() ) {
            sitefort_log( 'SiteFort: Affected version matcher class missing.' );
            return [ 'issues' => [], 'count' => 0, 'complete' => false, 'failed' => true ];
        }

        if ( $this->should_sync ) {
            $sync_class = '\\SiteFort\\Modules\\Cloud\\Services\\VulnerabilitySync';
            
            if ( ! class_exists( $sync_class ) ) {
                $path = dirname( __DIR__ ) . '/Cloud/Services/VulnerabilitySync.php';
                if ( file_exists( $path ) ) {
                    require_once $path;
                }
            }

            if ( ! class_exists( $sync_class ) ) {
                if ( class_exists( ScanLogger::class ) ) {
                    ScanLogger::log( "Vulnerability scanner unavailable: sync service class missing." );
                }
                sitefort_log( 'SiteFort: Vulnerability sync service class missing.' );
                return [ 'issues' => [], 'count' => 0, 'complete' => false, 'failed' => true ];
            }

            try {
                $syncer = new $sync_class();
                $sync_ok = $syncer->run();
            } catch ( \RuntimeException $e ) {
                if ( class_exists( ScanLogger::class ) ) {
                    ScanLogger::log( "Failed to check vulnerabilities: " . $e->getMessage() );
                }
                sitefort_log( 'SiteFort: Vulnerability sync execution failed: ' . $e->getMessage() );
                return [ 'issues' => [], 'count' => 0, 'complete' => false, 'failed' => true ];
            }
        } else {
            $sync_ok = true;
        }

        $raw_issues = \SiteFort\Includes\SettingsStore::get_field( \SiteFort\Includes\SettingsStore::COL_STATE, 'vulnerability_issues', [] );
        if ( ! is_array( $raw_issues ) ) {
            $raw_issues = [];
        }

        if ( $sync_ok !== true ) {
            sitefort_log( 'SiteFort: Vulnerability sync failed.' );
            return [ 'issues' => [], 'count' => 0, 'complete' => false, 'failed' => true ];
        }

        if ( ! function_exists( 'get_plugins' ) ) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }
        $installed_plugins = get_plugins();
        $installed_themes  = wp_get_themes();
        $active_theme = wp_get_theme();
        $active_stylesheet = $active_theme instanceof \WP_Theme ? (string) $active_theme->get_stylesheet() : '';
        $active_template = $active_theme instanceof \WP_Theme ? (string) $active_theme->get_template() : '';

        $results = [];

        // Plugins
        if ( ! empty( $raw_issues['plugins'] ) && is_array( $raw_issues['plugins'] ) ) {
            $plugin_updates = $this->get_plugin_update_map();
            foreach ( $raw_issues['plugins'] as $slug => $issues ) {
                if ( ! is_string( $slug ) || ! is_array( $issues ) ) {
                    continue;
                }

                $version = 'Unknown';
                $is_installed = false;
                $asset_name = $slug;
                $available_update_version = '';

                $plugin_file = '';
                foreach( $installed_plugins as $file => $data ) {
                    $matches_slug = ( dirname( $file ) === $slug ) || ( $file === $slug ) || ( strpos( $file, $slug . '/' ) === 0 );
                    if ( $matches_slug ) {
                        $is_installed = true;
                        $plugin_file  = $file;
                        $version = $data['Version'];
                        $asset_name = (string) ( $data['Name'] ?? $slug );
                        if ( isset( $plugin_updates[ $file ] ) ) {
                            $available_update_version = $this->resolve_available_update_version( $plugin_updates[ $file ] );
                        }
                        break;
                    }
                }

                // Skip vulnerabilities for plugins that are no longer installed.
                if ( ! $is_installed ) {
                    continue;
                }

                foreach ( $issues as $issue_data ) {
                    $ranges = AffectedVersionMatcher::normalize( $issue_data['affected_versions'] ?? ( $issue_data['vnv'] ?? null ) );
                    if ( $version !== 'Unknown' && ! empty( $ranges ) ) {
                        if ( AffectedVersionMatcher::is_affected( $version, $ranges ) === false ) {
                            continue;
                        }
                    }
                    $can_update = $this->is_safe_update_candidate( $available_update_version, $ranges );
                    $results[] = $this->transform( $issue_data, 'plugin', $slug, $version, $can_update, $plugin_file, null, $asset_name, $available_update_version, $ranges );
                }
            }
        }

        // Themes
        if ( ! empty( $raw_issues['themes'] ) && is_array( $raw_issues['themes'] ) ) {
            $theme_updates = $this->get_theme_update_map();
            foreach ( $raw_issues['themes'] as $slug => $issues ) {
                if ( ! is_string( $slug ) || ! is_array( $issues ) ) {
                    continue;
                }

                $version = 'Unknown';
                $asset_name = $slug;
                $available_update_version = '';
                if ( isset( $installed_themes[$slug] ) ) {
                    $version = $installed_themes[$slug]->get( 'Version' );
                    $asset_name = (string) $installed_themes[$slug]->get( 'Name' );
                    if ( isset( $theme_updates[ $slug ] ) ) {
                        $available_update_version = $this->resolve_available_update_version( $theme_updates[ $slug ] );
                    }
                    $is_active = (
                        ( $active_stylesheet !== '' && $active_stylesheet === $slug ) ||
                        ( $active_template !== '' && $active_template === $slug )
                    );
                } else {
                    // Skip cached rows for themes that are no longer installed.
                    continue;
                }

                foreach ( $issues as $issue_data ) {
                    $ranges = AffectedVersionMatcher::normalize( $issue_data['affected_versions'] ?? ( $issue_data['vnv'] ?? null ) );
                    if ( $version !== 'Unknown' && ! empty( $ranges ) ) {
                        if ( AffectedVersionMatcher::is_affected( $version, $ranges ) === false ) {
                            continue;
                        }
                    }
                    $can_update = $this->is_safe_update_candidate( $available_update_version, $ranges );
                    $results[] = $this->transform( $issue_data, 'theme', $slug, $version, $can_update, '', $is_active, $asset_name, $available_update_version, $ranges );
                }
            }
        }
        
        // WordPress Core
        if ( ! empty( $raw_issues['wordpress'] ) && is_array( $raw_issues['wordpress'] ) ) {
            global $wp_version;
            foreach ( $raw_issues['wordpress'] as $issue_data ) {
                $ranges = AffectedVersionMatcher::normalize( $issue_data['affected_versions'] ?? ( $issue_data['vnv'] ?? null ) );
                if ( ! empty( $ranges ) && AffectedVersionMatcher::is_affected( (string) $wp_version, $ranges ) === false ) {
                    continue;
                }

                $results[] = $this->transform( $issue_data, 'core', 'wordpress', $wp_version, false, '', null, 'WordPress Core', '', $ranges );
            }
        }

        if ( class_exists( ScanLogger::class ) ) {
            ScanLogger::log( "Vulnerability check complete. Found " . count( $results ) . " known issues." );
        }

        return [
            'issues'   => $results,
            'count'    => count( $results ),
            'complete' => true,
            'failed'   => false,
        ];
    }

    private function transform( $issue, $type, $slug, $current_version = 'Unknown', $can_update = false, $plugin_file = '', ?bool $is_theme_active = null, $asset_name = '', $available_update_version = '', array $affected_versions = [] ) {
        $raw_severity = strtolower( $issue['severity'] ?? 'medium' );
        $severity = $raw_severity;
        if ( ! in_array( $severity, ['critical', 'high', 'medium', 'low'] ) ) {
            $severity = 'medium';
            $raw_severity = 'medium';
        }

        $preferred_remediation = 'update';
        if ( $type === 'theme' && $is_theme_active === false ) {
            $severity = $this->downgrade_inactive_theme_severity( $severity );
            $preferred_remediation = 'delete';
        }

        $normalized_asset_name = is_string( $asset_name ) && trim( $asset_name ) !== '' ? trim( $asset_name ) : $slug;
        if ( $type === 'core' ) {
            $normalized_asset_name = 'WordPress Core';
        }
        $normalized_affected_versions = AffectedVersionMatcher::normalize( $affected_versions );
        $affected_versions_label = trim( (string) ( $issue['vnv'] ?? '' ) );
        if ( $affected_versions_label === '' && ! empty( $normalized_affected_versions ) ) {
            $affected_versions_label = implode( ', ', $normalized_affected_versions );
        }

        return [
            'id'          => $issue['id'] ?? uniqid('vuln_'),
            'hash'        => IssueRepository::generateHash( 'vulnerability', ($issue['id'] ?? '') . '_' . $slug ),
            'type'        => IssueType::VULNERABILITY,
            'status'      => 'vulnerable',
            'severity'    => $severity,
            'file'        => $slug,
            'message'     => $issue['title'] ?? 'Unknown Vulnerability',
            'description' => $issue['impact'] ?? '',
            'created_at'  => time(),
            'slug'        => $slug,
            'version'     => $current_version,
            'data'        => [
                'component_type'           => $type,
                'slug'                     => $slug,
                'plugin_file'              => $plugin_file,
                'component_name'           => $normalized_asset_name,
                'asset_name'               => $normalized_asset_name,
                'vulnerability_id'         => (string) ( $issue['id'] ?? '' ),
                'fixed_in'                 => $issue['fixed_in'] ?? null,
                'affected_versions'        => $normalized_affected_versions,
                'cve'                      => $issue['cve'] ?? '',
                'vnv'                      => $affected_versions_label,
                'cvss'                     => $issue['cvss'] ?? 0,
                'references'               => $issue['references'] ?? [],
                'can_update'               => $can_update,
                'available_update_version' => $available_update_version,
                'has_update_available'     => $available_update_version !== '',
                'is_active'                => $is_theme_active,
                'original_severity'        => $raw_severity,
                'remediation_preferred'    => $preferred_remediation,
                'version'                  => $current_version,
                'title'                    => $issue['title'] ?? 'Unknown Vulnerability',
                'description'              => $issue['impact'] ?? '',
            ],
            'meta'        => [
                'label'         => ucfirst($type) . ': ' . $slug,
                'layout'        => 'vulnerability_card',
                'asset_slug'    => $slug,
                'asset_name'    => $normalized_asset_name,
                'asset_type'    => $type,
                'asset_version' => $current_version,
                'is_active'     => $is_theme_active,
            ]
        ];
    }

    private function downgrade_inactive_theme_severity( string $severity ): string {
        if ( $severity === 'critical' ) {
            return 'high';
        }
        if ( $severity === 'high' ) {
            return 'medium';
        }
        if ( $severity === 'medium' ) {
            return 'low';
        }
        return 'low';
    }

    /**
     * Resolve plugin update map keyed by plugin base file.
     * Refreshes stale/missing transients to avoid false "manual fix" results.
     *
     * @return array<string,mixed>
     */
    private function get_plugin_update_map(): array {
        if ( ! function_exists( 'wp_update_plugins' ) ) {
            require_once ABSPATH . 'wp-includes/update.php';
        }

        $update_plugins = get_site_transient( 'update_plugins' );
        $is_stale = true;
        if ( is_object( $update_plugins ) && isset( $update_plugins->last_checked ) && is_numeric( $update_plugins->last_checked ) ) {
            $is_stale = ( time() - (int) $update_plugins->last_checked ) > ( 6 * HOUR_IN_SECONDS );
        }

        // Refresh when transient is missing, malformed, or stale.
        if (
            ! is_object( $update_plugins ) ||
            ! isset( $update_plugins->response ) ||
            $is_stale
        ) {
            wp_update_plugins();
            $update_plugins = get_site_transient( 'update_plugins' );
        }

        if ( ! is_object( $update_plugins ) || ! isset( $update_plugins->response ) ) {
            return [];
        }

        if ( is_array( $update_plugins->response ) ) {
            return $update_plugins->response;
        }

        if ( is_object( $update_plugins->response ) ) {
            return (array) $update_plugins->response;
        }

        return [];
    }

    /**
     * Resolve theme update map keyed by theme stylesheet slug.
     *
     * @return array<string,mixed>
     */
    private function get_theme_update_map(): array {
        if ( ! function_exists( 'wp_update_themes' ) ) {
            require_once ABSPATH . 'wp-includes/update.php';
        }

        $update_themes = get_site_transient( 'update_themes' );
        $is_stale = true;
        if ( is_object( $update_themes ) && isset( $update_themes->last_checked ) && is_numeric( $update_themes->last_checked ) ) {
            $is_stale = ( time() - (int) $update_themes->last_checked ) > ( 6 * HOUR_IN_SECONDS );
        }

        if (
            ! is_object( $update_themes ) ||
            ! isset( $update_themes->response ) ||
            $is_stale
        ) {
            wp_update_themes();
            $update_themes = get_site_transient( 'update_themes' );
        }

        if ( ! is_object( $update_themes ) || ! isset( $update_themes->response ) ) {
            return [];
        }

        if ( is_array( $update_themes->response ) ) {
            return $update_themes->response;
        }

        if ( is_object( $update_themes->response ) ) {
            return (array) $update_themes->response;
        }

        return [];
    }

    /**
     * Extract the target version from a WordPress update payload.
     *
     * @param mixed $update_entry
     * @return string
     */
    private function resolve_available_update_version( $update_entry ): string {
        if ( is_object( $update_entry ) && isset( $update_entry->new_version ) ) {
            return sanitize_text_field( (string) $update_entry->new_version );
        }

        if ( is_array( $update_entry ) && isset( $update_entry['new_version'] ) ) {
            return sanitize_text_field( (string) $update_entry['new_version'] );
        }

        return '';
    }

    /**
     * Determine whether the available WordPress update falls outside the affected range.
     *
     * @param mixed $available_update_version
     * @param array $affected_ranges
     * @return bool
     */
    private function is_safe_update_candidate( $available_update_version, array $affected_ranges ): bool {
        $available = sanitize_text_field( (string) $available_update_version );

        if ( $available === '' || empty( $affected_ranges ) ) {
            return false;
        }

        return AffectedVersionMatcher::is_affected( $available, $affected_ranges ) === false;
    }
}
