<?php

declare(strict_types=1);

namespace SiteFort\Modules\Vulnerability\Services;

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

use SiteFort\Includes\SettingsStore;
use SiteFort\Modules\Scanner\Repository\IssueRepository;

class VulnerabilityRemediationService {

	private ?string $active_scan_id = null;
	private ?array $installed_versions = null;

	public function reconcile_updated_asset( string $slug, string $type, string $installed_version ): array {
		$summary = $this->empty_summary( $installed_version );

		if ( ! $this->ensure_dependencies() ) {
			$summary['unverified'] = 1;
			return $summary;
		}

		$asset_issues = $this->collect_asset_issues( $slug, $type );
		$processed = [];
		foreach ( $asset_issues as $record ) {
			$hash = $this->extract_issue_hash( $record['issue'] );
			if ( $hash === '' || isset( $processed[ $hash ] ) ) {
				continue;
			}
			$processed[ $hash ] = true;

			$ranges = $this->extract_affected_ranges( $record['issue'], $slug, $type );
			$affected = AffectedVersionMatcher::is_affected( $installed_version, $ranges );
			$summary['checked']++;

			if ( $affected === false ) {
				$this->delete_issue_everywhere( $hash, $record['scan_ids'] );
				$summary['resolved']++;
				$summary['resolved_hashes'][] = $hash;
				continue;
			}

			$this->update_issue_version_everywhere( $hash, $record['scan_ids'], $installed_version );
			$summary['updated_hashes'][] = $hash;
			if ( $affected === true ) {
				$summary['still_affected']++;
				$summary['still_affected_hashes'][] = $hash;
			} else {
				$summary['unverified']++;
				$summary['unverified_hashes'][] = $hash;
			}
		}

		$summary['active_count'] = $this->sync_result_state( 'vulnerability_asset_remediated' );
		return $summary;
	}

	public function clear_asset_findings( string $slug, string $type ): array {
		$summary = $this->empty_summary( '' );

		if ( ! $this->ensure_dependencies() ) {
			return $summary;
		}

		$processed = [];
		foreach ( $this->collect_asset_issues( $slug, $type ) as $record ) {
			$hash = $this->extract_issue_hash( $record['issue'] );
			if ( $hash === '' || isset( $processed[ $hash ] ) ) {
				continue;
			}
			$processed[ $hash ] = true;
			$this->delete_issue_everywhere( $hash, $record['scan_ids'] );
			$summary['resolved']++;
			$summary['resolved_hashes'][] = $hash;
			$summary['checked']++;
		}

		$summary['active_count'] = $this->sync_result_state( 'vulnerability_asset_removed' );
		return $summary;
	}

	public function purge_resolved_active_findings(): array {
		$summary = $this->empty_summary( '' );

		if ( ! $this->ensure_dependencies() ) {
			return $summary;
		}

		$processed = [];
		$hashes_to_delete = [];
		foreach ( $this->get_current_vulnerability_issues() as $issue ) {
			if ( ! is_array( $issue ) ) {
				continue;
			}

			$hash = $this->extract_issue_hash( $issue );
			if ( $hash === '' || isset( $processed[ $hash ] ) ) {
				continue;
			}
			$processed[ $hash ] = true;

			$asset = $this->extract_asset_identity( $issue );
			if ( $asset['slug'] === '' || $asset['type'] === '' ) {
				continue;
			}

			$installed_version = $this->get_installed_asset_version( $asset['slug'], $asset['type'] );
			$summary['checked']++;

			if ( $installed_version === null ) {
				$hashes_to_delete[ $hash ] = true;
				$summary['resolved']++;
				$summary['resolved_hashes'][] = $hash;
				continue;
			}

			$ranges = $this->extract_affected_ranges( $issue, $asset['slug'], $asset['type'] );
			$affected = AffectedVersionMatcher::is_affected( $installed_version, $ranges );
			if ( $affected === false ) {
				$hashes_to_delete[ $hash ] = true;
				$summary['resolved']++;
				$summary['resolved_hashes'][] = $hash;
			} elseif ( $affected === true ) {
				$summary['still_affected']++;
				$summary['still_affected_hashes'][] = $hash;
			} else {
				$summary['unverified']++;
				$summary['unverified_hashes'][] = $hash;
			}
		}

		foreach ( array_keys( $hashes_to_delete ) as $hash ) {
			$this->delete_issue_everywhere( (string) $hash, [] );
		}

		if ( ! empty( $hashes_to_delete ) ) {
			$summary['active_count'] = $this->sync_result_state( 'vulnerability_active_findings_reconciled' );
		}

		return $summary;
	}

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

	private function collect_asset_issues( string $slug, string $type ): array {
		$slug = sanitize_key( $slug );
		$type = sanitize_key( $type );
		$records = [];

		$current_issues = $this->get_current_vulnerability_issues();
		foreach ( $current_issues as $issue ) {
			if ( ! is_array( $issue ) || ! $this->matches_asset_issue( $issue, $slug, $type ) ) {
				continue;
			}

			$hash = $this->extract_issue_hash( $issue );
			if ( $hash === '' ) {
				continue;
			}

			$records[ $hash ] = [
				'issue'    => $issue,
				'scan_ids' => [],
			];
		}

		$active_scan_id = $this->get_active_scan_id();
		if ( $active_scan_id !== '' ) {
			$staged_issues = $this->get_staged_vulnerability_issues( $active_scan_id );
			foreach ( $staged_issues as $issue ) {
				if ( ! is_array( $issue ) || ! $this->matches_asset_issue( $issue, $slug, $type ) ) {
					continue;
				}

				$hash = $this->extract_issue_hash( $issue );
				if ( $hash === '' ) {
					continue;
				}

				if ( ! isset( $records[ $hash ] ) ) {
					$records[ $hash ] = [
						'issue'    => $issue,
						'scan_ids' => [],
					];
				}

				$records[ $hash ]['scan_ids'][] = $active_scan_id;
			}
		}

		foreach ( $records as $hash => $record ) {
			$records[ $hash ]['scan_ids'] = array_values( array_unique( array_filter( $record['scan_ids'] ) ) );
		}

		return array_values( $records );
	}

	private function get_current_vulnerability_issues(): \Generator {
		$limit = 500;
		$offset = 0;

		do {
			$batch = IssueRepository::getCurrentIssuesByType( 'vulnerability', $limit, $offset );
			if ( empty( $batch ) ) {
				break;
			}

			foreach ( $batch as $issue ) {
				yield $issue;
			}

			$count = count( $batch );
			$offset += $count;
		} while ( $count === $limit );
	}

	private function get_staged_vulnerability_issues( string $scan_id ): \Generator {
		$limit = 500;
		$offset = 0;

		do {
			$batch = IssueRepository::getStagedIssuesByType( $scan_id, 'vulnerability', $limit, $offset );
			if ( empty( $batch ) ) {
				break;
			}

			foreach ( $batch as $issue ) {
				yield $issue;
			}

			$count = count( $batch );
			$offset += $count;
		} while ( $count === $limit );
	}

	private function matches_asset_issue( array $issue, string $slug, string $type ): bool {
		if ( sanitize_key( (string) ( $issue['type'] ?? '' ) ) !== 'vulnerability' ) {
			return false;
		}

		$asset = $this->extract_asset_identity( $issue );
		return $asset['type'] === sanitize_key( $type ) && $asset['slug'] === sanitize_key( $slug );
	}

	private function extract_asset_identity( array $issue ): array {
		$data = isset( $issue['data'] ) && is_array( $issue['data'] ) ? $issue['data'] : [];
		$issue_type = sanitize_key( (string) ( $data['component_type'] ?? ( $issue['asset_type'] ?? '' ) ) );
		$issue_slug = sanitize_key( (string) ( $issue['slug'] ?? $data['slug'] ?? $data['asset_slug'] ?? $issue['file_path'] ?? $issue['file'] ?? '' ) );

		return [
			'type' => $issue_type,
			'slug' => $issue_slug,
		];
	}

	private function extract_affected_ranges( array $issue, string $slug, string $type ): array {
		$data = isset( $issue['data'] ) && is_array( $issue['data'] ) ? $issue['data'] : [];

		$ranges = AffectedVersionMatcher::normalize(
			$data['affected_versions']
				?? ( $issue['affected_versions'] ?? ( $data['vnv'] ?? ( $issue['vnv'] ?? null ) ) )
		);
		if ( ! empty( $ranges ) ) {
			return $ranges;
		}

		return $this->find_cached_affected_ranges( $issue, $slug, $type );
	}

	private function find_cached_affected_ranges( array $issue, string $slug, string $type ): array {
		$cache = SettingsStore::get_field( SettingsStore::COL_STATE, 'vulnerability_issues', [] );
		if ( ! is_array( $cache ) ) {
			return [];
		}

		$bucket = $type === 'theme' ? 'themes' : ( $type === 'plugin' ? 'plugins' : 'wordpress' );
		$items = [];
		if ( $bucket === 'wordpress' ) {
			$items = isset( $cache['wordpress'] ) && is_array( $cache['wordpress'] ) ? $cache['wordpress'] : [];
		} elseif ( isset( $cache[ $bucket ][ $slug ] ) && is_array( $cache[ $bucket ][ $slug ] ) ) {
			$items = $cache[ $bucket ][ $slug ];
		}

		if ( empty( $items ) ) {
			return [];
		}

		$data = isset( $issue['data'] ) && is_array( $issue['data'] ) ? $issue['data'] : [];
		$vulnerability_id = trim( (string) ( $data['vulnerability_id'] ?? '' ) );
		$cve = trim( (string) ( $data['cve'] ?? ( $issue['cve'] ?? '' ) ) );
		$title = trim( (string) ( $data['title'] ?? ( $issue['title'] ?? ( $issue['message'] ?? '' ) ) ) );

		foreach ( $items as $item ) {
			if ( ! is_array( $item ) ) {
				continue;
			}

			$item_id = trim( (string) ( $item['id'] ?? '' ) );
			$item_cve = trim( (string) ( $item['cve'] ?? '' ) );
			$item_title = trim( (string) ( $item['title'] ?? '' ) );

			$matched = ( $vulnerability_id !== '' && $item_id !== '' && hash_equals( $vulnerability_id, $item_id ) )
				|| ( $cve !== '' && $item_cve !== '' && hash_equals( strtolower( $cve ), strtolower( $item_cve ) ) )
				|| ( $title !== '' && $item_title !== '' && hash_equals( strtolower( $title ), strtolower( $item_title ) ) );

			if ( $matched ) {
				return AffectedVersionMatcher::normalize( $item['affected_versions'] ?? ( $item['vnv'] ?? null ) );
			}
		}

		return [];
	}

	private function delete_issue_everywhere( string $hash, array $scan_ids ): void {
		IssueRepository::delete( $hash );
		foreach ( $this->with_active_scan_id( $scan_ids ) as $scan_id ) {
			IssueRepository::deleteStagedByHash( (string) $scan_id, $hash );
		}
	}

	private function update_issue_version_everywhere( string $hash, array $scan_ids, string $installed_version ): void {
		$data_update = [
			'version'                  => $installed_version,
			'available_update_version' => '',
			'has_update_available'     => false,
			'can_update'               => false,
		];

		IssueRepository::updateIssueData( $hash, $data_update );
		foreach ( $this->with_active_scan_id( $scan_ids ) as $scan_id ) {
			IssueRepository::updateStagedIssueData( (string) $scan_id, $hash, $data_update );
		}
	}

	private function get_installed_asset_version( string $slug, string $type ): ?string {
		$slug = sanitize_key( $slug );
		$type = sanitize_key( $type );
		if ( $slug === '' || $type === '' ) {
			return null;
		}

		$versions = $this->get_installed_asset_versions();
		return $versions[ $type ][ $slug ] ?? null;
	}

	private function get_installed_asset_versions(): array {
		if ( is_array( $this->installed_versions ) ) {
			return $this->installed_versions;
		}

		$versions = [
			'plugin' => [],
			'theme'  => [],
			'core'   => [],
		];

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

		foreach ( get_plugins() as $file => $data ) {
			$file = (string) $file;
			$version = sanitize_text_field( (string) ( $data['Version'] ?? '' ) );
			$slug_candidates = [
				dirname( $file ) !== '.' ? dirname( $file ) : '',
				basename( $file, '.php' ),
				$file,
			];

			foreach ( $slug_candidates as $candidate ) {
				$slug = sanitize_key( (string) $candidate );
				if ( $slug !== '' ) {
					$versions['plugin'][ $slug ] = $version;
				}
			}
		}

		foreach ( wp_get_themes() as $slug => $theme ) {
			if ( $theme instanceof \WP_Theme ) {
				$versions['theme'][ sanitize_key( (string) $slug ) ] = sanitize_text_field( (string) $theme->get( 'Version' ) );
			}
		}

		global $wp_version;
		$versions['core']['wordpress'] = sanitize_text_field( (string) $wp_version );

		$this->installed_versions = $versions;
		return $this->installed_versions;
	}

	private function with_active_scan_id( array $scan_ids ): array {
		$active_scan_id = $this->get_active_scan_id();
		if ( $active_scan_id !== '' ) {
			$scan_ids[] = $active_scan_id;
		}

		return array_values( array_unique( array_filter( array_map( 'strval', $scan_ids ) ) ) );
	}

	private function sync_result_state( string $action ): int {
		if ( ! class_exists( VulnerabilityRefreshService::class ) ) {
			$path = __DIR__ . '/VulnerabilityRefreshService.php';
			if ( file_exists( $path ) ) {
				require_once $path;
			}
		}

		if ( class_exists( VulnerabilityRefreshService::class ) ) {
			$active_count = VulnerabilityRefreshService::syncActiveIssueCount();
		} else {
			$counts = IssueRepository::getCounts();
			$active_count = is_array( $counts ) ? (int) ( $counts['active'] ?? 0 ) : 0;
			SettingsStore::set_field( SettingsStore::COL_STATE, 'scan_issues_count', $active_count );
		}

		do_action( 'sitefort_scan_results_changed', [
			'action'       => $action,
			'scope'        => 'vulnerability',
			'active_count' => $active_count,
		] );

		return $active_count;
	}

	private function get_active_scan_id(): string {
		if ( $this->active_scan_id !== null ) {
			return $this->active_scan_id;
		}

		if ( ! class_exists( '\SiteFort\Modules\Scanner\Queue\ScanQueue' ) ) {
			$path = dirname( dirname( __DIR__ ) ) . '/Scanner/Queue/ScanQueue.php';
			if ( file_exists( $path ) ) {
				require_once $path;
			}
		}

		if (
			! class_exists( '\SiteFort\Modules\Scanner\Queue\ScanQueue' )
			|| ! \SiteFort\Modules\Scanner\Queue\ScanQueue::is_active()
		) {
			$this->active_scan_id = '';
			return '';
		}

		$queue = \SiteFort\Modules\Scanner\Queue\ScanQueue::get();
		$this->active_scan_id = is_array( $queue ) ? sanitize_text_field( (string) ( $queue['id'] ?? '' ) ) : '';
		return $this->active_scan_id;
	}

	private function ensure_dependencies(): bool {
		if ( ! class_exists( IssueRepository::class ) ) {
			$path = dirname( dirname( __DIR__ ) ) . '/Scanner/Repository/IssueRepository.php';
			if ( file_exists( $path ) ) {
				require_once $path;
			}
		}

		if ( ! class_exists( AffectedVersionMatcher::class ) ) {
			$path = __DIR__ . '/AffectedVersionMatcher.php';
			if ( file_exists( $path ) ) {
				require_once $path;
			}
		}

		return class_exists( IssueRepository::class ) && class_exists( AffectedVersionMatcher::class );
	}

	private function extract_issue_hash( array $issue ): string {
		return sanitize_text_field( (string) ( $issue['hash'] ?? ( $issue['issue_hash'] ?? '' ) ) );
	}
}
