<?php

declare(strict_types=1);

namespace SiteFort\Modules\Vulnerability;

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

/**
 * Abandoned Plugin & Theme Checker.
 *
 * Detects plugins and themes that have not been updated within a configurable
 * threshold (default 365 days) by querying the WordPress.org API.
 *
 */
class AbandonedPluginChecker {

    /**
     * Threshold for "abandoned" -- plugins/themes not updated in this many days.
     *
     * @var int
     */
    private int $threshold_days;

    /**
     * Constructor.
     *
     * @param int $threshold_days Number of days without update to consider abandoned.
     */
    public function __construct( int $threshold_days = 365 ) {
        $this->threshold_days = $threshold_days;
    }

    /**
     * Check all installed plugins for abandonment.
     *
     * @return array List of abandoned plugins with details.
     */
    public function check_all(): array {
        if ( ! function_exists( 'get_plugins' ) ) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }

        $plugins   = get_plugins();
        $abandoned = [];

        foreach ( $plugins as $file => $data ) {
            $slug = dirname( $file );
            if ( $slug === '.' ) {
                $slug = basename( $file, '.php' );
            }

            $info = $this->get_plugin_info( $slug );
            if ( $info === null ) {
                continue; // Not in wp.org repo (custom/commercial)
            }

            $last_updated = $info['last_updated'] ?? null;
            if ( ! $last_updated ) {
                continue;
            }

            $last_updated_ts = strtotime( $last_updated );
            if ( false === $last_updated_ts || $last_updated_ts <= 0 ) {
                continue;
            }
            $days_since = (int) floor( ( time() - $last_updated_ts ) / 86400 );

            if ( $days_since >= $this->threshold_days ) {
                $abandoned[] = [
                    'slug'              => $slug,
                    'name'              => $data['Name'] ?? $slug,
                    'version'           => $data['Version'] ?? 'unknown',
                    'last_updated'      => $last_updated,
                    'days_since_update' => $days_since,
                    'active_installs'   => $info['active_installs'] ?? 0,
                    'tested_up_to'      => $info['tested'] ?? 'unknown',
                    'is_active'         => is_plugin_active( $file ),
                    'file'              => $file,
                    // Flag if removed from wp.org
                    'removed'           => ! empty( $info['removed'] ),
                ];
            }
        }

        // Sort by days since update (most outdated first)
        usort( $abandoned, fn( $a, $b ) => $b['days_since_update'] - $a['days_since_update'] );

        return $abandoned;
    }

    /**
     * Check all installed themes for abandonment.
     *
     * @return array List of abandoned themes with details.
     */
    public function check_themes(): array {
        $themes           = wp_get_themes();
        $abandoned        = [];
        $active_stylesheet = wp_get_theme()->get_stylesheet();

        foreach ( $themes as $slug => $theme ) {
            $info = $this->get_theme_info( $slug );
            if ( $info === null ) {
                continue; // Not in wp.org repo (custom/commercial)
            }

            $last_updated = $info['last_updated'] ?? null;
            if ( ! $last_updated ) {
                continue;
            }

            $last_updated_ts = strtotime( $last_updated );
            if ( false === $last_updated_ts || $last_updated_ts <= 0 ) {
                continue;
            }
            $days_since = (int) floor( ( time() - $last_updated_ts ) / 86400 );

            if ( $days_since >= $this->threshold_days ) {
                $is_active = ( $active_stylesheet === $slug );

                $abandoned[] = [
                    'slug'              => $slug,
                    'name'              => $theme->get( 'Name' ) ?: $slug,
                    'version'           => $theme->get( 'Version' ) ?: 'unknown',
                    'last_updated'      => $last_updated,
                    'days_since_update' => $days_since,
                    'active_installs'   => $info['active_installs'] ?? 0,
                    'tested_up_to'      => $info['tested'] ?? 'unknown',
                    'is_active'         => $is_active,
                    // Flag if removed from wp.org
                    'removed'           => ! empty( $info['removed'] ),
                ];
            }
        }

        // Sort by days since update (most outdated first)
        usort( $abandoned, fn( $a, $b ) => $b['days_since_update'] - $a['days_since_update'] );

        return $abandoned;
    }

    /**
     * Get plugin info from WordPress.org API.
     * Cached in transients for 12 hours.
     *
     * @param string $slug Plugin slug.
     * @return array|null Plugin info array, or null if not found.
     */
    private function get_plugin_info( string $slug ): ?array {
        $transient_key = 'sitefort_pinfo_' . md5( $slug );
        $cached        = get_transient( $transient_key );
        if ( $cached !== false ) {
            if ( ! empty( $cached['not_found'] ) ) {
                return null;
            }
            return $cached;
        }

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

        $response = plugins_api( 'plugin_information', [
            'slug'   => $slug,
            'fields' => [
                'last_updated'    => true,
                'active_installs' => true,
                'tested'          => true,
                'sections'        => false,
                'description'     => false,
            ],
        ] );

        if ( is_wp_error( $response ) ) {
            // Could be a removed/commercial plugin -- cache as null
            set_transient( $transient_key, [ 'not_found' => true ], 12 * HOUR_IN_SECONDS );
            return null;
        }

        $info = [
            'last_updated'    => $response->last_updated ?? null,
            'active_installs' => $response->active_installs ?? 0,
            'tested'          => $response->tested ?? null,
        ];

        set_transient( $transient_key, $info, 12 * HOUR_IN_SECONDS );
        return $info;
    }

    /**
     * Get theme info from WordPress.org API.
     * Cached in transients for 12 hours.
     *
     * @param string $slug Theme slug.
     * @return array|null Theme info array, or null if not found.
     */
    private function get_theme_info( string $slug ): ?array {
        $transient_key = 'sitefort_tinfo_' . md5( $slug );
        $cached        = get_transient( $transient_key );
        if ( $cached !== false ) {
            if ( ! empty( $cached['not_found'] ) ) {
                return null;
            }
            return $cached;
        }

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

        $response = themes_api( 'theme_information', [
            'slug'   => $slug,
            'fields' => [
                'last_updated'    => true,
                'active_installs' => true,
                'tested'          => true,
                'sections'        => false,
                'description'     => false,
            ],
        ] );

        if ( is_wp_error( $response ) ) {
            // Could be a removed/commercial theme -- cache as null
            set_transient( $transient_key, [ 'not_found' => true ], 12 * HOUR_IN_SECONDS );
            return null;
        }

        $info = [
            'last_updated'    => $response->last_updated ?? null,
            'active_installs' => $response->active_installs ?? 0,
            'tested'          => $response->tested ?? null,
        ];

        set_transient( $transient_key, $info, 12 * HOUR_IN_SECONDS );
        return $info;
    }
}
