<?php
/**
 * Orchestration des scans (lots AJAX, verdict, cibles).
 *
 * @package PCC
 * @license GPL-2.0-or-later
 */

defined( 'ABSPATH' ) || exit;

class PCC_Scanner {

	const ALL_VERSIONS     = array( '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' );
	const GUEST_SCAN_KEY   = 'pcc_guest_scan_log';
	const DROPIN_FILES     = array(
		'advanced-cache.php',
		'object-cache.php',
		'db.php',
		'sunrise.php',
		'maintenance.php',
	);

	/** @var PCC_Plugin */
	private $plugin;

	/** @var PCC_Static_Analyzer */
	private $analyzer;

	public function __construct( PCC_Plugin $plugin ) {
		$this->plugin   = $plugin;
		$this->analyzer = new PCC_Static_Analyzer();
	}

	public static function next_minor_version( $current ) {
		$parts = explode( '.', (string) $current );
		$major = (int) ( $parts[0] ?? 8 );
		$minor = (int) ( $parts[1] ?? 0 );
		$next  = $major . '.' . ( $minor + 1 );
		return in_array( $next, self::ALL_VERSIONS, true ) ? $next : '';
	}

	/**
	 * Chemin du dossier must-use (hors liste Extensions).
	 *
	 * @return string
	 */
	public static function mu_plugins_dir() {
		$dir = defined( 'WPMU_PLUGIN_DIR' ) ? WPMU_PLUGIN_DIR : WP_CONTENT_DIR . '/mu-plugins';
		return wp_normalize_path( $dir );
	}

	/**
	 * True si le site a au moins un fichier PHP must-use scannable.
	 */
	public static function has_mu_plugins() {
		$dir = self::mu_plugins_dir();
		if ( ! is_dir( $dir ) ) {
			return false;
		}

		$root_files = glob( $dir . '/*.php' );
		if ( is_array( $root_files ) && ! empty( $root_files ) ) {
			return true;
		}

		foreach ( glob( $dir . '/*', GLOB_ONLYDIR ) ?: array() as $subdir ) {
			$loader = $subdir . '/' . basename( $subdir ) . '.php';
			if ( is_file( $loader ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * True si au moins un drop-in WordPress est présent dans wp-content/.
	 */
	public static function has_dropins() {
		foreach ( self::DROPIN_FILES as $dropin ) {
			if ( is_file( WP_CONTENT_DIR . '/' . $dropin ) ) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Versions PHP sélectionnables selon le palier.
	 *
	 * @return string[]
	 */
	public function allowed_target_versions() {
		if ( $this->plugin->has_feature( 'php_target_full' ) ) {
			return self::ALL_VERSIONS;
		}
		$next = self::next_minor_version( phpversion() );
		if ( $next ) {
			return array( $next );
		}
		return array( '8.4' );
	}

	/**
	 * @return array<int, array{id:string,label:string,type:string,path:string,active:bool}>
	 */
	public function get_scan_targets() {
		$targets = array();

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

		$active_plugins = (array) get_option( 'active_plugins', array() );
		$all_plugins    = get_plugins();
		$guest_mode     = ! $this->plugin->has_feature( 'scan_all_plugins' );
		$can_premium    = $this->plugin->has_feature( 'scan_premium_code' );

		foreach ( $all_plugins as $file => $data ) {
			$slug   = dirname( $file );
			if ( '.' === $slug ) {
				$slug = basename( $file, '.php' );
			}
			$active = in_array( $file, $active_plugins, true );

			if ( $guest_mode && ! $active ) {
				continue;
			}

			if ( ! $guest_mode && ! $can_premium && ! $active && ! $this->is_wporg_plugin( $slug ) ) {
				continue;
			}

			if ( ! $guest_mode && ! empty( $this->plugin->settings->get( 'selected_plugins' ) ) ) {
				if ( ! in_array( $slug, $this->plugin->settings->get( 'selected_plugins' ), true ) ) {
					continue;
				}
			}

			$path = WP_PLUGIN_DIR . '/' . dirname( $file );
			if ( '.' === dirname( $file ) ) {
				$path = WP_PLUGIN_DIR . '/' . $file;
			}

			$targets[] = array(
				'id'     => 'plugin:' . $slug,
				'label'  => $data['Name'] ?? $slug,
				'type'   => 'plugin',
				'path'   => wp_normalize_path( is_file( $path ) ? dirname( $path ) : $path ),
				'active' => $active,
			);
		}

		if ( $this->plugin->settings->get( 'scan_themes', true ) ) {
			$stylesheet = get_stylesheet();
			$template   = get_template();
			$themes     = array( $stylesheet );
			if ( $this->plugin->has_feature( 'scan_parent_theme' ) && $stylesheet !== $template ) {
				$themes[] = $template;
			} elseif ( ! $this->plugin->has_feature( 'scan_parent_theme' ) && $stylesheet !== $template ) {
				$themes = array( $stylesheet );
			}

			if ( $this->plugin->has_feature( 'scan_all_plugins' ) && ! empty( $this->plugin->settings->get( 'selected_themes' ) ) ) {
				$themes = array_intersect( $themes, $this->plugin->settings->get( 'selected_themes' ) );
			}

			foreach ( array_unique( $themes ) as $slug ) {
				$theme = wp_get_theme( $slug );
				if ( ! $theme->exists() ) {
					continue;
				}
				$targets[] = array(
					'id'     => 'theme:' . $slug,
					'label'  => $theme->get( 'Name' ),
					'type'   => 'theme',
					'path'   => wp_normalize_path( $theme->get_stylesheet_directory() ),
					'active' => true,
				);
			}
		}

		if ( $this->plugin->settings->get( 'scan_mu_plugins' ) && $this->plugin->has_feature( 'scan_all_plugins' ) && self::has_mu_plugins() ) {
			$mu_dir = self::mu_plugins_dir();
			if ( is_dir( $mu_dir ) ) {
				$targets[] = array(
					'id'     => 'mu-plugins:mu-plugins',
					'label'  => __( 'Must-use plugins', 'php-compatibility-checker-by-volade' ),
					'type'   => 'mu-plugin',
					'path'   => $mu_dir,
					'active' => true,
				);
			}
		}

		if ( $this->plugin->settings->get( 'scan_active_first', true ) ) {
			usort(
				$targets,
				function ( $a, $b ) {
					if ( ( $a['active'] ?? false ) === ( $b['active'] ?? false ) ) {
						return strcmp( (string) ( $a['label'] ?? '' ), (string) ( $b['label'] ?? '' ) );
					}
					return ( $a['active'] ?? false ) ? -1 : 1;
				}
			);
		}

		return $targets;
	}

	/**
	 * @return array<int, array{component_id:string,component:string,file:string}>
	 */
	public function get_dropin_queue_items() {
		if ( ! $this->plugin->settings->get( 'scan_dropins' ) || ! $this->plugin->has_feature( 'scan_all_plugins' ) ) {
			return array();
		}

		$items = array();
		foreach ( self::DROPIN_FILES as $dropin ) {
			$path = WP_CONTENT_DIR . '/' . $dropin;
			if ( is_file( $path ) ) {
				$items[] = array(
					'component_id' => 'dropin:' . $dropin,
					'component'    => $dropin,
					'file'         => wp_normalize_path( $path ),
				);
			}
		}
		return $items;
	}

	public function effective_batch_size() {
		$size = (int) $this->plugin->settings->get( 'batch_size', 0 );
		if ( $size > 0 ) {
			return $size;
		}
		return PCC_Environment::recommended_batch_size();
	}

	/**
	 * Score readiness 0–100 for a component row.
	 *
	 * @param array{errors?:int,warnings?:int,files?:int} $row
	 */
	public function component_score( $row ) {
		$errors   = (int) ( $row['errors'] ?? 0 );
		$warnings = (int) ( $row['warnings'] ?? 0 );
		if ( 0 === $errors && 0 === $warnings ) {
			return 100;
		}
		$penalty = ( $errors * 15 ) + ( $warnings * 3 );
		return max( 0, 100 - min( 100, $penalty ) );
	}

	/**
	 * @param string $slug Plugin slug.
	 */
	private function is_wporg_plugin( $slug ) {
		$cache_key = 'pcc_wporg_' . md5( $slug );
		$cached    = get_transient( $cache_key );
		if ( false !== $cached ) {
			return 'yes' === $cached;
		}

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

		$api = plugins_api(
			'plugin_information',
			array(
				'slug'   => $slug,
				'fields' => array( 'slug' => true ),
			)
		);

		$on_org = ! is_wp_error( $api ) && ! empty( $api->slug );
		set_transient( $cache_key, $on_org ? 'yes' : 'no', DAY_IN_SECONDS );
		return $on_org;
	}

	public function can_guest_scan() {
		if ( $this->plugin->has_feature( 'scan_all_plugins' ) ) {
			return true;
		}

		$log  = get_option( self::GUEST_SCAN_KEY, array() );
		$day  = gmdate( 'Y-m-d' );
		$used = ( is_array( $log ) && ( $log['day'] ?? '' ) === $day ) ? (int) ( $log['count'] ?? 0 ) : 0;

		return $used < 1;
	}

	public function record_guest_scan() {
		if ( $this->plugin->has_feature( 'scan_all_plugins' ) ) {
			return;
		}
		$day = gmdate( 'Y-m-d' );
		$log = get_option( self::GUEST_SCAN_KEY, array() );
		if ( ! is_array( $log ) || ( $log['day'] ?? '' ) !== $day ) {
			$log = array( 'day' => $day, 'count' => 0 );
		}
		$log['count'] = (int) ( $log['count'] ?? 0 ) + 1;
		update_option( self::GUEST_SCAN_KEY, $log, false );
	}

	/**
	 * Démarre un scan — prépare la file de fichiers.
	 *
	 * @param array<string, mixed> $config
	 * @param bool                 $internal Bypass rate limit (cron/CLI).
	 * @return true|WP_Error
	 */
	public function start_scan( array $config, $internal = false ) {
		if ( ! $internal && ! current_user_can( 'manage_options' ) ) {
			return new WP_Error( 'forbidden', __( 'Access denied.', 'php-compatibility-checker-by-volade' ) );
		}

		if ( ! $internal && ! $this->can_guest_scan() ) {
			return new WP_Error(
				'guest_limit',
				__( 'Free mode allows one scan per day. Connect Volade for unlimited scans.', 'php-compatibility-checker-by-volade' )
			);
		}

		$this->plugin->settings->save( $config );

		$target = $this->plugin->settings->get( 'target_php' );
		if ( ! in_array( $target, $this->allowed_target_versions(), true ) ) {
			return new WP_Error( 'invalid_target', __( 'PHP target version not allowed for your profile.', 'php-compatibility-checker-by-volade' ) );
		}

		$queue         = array();
		$files_skipped = 0;
		$excludes      = $this->plugin->settings->get( 'custom_excludes', array() );

		foreach ( $this->get_scan_targets() as $component ) {
			$skip_vendor = $this->plugin->settings->get( 'skip_vendor', true );
			if (
				$this->plugin->has_feature( 'scan_premium_code' )
				&& 'plugin' === $component['type']
				&& ! $this->is_wporg_plugin( str_replace( 'plugin:', '', $component['id'] ) )
			) {
				$skip_vendor = false;
			}
			$listed = $this->analyzer->list_php_files( $component['path'], $skip_vendor, $excludes );
			$files_skipped += (int) ( $listed['skipped'] ?? 0 );
			foreach ( $listed['files'] as $file ) {
				$queue[] = array(
					'component_id' => $component['id'],
					'component'    => $component['label'],
					'file'         => $file,
				);
			}
		}

		foreach ( $this->get_dropin_queue_items() as $item ) {
			$queue[] = $item;
		}

		if ( empty( $queue ) ) {
			return new WP_Error( 'no_files', __( 'No PHP files found to scan.', 'php-compatibility-checker-by-volade' ) );
		}

		$baseline = $this->plugin->settings->get( 'baseline_php', '' );
		if ( ! $baseline ) {
			$baseline = phpversion();
		}
		if ( ! $this->plugin->settings->get( 'delta_mode', false ) ) {
			$baseline = '';
		}

		$this->plugin->settings->set_scan_state(
			array(
				'status'        => 'running',
				'target_php'    => $target,
				'target_wp'     => $this->plugin->settings->get( 'target_wp', '' ),
				'baseline_php'  => $baseline,
				'started_at'    => gmdate( 'c' ),
				'queue'         => $queue,
				'position'      => 0,
				'findings'      => array(),
				'by_component'  => array(),
				'summary'       => array(
					'errors'        => 0,
					'warnings'      => 0,
					'files'         => count( $queue ),
					'files_skipped' => $files_skipped,
				),
			)
		);

		if ( ! $internal ) {
			$this->record_guest_scan();
		}

		return true;
	}

	/**
	 * Traite un lot de fichiers.
	 *
	 * @return array{done:bool,progress:float,state:array,status:string}
	 */
	public function process_batch() {
		$state = $this->plugin->settings->get_scan_state();
		$status = $state['status'] ?? '';

		if ( empty( $state['queue'] ) || ! in_array( $status, array( 'running', 'paused' ), true ) ) {
			return array(
				'done'     => true,
				'progress' => 100,
				'state'    => $state,
				'status'   => $status,
			);
		}

		if ( 'paused' === $status ) {
			$total = count( $state['queue'] );
			$pos   = (int) ( $state['position'] ?? 0 );
			return array(
				'done'     => false,
				'progress' => $total > 0 ? round( ( $pos / $total ) * 100, 1 ) : 0,
				'state'    => $state,
				'status'   => 'paused',
			);
		}

		$batch_size = $this->effective_batch_size();
		$position   = (int) ( $state['position'] ?? 0 );
		$queue      = $state['queue'];
		$total      = count( $queue );
		$end        = min( $position + $batch_size, $total );

		$target    = $state['target_php'] ?? '8.4';
		$target_wp = $state['target_wp'] ?? $this->plugin->settings->get( 'target_wp', '' );
		$baseline  = $state['baseline_php'] ?? '';

		for ( $i = $position; $i < $end; $i++ ) {
			$item     = $queue[ $i ];
			$raw      = $this->analyzer->scan_file( $item['file'], $target, $baseline );
			if ( $target_wp ) {
				$raw = array_merge( $raw, $this->analyzer->scan_file_wp( $item['file'], $target_wp ) );
			}
			$filtered = $this->filter_findings( $raw, $item );

			foreach ( $filtered as $finding ) {
				$state['findings'][] = $finding;
				$cid = $item['component_id'];
				if ( ! isset( $state['by_component'][ $cid ] ) ) {
					$state['by_component'][ $cid ] = array(
						'label'    => $item['component'],
						'errors'   => 0,
						'warnings' => 0,
						'score'    => 100,
					);
				}
				if ( 'error' === $finding['severity'] ) {
					$state['by_component'][ $cid ]['errors']++;
					$state['summary']['errors']++;
				} else {
					$state['by_component'][ $cid ]['warnings']++;
					$state['summary']['warnings']++;
				}
			}
		}

		$state['position'] = $end;
		$done              = $end >= $total;

		if ( $done ) {
			$state = $this->finalize_scan( $state );
			$this->plugin->settings->set_last_scan( $state );
			$this->plugin->settings->clear_scan_state();
		} else {
			$this->plugin->settings->set_scan_state( $state );
		}

		return array(
			'done'     => $done,
			'progress' => $total > 0 ? round( ( $end / $total ) * 100, 1 ) : 100,
			'state'    => $state,
			'status'   => $done ? 'complete' : 'running',
		);
	}

	/**
	 * @param array<string, mixed> $state
	 * @return array<string, mixed>
	 */
	private function finalize_scan( $state ) {
		$state['status']       = 'complete';
		$state['completed_at'] = gmdate( 'c' );
		$state['findings']     = PCC_Static_Analyzer::sort_findings( $state['findings'] ?? array() );

		foreach ( $state['by_component'] ?? array() as $cid => $row ) {
			$state['by_component'][ $cid ]['score'] = $this->component_score( $row );
		}

		$state['verdict'] = $this->compute_verdict( $state['summary'] ?? array() );
		$state['id']      = uniqid( 'pcc_', true );
		$state['top_issues'] = $this->top_components( $state['by_component'] ?? array(), 5 );

		if ( class_exists( 'PCC_Audit' ) ) {
			$audit = new PCC_Audit( $this->plugin );
			$state['audit'] = $audit->run( $state['target_php'] ?? '8.4' );
		}

		return $state;
	}

	/**
	 * @param array<string, array<string, mixed>> $by_component
	 * @return array<int, array<string, mixed>>
	 */
	private function top_components( $by_component, $limit = 5 ) {
		$rows = array_values( $by_component );
		usort(
			$rows,
			function ( $a, $b ) {
				$ea = (int) ( $a['errors'] ?? 0 );
				$eb = (int) ( $b['errors'] ?? 0 );
				if ( $ea !== $eb ) {
					return $eb <=> $ea;
				}
				return (int) ( $b['warnings'] ?? 0 ) <=> (int) ( $a['warnings'] ?? 0 );
			}
		);
		return array_slice( $rows, 0, $limit );
	}

	public function pause_scan() {
		$state = $this->plugin->settings->get_scan_state();
		if ( empty( $state['queue'] ) || 'running' !== ( $state['status'] ?? '' ) ) {
			return false;
		}
		$state['status'] = 'paused';
		$this->plugin->settings->set_scan_state( $state );
		return true;
	}

	public function resume_scan() {
		$state = $this->plugin->settings->get_scan_state();
		if ( empty( $state['queue'] ) || 'paused' !== ( $state['status'] ?? '' ) ) {
			return false;
		}
		$state['status'] = 'running';
		$this->plugin->settings->set_scan_state( $state );
		return true;
	}

	public function cancel_scan() {
		$this->plugin->settings->clear_scan_state();
	}

	/**
	 * @param array{errors?:int,warnings?:int} $summary
	 */
	public function compute_verdict( $summary ) {
		$errors = (int) ( $summary['errors'] ?? 0 );
		if ( $errors > 0 ) {
			return 'blocked';
		}
		$warnings = (int) ( $summary['warnings'] ?? 0 );
		$max_warn = (int) $this->plugin->settings->get( 'max_warnings_verdict', 0 );
		if ( $warnings > 0 && ( 0 === $max_warn || $warnings > $max_warn ) ) {
			return 'caution';
		}
		return 'ready';
	}

	public function verdict_label( $verdict ) {
		$labels = array(
			'ready'   => __( 'Ready to upgrade', 'php-compatibility-checker-by-volade' ),
			'caution' => __( 'Review warnings first', 'php-compatibility-checker-by-volade' ),
			'blocked' => __( 'Fix errors before upgrading', 'php-compatibility-checker-by-volade' ),
			'unknown' => __( 'No scan yet', 'php-compatibility-checker-by-volade' ),
		);
		return $labels[ $verdict ] ?? $labels['unknown'];
	}

	/**
	 * @param array<int, array<string, mixed>> $findings
	 * @param array<string, string>            $item
	 * @return array<int, array<string, mixed>>
	 */
	private function filter_findings( $findings, $item ) {
		$out = array();
		foreach ( $findings as $f ) {
			$f['component']    = $item['component'];
			$f['component_id'] = $item['component_id'];
			$f['fingerprint']  = md5( ( $f['file'] ?? '' ) . ':' . ( $f['line'] ?? 0 ) . ':' . ( $f['code'] ?? '' ) );
			if ( $this->plugin->settings->is_false_positive( $f['fingerprint'] ) ) {
				continue;
			}
			$out[] = $f;
		}
		return $out;
	}

	/**
	 * Scan synchrone pour WP-CLI / cron.
	 *
	 * @param bool $force Bypass premium check for internal jobs.
	 * @return array<string, mixed>|WP_Error
	 */
	public function run_full_scan_cli( $force = false ) {
		if ( ! $force && ! $this->plugin->has_feature( 'wp_cli' ) ) {
			return new WP_Error( 'premium', __( 'WP-CLI requires V+ Premium.', 'php-compatibility-checker-by-volade' ) );
		}

		$start = $this->start_scan( $this->plugin->settings->all(), true );
		if ( is_wp_error( $start ) ) {
			return $start;
		}

		$guard = 0;
		do {
			$result = $this->process_batch();
			$guard++;
		} while ( empty( $result['done'] ) && $guard < 5000 );

		return $this->plugin->settings->get_last_scan();
	}

	/**
	 * Scan ciblé d'un seul plugin (connecté).
	 *
	 * @param string $slug Plugin slug.
	 * @return true|WP_Error
	 */
	public function start_single_plugin_scan( $slug ) {
		$slug = sanitize_key( $slug );
		if ( ! $slug || ! $this->plugin->has_feature( 'scan_all_plugins' ) ) {
			return new WP_Error( 'forbidden', __( 'Volade account required.', 'php-compatibility-checker-by-volade' ) );
		}

		$config = array_merge(
			$this->plugin->settings->all(),
			array(
				'selected_plugins' => array( $slug ),
				'selected_themes'  => array(),
				'scan_themes'      => false,
			)
		);

		return $this->start_scan( $config );
	}
}
