<?php

declare(strict_types=1);

namespace SiteFort\Modules\Vulnerability\Services;

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

class AffectedVersionMatcher {

	public static function normalize( $value ): array {
		if ( empty( $value ) ) {
			return [];
		}

		if ( is_array( $value ) ) {
			$items = $value;
		} elseif ( is_string( $value ) ) {
			$trimmed = trim( $value );
			if ( $trimmed === '' ) {
				return [];
			}

			if ( $trimmed[0] === '[' ) {
				$decoded = json_decode( $trimmed, true );
				$items = is_array( $decoded ) ? $decoded : [ $trimmed ];
			} else {
				$items = [ $trimmed ];
			}
		} else {
			return [];
		}

		$ranges = [];
		foreach ( $items as $item ) {
			if ( ! is_scalar( $item ) ) {
				continue;
			}

			$range = trim( (string) $item );
			if ( $range === '' ) {
				continue;
			}

			$ranges[] = $range;
		}

		return array_values( array_unique( $ranges ) );
	}

	public static function is_affected( string $installed_version, $ranges ): ?bool {
		$installed_version = trim( $installed_version );
		if ( $installed_version === '' || strtolower( $installed_version ) === 'unknown' ) {
			return null;
		}

		$normalized_ranges = self::normalize( $ranges );
		if ( empty( $normalized_ranges ) ) {
			return null;
		}

		$has_valid_constraint = false;
		foreach ( $normalized_ranges as $range ) {
			$result = self::matches_range( $installed_version, $range );
			if ( $result === null ) {
				continue;
			}

			$has_valid_constraint = true;
			if ( $result ) {
				return true;
			}
		}

		return $has_valid_constraint ? false : null;
	}

	private static function matches_range( string $installed_version, string $range ): ?bool {
		$range = trim( $range );
		if ( $range === '' ) {
			return null;
		}

		if ( strpos( $range, ' - ' ) !== false ) {
			$parts = array_map( 'trim', explode( ' - ', $range, 2 ) );
			if ( count( $parts ) !== 2 || $parts[0] === '' || $parts[1] === '' ) {
				return null;
			}

			$lower_bound_matches = self::compare( $installed_version, $parts[0], '>=' );
			$upper_bound_matches = self::compare( $installed_version, $parts[1], '<=' );
			if ( $lower_bound_matches === null || $upper_bound_matches === null ) {
				return null;
			}

			return $lower_bound_matches && $upper_bound_matches;
		}

		if ( strpos( $range, ',' ) !== false ) {
			$parts = array_map( 'trim', explode( ',', $range ) );
			$has_valid = false;
			foreach ( $parts as $part ) {
				if ( $part === '' ) {
					continue;
				}

				$matched = self::matches_range( $installed_version, $part );
				if ( $matched === null ) {
					return null;
				}

				$has_valid = true;
				if ( ! $matched ) {
					return false;
				}
			}

			return $has_valid ? true : null;
		}

		return self::compare( $installed_version, $range );
	}

	private static function compare( string $installed_version, string $constraint, string $default_operator = '' ): ?bool {
		$constraint = trim( $constraint );
		if ( $constraint === '' ) {
			return null;
		}

		if ( preg_match( '/^(<=|>=|!=|==|=|<|>)\s*(.+)$/', $constraint, $matches ) ) {
			$operator = $matches[1] === '=' ? '==' : $matches[1];
			$target = trim( $matches[2] );
			if ( $target === '' ) {
				return null;
			}

			return version_compare( $installed_version, $target, $operator );
		}

		if ( $default_operator !== '' ) {
			return version_compare( $installed_version, $constraint, $default_operator );
		}

		return null;
	}
}
