<?php

class Eurostocks_Anti_Spam_Settings {

	const OPTION_NAME = 'eurostocks-anti-spam';
	const DEFAULT_AUTOBAN = 100;
	const DEFAULT_EMAIL_LIMIT = 0;
	const DEFAULT_RATIO_MIN_DETAIL_REQUESTS = 20;
	const DEFAULT_RATIO_PERCENT_LIMIT = 150;
	const DEFAULT_PAGE_TYPE_LIMITS = array(
		'vehicle_list' => array(
			'autoban'                  => 50,
			'email_verification_limit' => 5,
		),
		'vehicle_detail' => array(
			'autoban'                  => 50,
			'email_verification_limit' => 3,
		),
		'vehicle_pdf' => array(
			'autoban'                  => 20,
			'email_verification_limit' => 2,
		),
	);
	const IP_METADATA_KEY = 'ip_metadata';

	/**
	 * Retrieve the stored anti-spam options.
	 *
	 * @return array
	 */
	public static function get_options() {
		$options = get_option(self::OPTION_NAME);
		if (!is_array($options)) {
			$options = array();
		}

		if (!isset($options[self::IP_METADATA_KEY]) || !is_array($options[self::IP_METADATA_KEY])) {
			$options[self::IP_METADATA_KEY] = array();
		}

		if (!isset($options['request_ratio_min_detail']) || !is_numeric($options['request_ratio_min_detail'])) {
			$options['request_ratio_min_detail'] = self::DEFAULT_RATIO_MIN_DETAIL_REQUESTS;
		} else {
			$options['request_ratio_min_detail'] = max(0, (int) $options['request_ratio_min_detail']);
		}

		if (!isset($options['request_ratio_percent']) || !is_numeric($options['request_ratio_percent'])) {
			$options['request_ratio_percent'] = self::DEFAULT_RATIO_PERCENT_LIMIT;
		} else {
			$options['request_ratio_percent'] = max(0, (int) $options['request_ratio_percent']);
		}

		return $options;
	}

	/**
	 * Default list of known page types.
	 *
	 * @return array
	 */
	public static function get_default_page_types() {
		return array(
			'vehicle_list'   => __('Vehicle list / search pages', 'eurostocks'),
			'vehicle_detail' => __('Vehicle detail pages', 'eurostocks'),
			'vehicle_pdf'    => __('Vehicle PDF downloads', 'eurostocks'),
		);
	}

	/**
	 * Default texts for the anti-spam workflow.
	 *
	 * @return array
	 */
	public static function get_default_texts() {
		return array(
			'verification_heading'          => __('Email verification required', 'eurostocks'),
			'verification_description'      => __('We have detected unusual browsing behavior. To continue accessing our vehicle listings, please verify your email address.', 'eurostocks'),
			'verification_email_label'      => __('Email address', 'eurostocks'),
			'verification_button_text'      => __('Send verification email', 'eurostocks'),
			'verification_helper_text'      => __('We will send you a link to verify your email address.', 'eurostocks'),
			'verification_notice_success'   => __('Your email has been verified. You can continue browsing.', 'eurostocks'),
			'verification_notice_failed'    => __('The verification link is invalid or expired. Please request a new email.', 'eurostocks'),
			'limit_verify_message'          => __('Please verify your email to continue.', 'eurostocks'),
			'limit_block_message'           => __('Too many requests', 'eurostocks'),
			'limit_generic_message'         => __('Request limit reached. Please try again later.', 'eurostocks'),
			'pdf_limit_verify_message'      => __('Request limit reached. Please verify your email via the website before downloading the PDF.', 'eurostocks'),
			'pdf_limit_block_message'       => __('Request limit reached. Please try again later.', 'eurostocks'),
			'ajax_success_message'          => __('We sent you a verification link. Please check your email.', 'eurostocks'),
			'ajax_invalid_email_message'    => __('Please enter a valid email address.', 'eurostocks'),
			'ajax_disposable_email_message' => __('Disposable email addresses are not allowed. Please use another email address.', 'eurostocks'),
			'ajax_missing_ip_message'       => __('We could not determine your IP address. Please try again later.', 'eurostocks'),
			'ajax_send_error_message'       => __('We could not send the verification email. Please try again later.', 'eurostocks'),
			'ajax_attempt_limit_message'    => __('You have reached the maximum number of verification requests for today. Please try again in 24 hours.', 'eurostocks'),
			'ajax_generic_error_message'    => __('Unable to process your request.', 'eurostocks'),
			'form_security_error_message'   => __('Security check failed.', 'eurostocks'),
			'email_subject'                 => __('Verify your email address', 'eurostocks'),
			'email_body'                    => __("Hi,\n\nWe've received a high number of requests from your connection. Please verify your email address by clicking the link below:\n\n{verification_link}\n\nThis link will expire in 12 hours.\n\nThank you,\n{site_name}", 'eurostocks'),
		);
	}

	/**
	 * Resolve a limit for a given page type.
	 *
	 * @param string $page_type
	 * @param string $key
	 * @param array|null $options
	 * @return int
	 */
	public static function get_page_type_limit($page_type, $key, $options = null) {
		$options = is_array($options) ? $options : self::get_options();
		$page_type = self::normalize_page_type($page_type);

		if (isset($options['per_page_limits'][$page_type][$key])) {
			return max(0, (int) $options['per_page_limits'][$page_type][$key]);
		}

		if ('email_verification_limit' !== $key && isset($options[$key])) {
			return max(0, (int) $options[$key]);
		}

		$default = self::get_default_page_limit($page_type, $key);
		if (null !== $default) {
			return $default;
		}

		if ('autoban' === $key) {
			return self::DEFAULT_AUTOBAN;
		}

		if ('email_verification_limit' === $key) {
			return self::DEFAULT_EMAIL_LIMIT;
		}
		return 0;
	}

	/**
	 * Return a default per-page limit for the given type and key.
	 *
	 * @param string $page_type
	 * @param string $key
	 * @return int|null
	 */
	private static function get_default_page_limit($page_type, $key) {
		if (!isset(self::DEFAULT_PAGE_TYPE_LIMITS[$page_type])) {
			return null;
		}

		if (!isset(self::DEFAULT_PAGE_TYPE_LIMITS[$page_type][$key])) {
			return null;
		}

		return max(0, (int) self::DEFAULT_PAGE_TYPE_LIMITS[$page_type][$key]);
	}

	/**
	 * Return the minimum number of detail requests before the ratio rule applies.
	 *
	 * @param array|null $options
	 * @return int
	 */
	public static function get_ratio_min_detail_requests($options = null) {
		$options = is_array($options) ? $options : self::get_options();
		$threshold = isset($options['request_ratio_min_detail']) ? (int) $options['request_ratio_min_detail'] : self::DEFAULT_RATIO_MIN_DETAIL_REQUESTS;

		return max(0, $threshold);
	}

	/**
	 * Return the ratio percentage limit (detail/search * 100) before blocking.
	 *
	 * @param array|null $options
	 * @return int
	 */
	public static function get_ratio_percent_limit($options = null) {
		$options = is_array($options) ? $options : self::get_options();
		$limit = isset($options['request_ratio_percent']) ? (int) $options['request_ratio_percent'] : self::DEFAULT_RATIO_PERCENT_LIMIT;

		return max(0, $limit);
	}

	/**
	 * Calculate the detail/search request ratio as a percentage.
	 *
	 * Returns (detail_requests / search_requests) * 100. When search_requests is 0,
	 * returns detail_requests * 100 to represent an infinitely high ratio.
	 *
	 * @param int $detail_requests
	 * @param int $search_requests
	 * @return float
	 */
	public static function calculate_detail_to_search_ratio($detail_requests, $search_requests) {
		$detail_count = max(0, (int) $detail_requests);
		$search_count = max(0, (int) $search_requests);

		if (0 === $detail_count) {
			return 0.0;
		}

		if (0 === $search_count) {
			return (float) ($detail_count * 100);
		}

		return (float) (($detail_count / $search_count) * 100);
	}

	/**
	 * Return a single text value with fallback to defaults.
	 *
	 * @param string $key
	 * @param array|null $options
	 * @return string
	 */
	public static function get_text($key, $options = null) {
		$options = is_array($options) ? $options : self::get_options();
		$defaults = self::get_default_texts();

		if (isset($options['texts'][$key]) && $options['texts'][$key] !== '') {
			return $options['texts'][$key];
		}

		return isset($defaults[$key]) ? $defaults[$key] : '';
	}

	/**
	 * Return the merged set of saved texts and defaults.
	 *
	 * @param array|null $options
	 * @return array
	 */
	public static function get_texts($options = null) {
		$options = is_array($options) ? $options : self::get_options();
		$defaults = self::get_default_texts();
		$saved = isset($options['texts']) && is_array($options['texts']) ? $options['texts'] : array();

		return array_merge($defaults, array_filter($saved, static function ($value) {
			return $value !== '';
		}));
	}

	/**
	 * Return a normalized list of blocked IP addresses.
	 *
	 * @param array|null $options
	 * @return array
	 */
	public static function get_blocked_ips($options = null) {
		$options = is_array($options) ? $options : self::get_options();
		$iplist = isset($options['iplist']) ? $options['iplist'] : '';

		if ('' === trim((string) $iplist)) {
			return array();
		}

		$raw_entries = preg_split('/[\r\n,]+/', $iplist);

		$ips = array();
		foreach ($raw_entries as $entry) {
			$clean_ip = trim($entry);
			if ('' === $clean_ip) {
				continue;
			}
			$ips[$clean_ip] = true;
		}

		return array_keys($ips);
	}

	/**
	 * Retrieve stored IP metadata entries.
	 *
	 * @param array|null $options
	 * @return array
	 */
	public static function get_ip_metadata($options = null) {
		$options = is_array($options) ? $options : self::get_options();
		return isset($options[self::IP_METADATA_KEY]) && is_array($options[self::IP_METADATA_KEY]) ? $options[self::IP_METADATA_KEY] : array();
	}

	/**
	 * Retrieve metadata for a specific IP.
	 *
	 * @param string $ip
	 * @param array|null $options
	 * @return array
	 */
	public static function get_ip_metadata_for($ip, $options = null) {
		$ip = trim((string) $ip);
		if ('' === $ip) {
			return array();
		}

		$metadata = self::get_ip_metadata($options);
		return isset($metadata[$ip]) && is_array($metadata[$ip]) ? $metadata[$ip] : array();
	}

	/**
	 * Return block rows enriched with metadata for the admin UI.
	 *
	 * @param array|null $options
	 * @return array
	 */
	public static function get_blocked_ip_rows($options = null) {
		$options = is_array($options) ? $options : self::get_options();
		$blocked_ips = self::get_blocked_ips($options);
		$ip_metadata = self::get_ip_metadata($options);

		$rows = array();
		foreach ($blocked_ips as $ip) {
			$meta = isset($ip_metadata[$ip]) && is_array($ip_metadata[$ip]) ? $ip_metadata[$ip] : array();
			$reason_key = isset($meta['reason_key']) ? $meta['reason_key'] : '';
			$rows[] = array(
				'ip'           => $ip,
				'reason_key'   => $reason_key,
				'reason_label' => self::get_reason_label($reason_key),
				'page_type'    => isset($meta['page_type']) ? $meta['page_type'] : '',
				'status'       => isset($meta['status']) ? $meta['status'] : 'blocked',
				'updated_at'   => isset($meta['updated_at']) ? (int) $meta['updated_at'] : 0,
				'metadata'     => $meta,
			);
		}

		return $rows;
	}

	/**
	 * Map a stored reason key to a label.
	 *
	 * @param string $reason_key
	 * @return string
	 */
	public static function get_reason_label($reason_key) {
		$key = sanitize_key($reason_key);
		$labels = array(
			'manual'             => __('Manual block', 'eurostocks'),
			'search_limit'       => __('Search limit reached', 'eurostocks'),
			'detail_limit'       => __('Detail limit reached', 'eurostocks'),
			'pdf_limit'          => __('PDF limit reached', 'eurostocks'),
			'ratio_limit'        => __('Ratio limit reached', 'eurostocks'),
			'country_not_allowed'=> __('Country not allowed', 'eurostocks'),
			'country_unknown'    => __('Unknown country of origin', 'eurostocks'),
			'ip_blocklist'       => __('Blocklist match', 'eurostocks'),
			'too_many_requests'  => __('Too many requests', 'eurostocks'),
			'limit_reached'      => __('Limit reached', 'eurostocks'),
			'no_countries_allowed'   => __('No countries configured', 'eurostocks'),
			'no_countries_configured'=> __('No countries configured', 'eurostocks'),
			'verification_required'  => __('Verification required', 'eurostocks'),
			'unknown'            => __('Unknown reason', 'eurostocks'),
		);

		if (isset($labels[$key])) {
			return $labels[$key];
		}

		return $labels['unknown'];
	}

	/**
	 * Determine a reason key for a given page type.
	 *
	 * @param string $page_type
	 * @param string $default
	 * @return string
	 */
	public static function get_reason_key_for_page_type($page_type, $default = '') {
		$normalized = self::normalize_page_type($page_type);
		switch ($normalized) {
			case 'vehicle_list':
				return 'search_limit';
			case 'vehicle_detail':
				return 'detail_limit';
			case 'vehicle_pdf':
				return 'pdf_limit';
			case 'ratio':
			case 'ratio_limit':
			case 'request_ratio':
				return 'ratio_limit';
			default:
				return '' !== $default ? sanitize_key($default) : 'limit_reached';
		}
	}

	/**
	 * Store metadata for an IP event.
	 *
	 * @param string $ip
	 * @param array  $args
	 * @return bool
	 */
	public static function record_ip_event($ip, $args = array()) {
		$ip = trim((string) $ip);
		if ('' === $ip) {
			return false;
		}

		$options = self::get_options();
		$metadata = self::get_ip_metadata($options);
		$current_entry = isset($metadata[$ip]) ? $metadata[$ip] : array();

		$new_entry = self::prepare_ip_metadata_entry($args, $current_entry);

		$has_changes = empty($current_entry);
		if (!$has_changes) {
			$compare_keys = array('reason_key', 'page_type', 'status');
			foreach ($compare_keys as $key) {
				$old_value = isset($current_entry[$key]) ? $current_entry[$key] : '';
				$new_value = isset($new_entry[$key]) ? $new_entry[$key] : '';
				if ($old_value !== $new_value) {
					$has_changes = true;
					break;
				}
			}

			if (!$has_changes) {
				$old_details = isset($current_entry['details']) && is_array($current_entry['details']) ? $current_entry['details'] : array();
				$new_details = isset($new_entry['details']) && is_array($new_entry['details']) ? $new_entry['details'] : array();
				if ($old_details !== $new_details) {
					$has_changes = true;
				}
			}
		}

		if (!$has_changes) {
			return false;
		}

		if (!isset($args['updated_at'])) {
			$new_entry['updated_at'] = time();
		}

		$metadata[$ip] = $new_entry;
		$options[self::IP_METADATA_KEY] = $metadata;

		update_option(self::OPTION_NAME, $options);
		return true;
	}

	/**
	 * Remove metadata for a specific IP.
	 *
	 * @param string $ip
	 * @return void
	 */
	public static function remove_ip_metadata($ip) {
		$ip = trim((string) $ip);
		if ('' === $ip) {
			return;
		}

		$options = self::get_options();
		if (!isset($options[self::IP_METADATA_KEY][$ip])) {
			return;
		}

		unset($options[self::IP_METADATA_KEY][$ip]);
		update_option(self::OPTION_NAME, $options);
	}

	/**
	 * Prepare an IP metadata entry without persisting it.
	 *
	 * @param array $args
	 * @param array $existing_entry
	 * @return array
	 */
	public static function prepare_ip_metadata_entry($args = array(), $existing_entry = array()) {
		$entry = is_array($existing_entry) ? $existing_entry : array();
		$reason_key = isset($entry['reason_key']) ? sanitize_key($entry['reason_key']) : '';
		$page_type = isset($entry['page_type']) ? sanitize_key($entry['page_type']) : '';
		$status = isset($entry['status']) ? sanitize_key($entry['status']) : '';
		$details = isset($entry['details']) && is_array($entry['details']) ? $entry['details'] : array();

		if (isset($args['reason_key'])) {
			$reason_key = sanitize_key($args['reason_key']);
		}
		if (isset($args['page_type'])) {
			$page_type = self::normalize_page_type($args['page_type']);
		}
		if (isset($args['status'])) {
			$status = sanitize_key($args['status']);
		}
		if (isset($args['details'])) {
			$details = self::sanitize_ip_metadata_details($args['details']);
		}

		$timestamp = isset($args['updated_at']) ? (int) $args['updated_at'] : (isset($entry['updated_at']) ? (int) $entry['updated_at'] : time());

		$entry = array(
			'reason_key' => $reason_key,
			'page_type'  => $page_type,
			'status'     => $status,
			'details'    => $details,
			'updated_at' => $timestamp,
		);

		return $entry;
	}

	/**
	 * Sanitize metadata detail payloads.
	 *
	 * @param mixed $details
	 * @return array
	 */
	protected static function sanitize_ip_metadata_details($details) {
		if (!is_array($details)) {
			return array();
		}

		$clean = array();
		foreach ($details as $key => $value) {
			$clean_key = sanitize_key((string) $key);

			if (is_numeric($value)) {
				$clean[$clean_key] = (int) $value;
			} elseif (is_scalar($value)) {
				$clean[$clean_key] = sanitize_text_field((string) $value);
			}
		}

		return $clean;
	}

	/**
	 * Normalize a page type slug.
	 *
	 * @param string $page_type
	 * @return string
	 */
	protected static function normalize_page_type($page_type) {
		$page_type = is_string($page_type) ? sanitize_key($page_type) : '';
		return '' === $page_type ? 'general' : $page_type;
	}
}
