<?php

namespace iThemesSecurity\Site_Scanner;

class Vulnerability {
	public const T_WORDPRESS = 'wordpress';
	public const T_PLUGIN = 'plugin';
	public const T_THEME = 'theme';

	public const R_PATCHED = 'patched';
	public const R_AUTO_UPDATED = 'auto-updated';
	public const R_UPDATED = 'updated';
	public const R_DEACTIVATED = 'deactivated';
	public const R_DELETED = 'deleted';
	public const R_MUTED = 'muted';

	/** @var string */
	private $id;

	/** @var string */
	private $software_type;

	/** @var string */
	private $software_slug;

	/** @var array */
	private $details;

	/** @var \DateTimeInterface|null */
	private $first_seen;

	/** @var \DateTimeInterface|null */
	private $last_seen;

	/** @var \DateTimeInterface|null */
	private $resolved_at;

	/** @var \WP_User|null */
	private $resolved_by;

	/** @var string */
	private $resolution;

	public function __construct(
		string $id,
		string $software_type,
		string $software_slug,
		array $details,
		?\DateTimeInterface $first_seen = null,
		?\DateTimeInterface $last_seen = null,
		?\DateTimeInterface $resolved_at = null,
		?\WP_User $resolved_by = null,
		string $resolution = ''
	) {
		if ( ! in_array( $software_type, [ self::T_PLUGIN, self::T_THEME, self::T_WORDPRESS ], true ) ) {
			throw new \InvalidArgumentException( 'Invalid product type.' );
		}

		if (
			! in_array( $resolution, [
				'',
				self::R_PATCHED,
				self::R_AUTO_UPDATED,
				self::R_UPDATED,
				self::R_DEACTIVATED,
				self::R_DELETED,
				self::R_MUTED
			], true )
		) {
			throw new \InvalidArgumentException( 'Invalid resolution.' );
		}

		$this->id            = $id;
		$this->software_type = $software_type;
		$this->software_slug = $software_slug;
		$this->details       = $details;
		$this->first_seen    = $first_seen;
		$this->last_seen     = $last_seen;
		$this->resolved_at   = $resolved_at;
		$this->resolved_by   = $resolved_by;
		$this->resolution    = $resolution;
	}

	public function refresh_details( array $details ): void {
		$this->details = $details;
	}

	public function seen(): void {
		if ( ! $this->first_seen ) {
			$this->first_seen = \ITSEC_Core::get_current_time_gmt( true );
		}

		$this->last_seen = \ITSEC_Core::get_current_time_gmt( true );
	}

	private function resolve( \WP_User $by = null ): void {
		$this->seen();
		$this->resolved_at = \ITSEC_Core::get_current_time_gmt( true );
		$this->resolved_by = $by;
	}

	public function patched(): self {
		if ( $this->is_patched() ) {
			return $this;
		}

		$this->resolve();
		$this->resolution = self::R_PATCHED;

		return $this;
	}

	public function updated( bool $automatic, \WP_User $by = null ): self {
		if ( $this->is_updated() ) {
			return $this;
		}

		$this->resolve( $by );
		$this->resolution = $automatic ? self::R_AUTO_UPDATED : self::R_UPDATED;

		return $this;
	}

	public function deactivated( \WP_User $by = null ): self {
		if ( $this->is_deactivated() ) {
			return $this;
		}

		$this->resolve( $by );
		$this->resolution = self::R_DEACTIVATED;

		return $this;
	}

	public function deleted( \WP_User $by = null ): self {
		if ( $this->is_deleted() ) {
			return $this;
		}

		$this->resolve( $by );
		$this->resolution = self::R_DELETED;

		return $this;
	}

	public function muted( \WP_User $by = null ): self {
		if ( $this->is_muted() ) {
			return $this;
		}

		$this->resolve( $by );
		$this->resolution = self::R_MUTED;

		return $this;
	}

	public function unresolve(): self {
		$this->resolution  = '';
		$this->resolved_by = null;
		$this->resolved_at = null;

		return $this;
	}

	public function unmute(): self {
		if ( $this->resolution === self::R_MUTED ) {
			if ( ! $this->is_software_installed() ) {
				$this->deleted();
			} elseif ( ! $this->is_software_active() ) {
				$this->deactivated();
			} else {
				$this->unresolve();
			}
		}

		return $this;
	}

	public function get_id(): string {
		return $this->id;
	}

	public function get_software_type(): string {
		return $this->software_type;
	}

	public function get_software_type_label(): string {
		switch ( $this->get_software_type() ) {
			case self::T_WORDPRESS:
				return __( 'WordPress', 'better-wp-security' );
			case self::T_PLUGIN:
				return __( 'Plugin', 'better-wp-security' );
			case self::T_THEME:
				return __( 'Theme', 'better-wp-security' );
			default:
				return '';
		}
	}

	public function get_software_slug(): string {
		return $this->software_slug;
	}

	public function get_details(): array {
		return $this->details;
	}

	public function get_first_seen(): ?\DateTimeInterface {
		return $this->first_seen;
	}

	public function get_last_seen(): ?\DateTimeInterface {
		return $this->last_seen;
	}

	public function get_resolved_at(): ?\DateTimeInterface {
		return $this->resolved_at;
	}

	public function get_resolved_by(): ?\WP_User {
		return $this->resolved_by;
	}

	public function get_resolution(): string {
		return $this->resolution;
	}

	public function get_resolution_label(): string {
		switch ( $this->get_resolution() ) {
			case self::R_PATCHED:
				return __( 'Mitigated', 'better-wp-security' );
			case self::R_AUTO_UPDATED:
				return __( 'Auto-Updated', 'better-wp-security' );
			case self::R_UPDATED:
				return __( 'Updated', 'better-wp-security' );
			case self::R_DEACTIVATED:
				if ( $this->get_first_seen() == $this->get_resolved_at() ) {
					return __( 'Inactive', 'better-wp-security' );
				}

				return __( 'Deactivated', 'better-wp-security' );
			case self::R_DELETED:
				return __( 'Deleted', 'better-wp-security' );
			case self::R_MUTED:
				return __( 'Muted', 'better-wp-security' );
			default:
				return __( 'Unresolved', 'better-wp-security' );
		}
	}

	public function get_resolution_description(): string {
		if ( ! $this->is_resolved() ) {
			return '';
		}

		$date = wp_date(
			get_option( 'date_format' ),
			$this->get_resolved_at()->getTimestamp()
		);
		$time = wp_date(
			get_option( 'time_format' ),
			$this->get_resolved_at()->getTimestamp()
		);

		switch ( $this->get_resolution() ) {
			case self::R_PATCHED:
				return __( 'A virtual patch was automatically applied to mitigate this vulnerability.', 'better-wp-security' );
			case self::R_MUTED:
				if ( $this->get_resolved_by() ) {
					$by = $this->get_resolved_by()->display_name;

					return sprintf(
					/* translators: 1. Name, 2. Formatted date, 3. Formatted time. */
						__( 'This vulnerability was muted by %1$s on %2$s at %3$s.', 'better-wp-security' ),
						$by,
						$date,
						$time
					);
				} else {
					/* translators: 1. Formatted date, 2. Formatted time. */
					return sprintf( __( 'This vulnerability was muted on %1$s at %2$s.', 'better-wp-security' ), $date, $time );
				}
			case self::R_AUTO_UPDATED:
				switch ( $this->get_software_type() ) {
					case self::T_PLUGIN:
						return sprintf(
						/* translators: 1. Formatted date, 2. Formatted time. */
							__( 'The plugin was automatically updated on %1$s at %2$s.', 'better-wp-security' ),
							$date,
							$time
						);
					case self::T_THEME:
						return sprintf(
						/* translators: 1. Formatted date, 2. Formatted time. */
							__( 'The theme was automatically updated on %1$s at %2$s.', 'better-wp-security' ),
							$date,
							$time
						);
					case self::T_WORDPRESS:
						return sprintf(
						/* translators: 1. Formatted date, 2. Formatted time. */
							__( 'WordPress Core was automatically updated on %1$s at %2$s.', 'better-wp-security' ),
							$date,
							$time
						);
					default:
						return '';
				}
			case self::R_UPDATED:
				if ( $this->get_resolved_by() ) {
					$by = $this->get_resolved_by()->display_name;

					switch ( $this->get_software_type() ) {
						case self::T_PLUGIN:
							return sprintf(
							/* translators: 1. Name, 2. Formatted date, 3. Formatted time. */
								__( 'The plugin was updated by %1$s on %2$s at %3$s.', 'better-wp-security' ),
								$by,
								$date,
								$time
							);
						case self::T_THEME:
							return sprintf(
							/* translators: 1. Name, 2. Formatted date, 3. Formatted time. */
								__( 'The theme was updated by %1$s on %2$s at %3$s.', 'better-wp-security' ),
								$by,
								$date,
								$time
							);
						case self::T_WORDPRESS:
							return sprintf(
							/* translators: 1. Name, 2. Formatted date, 3. Formatted time. */
								__( 'WordPress Core was updated by %1$s on %2$s at %3$s.', 'better-wp-security' ),
								$by,
								$date,
								$time
							);
						default:
							return '';
					}
				} else {
					switch ( $this->get_software_type() ) {
						case self::T_PLUGIN:
							return sprintf(
							/* translators: 1. Formatted date, 2. Formatted time. */
								__( 'The plugin was updated on %1$s at %2$s.', 'better-wp-security' ),
								$date,
								$time
							);
						case self::T_THEME:
							return sprintf(
							/* translators: 1. Formatted date, 2. Formatted time. */
								__( 'The theme was updated on %s1$ at %2$s.', 'better-wp-security' ),
								$date,
								$time
							);
						case self::T_WORDPRESS:
							return sprintf(
							/* translators: 1. Formatted date, 2. Formatted time. */
								__( 'WordPress Core was updated on %1$s at %2$s.', 'better-wp-security' ),
								$date,
								$time
							);
						default:
							return '';
					}
				}
			case self::R_DEACTIVATED:
				if ( $this->get_first_seen() == $this->get_resolved_at() ) {
					switch ( $this->get_software_type() ) {
						case self::T_PLUGIN:
							return sprintf(
							/* translators: 1. Formatted date, 2. Formatted time */
								__( 'The plugin was inactive when scanned on %1$s at %2$s.', 'better-wp-security' ),
								$date,
								$time
							);
						case self::T_THEME:
							return sprintf(
							/* translators: 1. Formatted date, 2. Formatted time */
								__( 'The theme was inactive when scanned on %1$s at %2$s.', 'better-wp-security' ),
								$date,
								$time
							);
						default:
							return '';
					}
				} elseif ( $this->get_resolved_by() ) {
					$by = $this->get_resolved_by()->display_name;

					switch ( $this->get_software_type() ) {
						case self::T_PLUGIN:
							return sprintf(
							/* translators: 1. Name, 2. Formatted date, 3. Formatted time */
								__( 'The plugin was deactivated by %1$s on %2$s at %3$s.', 'better-wp-security' ),
								$by,
								$date,
								$time
							);
						case self::T_THEME:
							return sprintf(
							/* translators: 1. Name, 2. Formatted date, 3. Formatted time */
								__( 'The theme was deactivated by %1$s on %2$s at %3$s.', 'better-wp-security' ),
								$by,
								$date,
								$time
							);
						default:
							return '';
					}
				} else {
					switch ( $this->get_software_type() ) {
						case self::T_PLUGIN:
							return sprintf(
							/* translators: 1. Formatted date, 2. Formatted time */
								__( 'The plugin was deactivated on %1$s at %2$s.', 'better-wp-security' ),
								$date,
								$time
							);
						case self::T_THEME:
							return sprintf(
							/* translators: 1. Formatted date, 2. Formatted time */
								__( 'The theme was deactivated on %1$s at %2$s.', 'better-wp-security' ),
								$date,
								$time
							);
						default:
							return '';
					}
				}
			case self::R_DELETED:
				if ( $this->get_resolved_by() ) {
					$by = $this->get_resolved_by()->display_name;

					switch ( $this->get_software_type() ) {
						case self::T_PLUGIN:
							return sprintf(
							/* translators: 1. Name, 2. Formatted date, 3. Formatted time. */
								__( 'The plugin was deleted by %1$s on %2$s at %3$s.', 'better-wp-security' ),
								$by,
								$date,
								$time
							);
						case self::T_THEME:
							return sprintf(
							/* translators: 1. Name, 2. Formatted date, 3. Formatted time. */
								__( 'The theme was deleted by %1$s on %2$s at %3$s.', 'better-wp-security' ),
								$by,
								$date,
								$time
							);
						default:
							return '';
					}
				} else {
					switch ( $this->get_software_type() ) {
						case self::T_PLUGIN:
							return sprintf(
							/* translators: 1. Formatted date, 2. Formatted time. */
								__( 'The plugin was deleted on %1$s at %2$s.', 'better-wp-security' ),
								$date,
								$time
							);
						case self::T_THEME:
							return sprintf(
							/* translators: 1. Formatted date, 2. Formatted time. */
								__( 'The theme was deleted on %s1$ at %2$s.', 'better-wp-security' ),
								$date,
								$time
							);
						default:
							return '';
					}
				}
			default:
				return '';
		}
	}

	public function is_resolved(): bool {
		return $this->get_resolution() !== '';
	}

	public function is_patched(): bool {
		return $this->get_resolution() === self::R_PATCHED;
	}

	public function is_auto_updated(): bool {
		return $this->get_resolution() === self::R_AUTO_UPDATED;
	}

	public function is_manually_updated(): bool {
		return $this->get_resolution() === self::R_UPDATED;
	}

	public function is_updated(): bool {
		return $this->get_resolution() === self::R_UPDATED || $this->get_resolution() === self::R_AUTO_UPDATED;
	}

	public function is_deactivated(): bool {
		return $this->get_resolution() === self::R_DEACTIVATED;
	}

	public function is_deleted(): bool {
		return $this->get_resolution() === self::R_DELETED;
	}

	public function is_muted(): bool {
		return $this->get_resolution() === self::R_MUTED;
	}

	/**
	 * Checks if the vulnerable software is installed.
	 *
	 * @return bool
	 */
	public function is_software_installed(): bool {
		switch ( $this->get_software_type() ) {
			case self::T_WORDPRESS:
				return true;
			case self::T_PLUGIN:
				return (bool) $this->get_plugin_file();
			case self::T_THEME:
				return wp_get_theme( $this->get_software_slug() )->exists();
			default:
				return false;
		}
	}

	/**
	 * Checks if the vulnerable software is installed.
	 *
	 * @return bool
	 */
	public function is_software_active(): bool {
		switch ( $this->get_software_type() ) {
			case self::T_WORDPRESS:
				return true;
			case self::T_PLUGIN:
				$file = $this->get_plugin_file();

				return $file && is_plugin_active( $file );
			case self::T_THEME:
				return get_stylesheet() === $this->get_software_slug()
				       || get_template() === $this->get_software_slug();
			default:
				return false;
		}
	}

	/**
	 * Gets the main plugin file for the vulnerability.
	 *
	 * @return string|null
	 */
	public function get_plugin_file(): ?string {
		if ( $this->get_software_type() !== self::T_PLUGIN ) {
			return null;
		}

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

		foreach ( get_plugins() as $file => $plugin ) {
			if ( str_starts_with( $file, $this->get_software_slug() . '/' ) ) {
				return $file;
			}
		}

		return null;
	}

	public function get_software_label(): string {
		switch ( $this->get_software_type() ) {
			case self::T_PLUGIN:
				if ( ! $file = $this->get_plugin_file() ) {
					return '';
				}

				$plugins = get_plugins();

				return $plugins[ $file ]['Name'] ?? '';
			case self::T_THEME:
				$theme = wp_get_theme( $this->get_software_slug() );

				return $theme->display( 'Name', false ) ?: '';
			default:
				return '';
		}
	}

	public function as_issue(): Vulnerability_Issue {
		return new Vulnerability_Issue(
			[
				'type'     => $this->get_software_type(),
				'software' => [
					'slug'           => $this->get_software_slug(),
					'label'          => '',
					'latest_version' => null,
				],
				'issues'   => [ $this->get_details() ],
			],
			$this->get_id(),
			''
		);
	}
}
