<?php

namespace Limb_Chatbot\Includes\Services\Actions\Validators;

use Limb_Chatbot\Includes\Data_Objects\Parameter;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Services\Actions\Parameter_Types\Abstract_Parameter_Type;

class Parameter_Validator {

	private Abstract_Parameter_Type $parameter_type;

	public function __construct( Abstract_Parameter_Type $parameter_type ) {
		$this->parameter_type = $parameter_type;
	}

	/**
	 * Validate the parameter and return if everything is okay
	 *
	 * @param  Parameter  $parameter  Parameter that should be validated
	 * @param  array  $params  The params from which the parameter should be created
	 *
	 * @return Parameter
	 * @throws Exception
	 */
	public function validate( Parameter $parameter, array $params ) {
		$this->validate_and_set_order($parameter, $params['order'] ?? 1);
		$this->validate_and_set_name( $parameter, $params['name'] ?? '' );
		$this->validate_and_set_type( $parameter, $params['type'] ?? '' );
		$this->validate_and_set_required( $parameter, $params['required'] ?? 0 );
		$this->validate_and_set_label( $parameter, $params['label'] ?? '' );
		$this->validate_and_set_question( $parameter, $params['question'] ?? null );
		$this->validate_and_set_placeholder( $parameter, $params['placeholder'] ?? null );
		$this->validate_and_set_validation_rules( $parameter, $params['validation_rules'] ?? null );
		$this->validate_and_set_suggestions( $parameter, $params['suggestions'] ?? null );
		$this->validate_and_set_default_value( $parameter, $params['default_value'] ?? null );
		$this->validate_and_set_config( $parameter, $params['config'] ?? null );

		return $parameter;
	}

	/**
	 * Validate and set parameter name.
	 *
	 * @param  Parameter  $parameter  Parameter instance
	 * @param  string     $name       Name to validate
	 *
	 * @return void
	 * @throws Exception If name is invalid
	 * @since 1.0.0
	 */
	private function validate_and_set_name( Parameter $parameter, string $name ): void {
		$name = sanitize_text_field( trim( $name ) );
		if ( empty( $name ) ) {
			throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE, __( 'Parameter name is required and cannot be empty.', 'limb-chatbot' ) );
		}
		if ( ! preg_match( '/^[a-z0-9_]+$/', $name ) ) {
			throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE, __( 'Parameter name can only contain lowercase letters, numbers, and underscores.', 'limb-chatbot' ) );
		}
		$action_id  = $parameter->get_action_id();
		$parameters = Parameter::where( [ 'action_id' => $action_id, 'name' => $name ] );
		if ( ! $parameters->is_empty() ) {
			$parameters->each( function ( Parameter $item ) use ( $parameter ) {
				if ( ! $parameter->get_id() || $item->get_id() != $parameter->get_id() ) {
					throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE, __( 'Parameter names of the action should be unique.', 'limb-chatbot' ) );
				}
			} );
		}

		$parameter->set_name( $name );
	}

	/**
	 * Validate and set parameter type.
	 *
	 * @param  Parameter  $parameter  Parameter instance
	 * @param  string     $type       Type to validate
	 *
	 * @return void
	 * @throws Exception If type is invalid
	 * @since 1.0.0
	 */
	private function validate_and_set_type( Parameter $parameter, string $type ): void {
		$type = Parameter::normalize_type( sanitize_text_field( trim( strtolower( $type ) ) ) );
		if ( empty( $type ) ) {
			throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE, __( 'Parameter type is required.', 'limb-chatbot' ) );
		}
		if ( $type !== $this->parameter_type->get_type() ) {
			throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE, sprintf( __( 'Parameter type "%s" does not match handler type "%s".', 'limb-chatbot' ), $type, $this->parameter_type->get_type() ) );
		}

		$parameter->set_type( $type );
	}

	/**
	 * Validate and set required flag.
	 *
	 * @param  Parameter  $parameter  Parameter instance
	 * @param  mixed      $required   Required value
	 *
	 * @return void
	 * @since 1.0.0
	 */
	private function validate_and_set_required( Parameter $parameter, $required ): void {
		$required = in_array( $required, [ 1, '1', true ], true ) ? 1 : 0;
		$parameter->set_required( $required );
	}

	/**
	 * Validate and set parameter label.
	 *
	 * @param  Parameter  $parameter  Parameter instance
	 * @param  string  $label  Label to validate
	 *
	 * @return void
	 * @throws Exception
	 * @since 1.0.0
	 */
	private function validate_and_set_label( Parameter $parameter, string $label ): void {
		$label = sanitize_text_field( trim( $label ) );
		if ( empty( $label ) ) {
			$words = explode( '_', $parameter->get_name() );
			$label = ucfirst( implode( ' ', array_map( 'ucfirst', $words ) ) );
		}
		if ( mb_strlen( $label ) > 255 ) {
			throw new Exception(Error_Codes::VALIDATION_INVALID_VALUE, __('Parameter label is too long.', 'limb-chatbot'));
		}

		$parameter->set_label( $label );
	}

	/**
	 * Validate and set question text.
	 *
	 * @param  Parameter  $parameter  Parameter instance
	 * @param  string|null  $question  Question to validate
	 *
	 * @return void
	 * @throws Exception
	 * @since 1.0.0
	 */
	private function validate_and_set_question( Parameter $parameter, ?string $question ): void {
		if ( empty( $question ) ) {
			$parameter->set_question( null );
			return;
		}
		$question = sanitize_textarea_field( trim( $question ) );
		if ( mb_strlen( $question ) > 1000 ) {
			throw new Exception(Error_Codes::VALIDATION_INVALID_VALUE, __('Parameter question is too long.', 'limb-chatbot'));
		}

		$parameter->set_question( $question );
	}

	/**
	 * Validate and set placeholder text.
	 *
	 * @param  Parameter  $parameter  Parameter instance
	 * @param  string|null  $placeholder  Placeholder to validate
	 *
	 * @return void
	 * @throws Exception
	 * @since 1.0.0
	 */
	private function validate_and_set_placeholder( Parameter $parameter, ?string $placeholder ): void {
		if ( empty( $placeholder ) ) {
			$parameter->set_placeholder( null );
			return;
		}
		$placeholder = sanitize_text_field( trim( $placeholder ) );
		if ( mb_strlen( $placeholder ) > 255 ) {
			throw new Exception(Error_Codes::VALIDATION_INVALID_VALUE, __('Parameter placeholder is too long.', 'limb-chatbot'));
		}

		$parameter->set_placeholder( $placeholder );
	}

	/**
	 * Validate and set validation rules.
	 *
	 * Uses Parameter_Type_Schema to validate rules for the parameter type.
	 *
	 * @param  Parameter  $parameter  Parameter instance
	 * @param  array|null  $rules  Validation rules
	 *
	 * @return void
	 * @throws Exception
	 * @since 1.0.0
	 */
	private function validate_and_set_validation_rules( Parameter $parameter, ?array $rules ): void {
		if ( empty( $rules ) ) {
			$parameter->set_validation_rules( null );
			return;
		}
		if ( ! is_array( $rules ) ) {
			throw new Exception(Error_Codes::VALIDATION_INVALID_VALUE, __('Validation rules must be an array', 'limb-chatbot'));
		}
		$validated_rules = [];
		foreach ( $rules as $rule_key => $rule_value ) {
			$rule_key = sanitize_key( $rule_key );
			// Validate using type handler's schema
			if ( ! $this->parameter_type->is_valid_rule( $rule_key, $rule_value ) ) {
				throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE, sprintf( __( 'Invalid validation rule %s.', 'limb-chatbot' ), $rule_key ) );
			}
			$validated_rules[ $rule_key ] = $rule_value;
		}

		if ( ! empty( $validated_rules ) ) {
			$parameter->set_validation_rules( $validated_rules );
		}
	}

	/**
	 * Validate and set suggestions array.
	 *
	 * @param  Parameter  $parameter  Parameter instance
	 * @param  array|null  $suggestions  Suggestions array
	 *
	 * @return void
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function validate_and_set_suggestions( Parameter $parameter, ?array $suggestions ): void {
		if ( empty( $suggestions ) ) {
			$parameter->set_suggestions( null );
			return;
		}
		if ( ! is_array( $suggestions ) ) {
			throw new Exception(Error_Codes::VALIDATION_INVALID_VALUE,  __( 'Suggestions must be an array.', 'limb-chatbot' ) );
		}
		$validated_suggestions = [];
		foreach ( $suggestions as $index => $suggestion ) {
			// Sanitize using type handler
			$sanitized = $this->parameter_type->sanitize( $suggestion, $parameter );
			// Validate using type handler
			if ( ! $this->parameter_type->validate( $sanitized, $parameter ) ) {
				$error = $this->parameter_type->get_first_error();
				throw new Exception(Error_Codes::VALIDATION_INVALID_VALUE, sprintf( __( 'Suggestion at index %d is invalid: %s', 'limb-chatbot' ), $index, $error ?: __( 'Validation failed', 'limb-chatbot' ) ) );
			}

			$validated_suggestions[] = $sanitized;
		}
		if ( ! empty( $validated_suggestions ) ) {
			$parameter->set_suggestions( $validated_suggestions );
		}
	}

	/**
	 * Validate and set default value.
	 *
	 * @param  Parameter  $parameter  Parameter instance
	 * @param  mixed  $default_value  Default value to validate
	 *
	 * @return void
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function validate_and_set_default_value( Parameter $parameter, $default_value ): void {
		if ( null === $default_value || '' === $default_value ) {
			$parameter->set_default_value( null );
			return;
		}
		// Sanitize using type handler
		$sanitized = $this->parameter_type->sanitize( $default_value, $parameter );
		// Validate using type handler
		if ( ! $this->parameter_type->validate( $sanitized, $parameter ) ) {
			$error = $this->parameter_type->get_first_error();
			throw new Exception(Error_Codes::VALIDATION_INVALID_VALUE, sprintf( __( 'Default value is invalid: %s', 'limb-chatbot' ), $error ?: __( 'Validation failed', 'limb-chatbot' ) ) );
		}

		$parameter->set_default_value( $sanitized );
	}

	/**
	 * Validate and set type-specific configuration.
	 *
	 * Uses Parameter_Type_Schema to validate configs for the parameter type.
	 *
	 * @param  Parameter  $parameter  Parameter instance
	 * @param  array|null  $config  Configuration array
	 *
	 * @return void
	 * @throws Exception
	 * @since 1.0.0
	 */
	private function validate_and_set_config( Parameter $parameter, ?array $config ): void {
		if ( ! is_array( $config ) ) {
			throw new Exception(Error_Codes::VALIDATION_INVALID_VALUE,  __( 'Configuration must be an array.', 'limb-chatbot' ) );
		}
		$validated_config = [];
		foreach ( $config as $key => $value ) {
			$key = sanitize_key( $key );
			// Validate using type handler's schema
			if ( ! $this->parameter_type->is_valid_config( $key, $value ) ) {
				throw new Exception(Error_Codes::VALIDATION_INVALID_VALUE, sprintf(__('Invalid config %s.', 'limb-chatbot'), $key));
			}
			$validated_config[ $key ] = $value;
		}
		$config_schema = $this->parameter_type->get_config_schema();
		foreach ( $config_schema as $key => $config_item ) {
			$has_key = array_key_exists( $key, $validated_config );
			$value = $has_key ? $validated_config[ $key ] : null;
			if ( ! empty( $config_item['required'] ) && ( ! $has_key || null === $value || '' === $value ) ) {
				throw new Exception( Error_Codes::VALIDATION_REQUIRED, sprintf( __( 'The config %s for %s is required.', 'limb-chatbot' ), $key, $this->parameter_type->get_type() ) );
			}
		}

		if ( ! empty( $validated_config ) ) {
			$parameter->set_config( $validated_config );
		}
	}

	private function validate_and_set_order( Parameter $parameter, $param ) {
		if ( $param < 1 ) {
			throw new Exception( Error_Codes::VALIDATION_REQUIRED,
				__( 'The order can\'t be negative.', 'limb-chatbot' ) );
		}
		$parameter->set_order( $param );
	}
}