<?php
/**
 * Security functions for Comhit Custom Price List plugin
 * 
 * @package Comhit_Custom_Price_List
 */

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

/**
 * Check rate limiting for form submissions
 * 
 * @param string $action Action identifier (e.g., 'inquiry', 'unlock', 'request_access')
 * @param int    $limit  Maximum number of attempts allowed
 * @param int    $period Time period in seconds
 * @return bool|WP_Error True if allowed, WP_Error if rate limited
 */
function chcpl_check_rate_limit( $action, $limit = 5, $period = 300 ) {
	$ip = chcpl_get_client_ip();
	$key = 'chcpl_rate_limit_' . $action . '_' . md5( $ip );
	$attempts = get_transient( $key );
	
	if ( false === $attempts ) {
		$attempts = 0;
	}
	
	if ( $attempts >= $limit ) {
		return new WP_Error( 
			'rate_limit_exceeded', 
			__( 'Too many attempts. Please try again in a few minutes.', 'ad-pro-price-list' ),
			array( 'status' => 429 )
		);
	}
	
	$attempts++;
	set_transient( $key, $attempts, $period );
	
	return true;
}

/**
 * Get client IP address (handles proxies)
 * 
 * @return string Client IP address
 */
function chcpl_get_client_ip() {
	$ip_keys = array(
		'HTTP_CF_CONNECTING_IP', // Cloudflare
		'HTTP_X_REAL_IP',        // Nginx proxy
		'HTTP_X_FORWARDED_FOR',  // Standard proxy header
		'REMOTE_ADDR',           // Fallback
	);
	
	foreach ( $ip_keys as $key ) {
		if ( ! empty( $_SERVER[ $key ] ) ) {
			$ip = sanitize_text_field( wp_unslash( $_SERVER[ $key ] ) );
			// Handle comma-separated IPs (X-Forwarded-For can contain multiple)
			if ( strpos( $ip, ',' ) !== false ) {
				$ips = explode( ',', $ip );
				$ip = trim( $ips[0] );
			}
			// Validate IP
			if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
				return $ip;
			}
		}
	}
	
	// Fallback to REMOTE_ADDR even if it's private
	return isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '0.0.0.0';
}

/**
 * Validate input length to prevent DoS attacks
 * 
 * @param string $value Input value
 * @param int    $max_length Maximum allowed length
 * @param string $field_name Field name for error message
 * @return bool|WP_Error True if valid, WP_Error if invalid
 */
function chcpl_validate_length( $value, $max_length, $field_name = '' ) {
	if ( strlen( $value ) > $max_length ) {
		return new WP_Error( 
			'invalid_length', 
			sprintf( 
				/* translators: 1: field name, 2: maximum length */
				__( 'The field "%1$s" is too long (maximum %2$d characters).', 'ad-pro-price-list' ),
				$field_name ? $field_name : __( 'Field', 'ad-pro-price-list' ),
				$max_length
			)
		);
	}
	return true;
}

/**
 * Validate and sanitize form input
 * 
 * @param array $fields Array of field definitions with 'name', 'type', 'required', 'max_length'
 * @return array|WP_Error Sanitized data or WP_Error
 */
function chcpl_validate_form_input( $fields ) {
	$sanitized = array();
	$errors = array();
	
	foreach ( $fields as $field ) {
		$name = $field['name'];
		$type = isset( $field['type'] ) ? $field['type'] : 'text';
		$required = isset( $field['required'] ) ? $field['required'] : false;
		$max_length = isset( $field['max_length'] ) ? $field['max_length'] : 1000;
		
		// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$value = isset( $_POST[ $name ] ) ? wp_unslash( $_POST[ $name ] ) : '';
		
		// Check required fields
		if ( $required && empty( $value ) ) {
			$errors[] = sprintf( 
				/* translators: %s: field name */
				__( 'The "%s" field is required.', 'ad-pro-price-list' ),
				isset( $field['label'] ) ? $field['label'] : $name
			);
			continue;
		}
		
		// Skip empty optional fields
		if ( empty( $value ) && ! $required ) {
			$sanitized[ $name ] = '';
			continue;
		}
		
		// Validate length
		$length_check = chcpl_validate_length( $value, $max_length, isset( $field['label'] ) ? $field['label'] : $name );
		if ( is_wp_error( $length_check ) ) {
			$errors[] = $length_check->get_error_message();
			continue;
		}
		
		// Sanitize based on type
		switch ( $type ) {
			case 'email':
				$value = sanitize_email( $value );
				if ( ! is_email( $value ) ) {
					$errors[] = sprintf( 
						/* translators: %s: field name */
						__( 'The email "%s" is not valid.', 'ad-pro-price-list' ),
						isset( $field['label'] ) ? $field['label'] : $name
					);
					continue 2;
				}
				break;
			case 'int':
				$value = (int) $value;
				if ( isset( $field['min'] ) && $value < $field['min'] ) {
					$errors[] = sprintf( 
						/* translators: 1: field name, 2: minimum value */
						__( 'The "%1$s" field must be %2$d or greater.', 'ad-pro-price-list' ),
						isset( $field['label'] ) ? $field['label'] : $name,
						$field['min']
					);
					continue 2;
				}
				if ( isset( $field['max'] ) && $value > $field['max'] ) {
					$errors[] = sprintf( 
						/* translators: 1: field name, 2: maximum value */
						__( 'The "%1$s" field must be %2$d or less.', 'ad-pro-price-list' ),
						isset( $field['label'] ) ? $field['label'] : $name,
						$field['max']
					);
					continue 2;
				}
				break;
			case 'textarea':
				$value = wp_kses_post( $value );
				break;
			case 'text':
			default:
				$value = sanitize_text_field( $value );
				break;
		}
		
		$sanitized[ $name ] = $value;
	}
	
	if ( ! empty( $errors ) ) {
		return new WP_Error( 'validation_error', implode( ' ', $errors ) );
	}
	
	return $sanitized;
}

/**
 * Check honeypot field to prevent bot submissions
 * 
 * @return bool|WP_Error True if valid (honeypot empty), WP_Error if bot detected
 */
function chcpl_check_honeypot() {
	// Check multiple honeypot field names to catch different bots
	$honeypot_fields = array( 'website', 'url', 'homepage', 'chcpl_honeypot' );
	
	foreach ( $honeypot_fields as $field ) {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing
		if ( isset( $_POST[ $field ] ) && ! empty( $_POST[ $field ] ) ) {
			// Bot detected - log but don't reveal
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( 'CHCPL: Bot detected via honeypot field: ' . $field );
			return new WP_Error( 
				'bot_detected', 
				__( 'Invalid submission.', 'ad-pro-price-list' )
			);
		}
	}
	
	return true;
}

/**
 * Validate file upload for import
 * 
 * @param array $file $_FILES array entry
 * @param int   $max_size Maximum file size in bytes
 * @return bool|WP_Error True if valid, WP_Error if invalid
 */
function chcpl_validate_uploaded_file( $file, $max_size = 5242880 ) { // 5MB default
	if ( ! isset( $file['tmp_name'] ) || empty( $file['tmp_name'] ) ) {
		return new WP_Error( 'no_file', __( 'No file uploaded.', 'ad-pro-price-list' ) );
	}
	
	// Check file size
	if ( $file['size'] > $max_size ) {
		return new WP_Error( 
			'file_too_large', 
			sprintf( 
				/* translators: %s: maximum file size */
				__( 'File too large. Maximum size: %s.', 'ad-pro-price-list' ),
				size_format( $max_size )
			)
		);
	}
	
	// Check file extension
	$ext = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) );
	$allowed_extensions = array( 'json' );
	if ( ! in_array( $ext, $allowed_extensions, true ) ) {
		return new WP_Error( 
			'invalid_extension', 
			__( 'Invalid file type. Only JSON files are accepted.', 'ad-pro-price-list' )
		);
	}
	
	// Check MIME type
	$finfo = finfo_open( FILEINFO_MIME_TYPE );
	$mime_type = finfo_file( $finfo, $file['tmp_name'] );
	finfo_close( $finfo );
	
	$allowed_mimes = array( 'application/json', 'text/json', 'text/plain' );
	if ( ! in_array( $mime_type, $allowed_mimes, true ) ) {
		return new WP_Error( 
			'invalid_mime', 
			__( 'Invalid file MIME type.', 'ad-pro-price-list' )
		);
	}
	
	// Validate JSON structure
	$content = file_get_contents( $file['tmp_name'] );
	if ( false === $content ) {
		return new WP_Error( 'read_error', __( 'Could not read the file.', 'ad-pro-price-list' ) );
	}
	
	$json = json_decode( $content, true );
	if ( json_last_error() !== JSON_ERROR_NONE ) {
		return new WP_Error( 
			'invalid_json', 
			__( 'Invalid JSON file.', 'ad-pro-price-list' )
		);
	}
	
	// Check if it's a valid export file
	if ( ! is_array( $json ) || ! isset( $json['plugin'] ) || $json['plugin'] !== 'ad-pro-price-list' ) {
		return new WP_Error( 
			'invalid_export', 
			__( 'This file is not a valid export from this plugin.', 'ad-pro-price-list' )
		);
	}
	
	return true;
}

/**
 * Check if the current site has access to a given plan tier (or higher).
 * Returns true for any plan at or above $plan: solo ≤ pro ≤ business.
 * Always returns true in WP_DEBUG mode so every feature is testable locally.
 *
 * @param string $plan  'solo' | 'pro' | 'business'
 * @return bool
 */
function chcpl_is_plan( $plan ) {
	return function_exists( 'appl_fs' )
		&& appl_fs()->can_use_premium_code__premium_only()
		&& appl_fs()->is_plan( $plan );
}

function chcpl_is_free() {
	return ! ( function_exists( 'appl_fs' ) && appl_fs()->can_use_premium_code__premium_only() );
}

/**
 * Sanitize category order input
 *
 * @param mixed $value Input value
 * @return int Sanitized integer (0-999999)
 */
function chcpl_sanitize_category_order( $value ) {
	$value = (int) $value;
	// Limit to reasonable range
	if ( $value < 0 ) {
		$value = 0;
	}
	if ( $value > 999999 ) {
		$value = 999999;
	}
	return $value;
}

/**
 * Validate SIREN number format (French business ID)
 * 
 * @param string $siren SIREN number
 * @return bool|WP_Error True if valid, WP_Error if invalid
 */
function chcpl_validate_siren( $siren ) {
	// Remove spaces and non-numeric characters
	$siren = preg_replace( '/[^0-9]/', '', $siren );
	
	// SIREN should be 9 digits
	if ( strlen( $siren ) !== 9 ) {
		return new WP_Error( 
			'invalid_siren', 
			__( 'The company number must contain 9 digits.', 'ad-pro-price-list' )
		);
	}
	
	// Basic validation - all digits
	if ( ! ctype_digit( $siren ) ) {
		return new WP_Error( 
			'invalid_siren', 
			__( 'The company number must contain digits only.', 'ad-pro-price-list' )
		);
	}
	
	return true;
}

