<?php

namespace Limb_Chatbot\Includes\Services\Actions\Data_Collecting_Processors;

use Limb_Chatbot\Includes\Data_Objects\Action_Plan;
use Limb_Chatbot\Includes\Data_Objects\Action_Plan_Step;
use Limb_Chatbot\Includes\Data_Objects\Chat;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Data_Objects\Parameter;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Factories\Parameter_Type_Factory;
use Limb_Chatbot\Includes\Interfaces\Data_Collecting_Processor_Interface;
use Limb_Chatbot\Includes\Services\Actions\Email_Verification_Message_Builder;
use Limb_Chatbot\Includes\Services\Actions\Parameter_Request_Message_Builder;
use Limb_Chatbot\Includes\Services\Actions\Parameter_Value_Processor;
use Limb_Chatbot\Includes\Services\Actions\Validation_Error_Message_Generator;
use Limb_Chatbot\Includes\Utilities\Chatbot_Utility;

abstract class Data_Collecting_Processor implements Data_Collecting_Processor_Interface {
	protected Parameter_Value_Processor $value_processor;
	protected Validation_Error_Message_Generator $error_message_generator;
	protected Parameter_Request_Message_Builder $parameter_message_builder;
	protected Chat $chat;
	protected Email_Verification_Message_Builder $verification_message_builder;
	protected Chatbot_Utility $chatbot_utility;

	public function __construct( Chat $chat, Chatbot_Utility $chatbot_utility ) {
		$this->chat                         = $chat;
		$this->chatbot_utility              = $chatbot_utility;
		$this->value_processor              = new Parameter_Value_Processor();
		$this->error_message_generator      = new Validation_Error_Message_Generator();
		$this->parameter_message_builder    = new Parameter_Request_Message_Builder( $chatbot_utility );
		$this->verification_message_builder = $verification_message_builder ?? new Email_Verification_Message_Builder();
	}

	abstract public function process(
		Action_Plan $action_plan,
		Action_Plan_Step $step,
		Message $user_message
	): Message;


	/**
	 * Initiate a post-collection step (e.g., email verification)
	 *
	 * Post-collection steps are parameter type-specific operations that occur
	 * after a parameter is collected. This method is generic and works with
	 * any parameter type that implements post-collection logic.
	 *
	 * @param  Action_Plan  $action_plan  The action plan
	 * @param  Action_Plan_Step  $step  The post-collection step
	 *
	 * @return Message Step initiation or error message
	 * @throws \Exception
	 * @since 1.0.0
	 */
	protected function initiate_post_collection_step(
		Action_Plan $action_plan,
		Action_Plan_Step $step
	): Message {
		try {
			$step_data = $step->get_data();
			$parameter = $step_data['parameter'] ?? null;
			if ( ! $parameter instanceof Parameter ) {
				throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE,
					__( 'Invalid parameter in post-collection step', 'limb-chatbot' ) );
			}
			// Get the parameter type handler
			$parameter_type = ( new Parameter_Type_Factory() )->make( $parameter->get_type() );
			// Get the collected value for this parameter
			$collected_fields = $action_plan->collected_fields();
			$collected_value  = $collected_fields[ $parameter->get_name() ] ?? null;
			if ( empty( $collected_value ) ) {
				throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE,
					__( 'Collected value not found for post-collection step', 'limb-chatbot' ) );
			}
			// Let the parameter type handler prepare the post-collection step data
			$post_step_data = $parameter_type->get_post_collection_step_data( $parameter, $collected_value );

			// Update step with the prepared data
			foreach ( $post_step_data as $key => $value ) {
				$step->add_data( $key, $value );
			}

			$this->save_action_plan( $action_plan );

			// Return appropriate request message for this post-collection step
			return $this->build_post_collection_request_message( $action_plan, $step );
		} catch ( Exception $e ) {
			// Handle email verification failures specifically
			if ( $step->get_type() === Action_Plan_Step::TYPE_EMAIL_VERIFICATION ) {
				return $this->handle_email_verification_failure( $action_plan, $step, $e );
			}
			return $this->build_generic_error_message( $e );
		}
	}

	/**
	 * Save action plan to chat meta
	 *
	 * @param  Action_Plan  $plan  The action plan to save
	 *
	 * @return void
	 * @since 1.0.0
	 */
	protected function save_action_plan( Action_Plan $plan ): void {
		$this->chat->update_meta( Chat::KEY_CURRENT_ACTION, $plan );
	}

	/**
	 * Build a post-collection request message
	 *
	 * Generic message builder for post-collection step requests.
	 * For now, handles email verification. Can be extended for other types.
	 *
	 * @param  Action_Plan  $action_plan  The action plan
	 * @param  Action_Plan_Step  $step  The post-collection step
	 *
	 * @return Message Request message
	 * @since 1.0.0
	 */
	private function build_post_collection_request_message(
		Action_Plan $action_plan,
		Action_Plan_Step $step
	): Message {
		// For email verification, use the specialized message builder
		if ( $step->get_type() === Action_Plan_Step::TYPE_EMAIL_VERIFICATION ) {
			return $this->verification_message_builder->build( $action_plan, $step, $this->chatbot_utility->get_stream() );
		}

		// Generic fallback for other post-collection steps
		return Message::make( [
			'role'    => Message::ROLE_ASSISTANT,
			'content' => [
				[
					'type' => Message::CONTENT_TYPE_TEXT,
					'text' => [ 'value' => __( 'Please provide the requested information.', 'limb-chatbot' ) ],
				],
			],
		] );
	}

	/**
	 * Handle email verification failure by reverting to email collection step
	 *
	 * When email verification fails (e.g., email sending fails), this method:
	 * 1. Finds and marks the email collection step as incomplete (to re-request it)
	 * 2. Keeps the verification step incomplete so it can be retried after email is collected again
	 * 3. Returns a message requesting the email again
	 *
	 * @param  Action_Plan  $action_plan  The action plan
	 * @param  Action_Plan_Step  $verification_step  The failed verification step
	 * @param  Exception  $e  The exception that occurred
	 *
	 * @return Message Message requesting email again
	 * @since 1.0.0
	 */
	private function handle_email_verification_failure(
		Action_Plan $action_plan,
		Action_Plan_Step $verification_step,
		Exception $e
	): Message {
		$step_data = $verification_step->get_data();
		$parameter = $step_data['parameter'] ?? null;

		if ( ! $parameter instanceof Parameter ) {
			return $this->build_generic_error_message( $e );
		}

		// Find the email collection step and mark it as incomplete
		$email_collection_step = $this->find_email_collection_step( $action_plan, $parameter->get_name() );
		if ( $email_collection_step ) {
			// Remove the collected value so it gets re-requested
			$step_data = $email_collection_step->get_data();
			unset( $step_data['value'] );
			$email_collection_step->set_data( $step_data );
			$email_collection_step->set_state( Action_Plan_Step::STATE_INCOMPLETE );
		}

		// Keep verification step incomplete so it can be retried after email is collected again
		// The email step will be processed first since it comes before verification in the steps array

		$this->save_action_plan( $action_plan );

		// Build error message and re-request email
		$error_text = sprintf(
			$e->getMessage()
		);

		// If we found the email step, request it again
		if ( $email_collection_step ) {
			$error_message = Message::make( [
				'role'    => Message::ROLE_ASSISTANT,
				'content' => [
					[
						'type' => Message::CONTENT_TYPE_TEXT,
						'text' => [
							'value' => $error_text,
						],
					],
				],
			] );

			// Build the parameter request message
			$parameter_message = $this->parameter_message_builder->build( $action_plan, $email_collection_step, true );

			// Merge the error message with the parameter request
			$content = array_merge( $error_message->get_content(), $parameter_message->get_content() );

			return Message::make( [
				'role'    => Message::ROLE_ASSISTANT,
				'content' => $content,
			] );
		}

		// Fallback to generic error if we couldn't find the email step
		return $this->build_generic_error_message( $e );
	}

	/**
	 * Find the email collection step for a given parameter name
	 *
	 * @param  Action_Plan  $action_plan  The action plan
	 * @param  string  $parameter_name  The parameter name to find
	 *
	 * @return Action_Plan_Step|null The email collection step or null if not found
	 * @since 1.0.0
	 */
	private function find_email_collection_step( Action_Plan $action_plan, string $parameter_name ): ?Action_Plan_Step {
		foreach ( $action_plan->steps as $step ) {
			if ( ! $step instanceof Action_Plan_Step ) {
				continue;
			}

			if ( $step->get_type() === Action_Plan_Step::TYPE_DATA_COLLECTION ) {
				$step_data = $step->get_data();
				$step_parameter = $step_data['parameter'] ?? null;

				if ( $step_parameter instanceof Parameter && $step_parameter->get_name() === $parameter_name ) {
					return $step;
				}
			}
		}

		return null;
	}

	private function build_generic_error_message( Exception $e ) {
		return Message::make( [
			'role'    => Message::ROLE_ASSISTANT,
			'content' => [
				[
					'type' => Message::CONTENT_TYPE_TEXT,
					'text' => [
						'value' => sprintf(
							__( 'Sorry, I encountered an error: %s', 'limb-chatbot' ),
							$e->getMessage()
						),
					],
				],
			],
		] );
	}

	/**
	 * Extract parameter value from message content.
	 *
	 * Attempts to extract the object/post ID from content[0].text.data first,
	 * then falls back to content[0].text.value for the display label/value.
	 *
	 * @param  Message  $message  The message to extract from.
	 *
	 * @return string The extracted value.
	 * @since 1.0.0
	 */
	protected function extract_parameter_value( Message $message ): string {
		$content     = $message->get_content();
		$attachments = [];
		$value       = '';

		foreach ( $content as $content_part ) {
			if ( ! empty( $content_part['type'] ) && $content_part['type'] === Message::CONTENT_TYPE_PARAMETER_VALUE ) {
				$value = $content_part[ Message::CONTENT_TYPE_PARAMETER_VALUE ]['value'] ?? '';
			}
			if ( ! empty( $content_part['type'] ) && $content_part['type'] === 'attachment' && ! empty( $content_part['attachment'] ) && is_array( $content_part['attachment'] ) ) {
				$attachments[] = [
					'title'     => isset( $content_part['attachment']['title'] ) ? sanitize_text_field( (string) $content_part['attachment']['title'] ) : '',
					'mime_type' => isset( $content_part['attachment']['mime_type'] ) ? sanitize_mime_type( (string) $content_part['attachment']['mime_type'] ) : '',
					'value'     => isset( $content_part['attachment']['value'] ) ? esc_url_raw( (string) $content_part['attachment']['value'] ) : '',
					'media_id'  => isset( $content_part['attachment']['media_id'] ) ? absint( $content_part['attachment']['media_id'] ) : 0,
					'file_size' => isset( $content_part['attachment']['file_size'] ) ? absint( $content_part['attachment']['file_size'] ) : 0,
					'file_name' => isset( $content_part['attachment']['file_name'] ) ? sanitize_file_name( (string) $content_part['attachment']['file_name'] ) : '',
				];
			}
		}

		if ( ! empty( $attachments ) ) {
			return wp_json_encode( $attachments );
		}

		return ! empty( $value ) ? $value : $message->extract_text();
	}
}