<?php

namespace Limb_Chatbot\Includes\Services\Jobs\Managers;

use Limb_Chatbot\Includes\Data_Objects\Job;
use Limb_Chatbot\Includes\Data_Objects\Task;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Interfaces\Job_Handler_Interface;
use Limb_Chatbot\Includes\Interfaces\Multitask_Handler_Interface;

/**
 * Job Error Handler
 *
 * Handles error creation, classification, and job state management based on errors.
 *
 * @since 1.1.0
 */
class Job_Error_Handler {

	/**
	 * Error type constants.
	 *
	 * @since 1.1.0
	 */
	const ERROR_TYPE_CRITICAL = 'critical'; // Process stops/pauses
	const ERROR_TYPE_WARNING = 'warning';  // Process continues, user should be aware
	const ERROR_TYPE_ERROR = 'error';    // Error occurred but process continues (handled)

	/**
	 * Handle task processing error.
	 *
	 * @param  Task  $task  Task that failed.
	 * @param  Exception  $exception  Exception that occurred.
	 * @param  Job_Handler_Interface|Multitask_Handler_Interface  $handler  Job handler.
	 * @param  Job  $job  Job instance.
	 *
	 * @return void
	 * @since 1.1.0
	 */
	public function handle_task_error(
		Task $task,
		Exception $exception,
		$handler,
		Job $job
	): void {
		// Determine if error is critical (stops process)
		$is_critical = $handler->is_critical_error( $exception );

		// Determine error type - use WARNING for non-critical errors that should be logged
		// but don't stop processing (like "No content found")
		$error_type = $is_critical ? self::ERROR_TYPE_CRITICAL : self::ERROR_TYPE_WARNING;

		// Create structured error
		$error = $this->create_error( $exception );

		// Add error to job (for both warnings and critical errors)
		$job->add_error( $error );
		$job->save();

		// Only pause job if it's a critical error
		if ( $is_critical ) {
			$this->handle_critical_error( $job, $error );
		}

		// Handle task failure
		$this->handle_task_failure( $task, $error );
	}

	/**
	 * Create a standardized error structure.
	 *
	 * All errors follow the same structure for consistency and easier handling.
	 *
	 * @return array Structured error array.
	 * @since 1.1.0
	 */
	public function create_error( \Exception $e ): array {
		$error_code  = method_exists( $e, 'get_error_code' ) ? $e->get_error_code() : $e->getCode();
		$message     = $e->getMessage();
		$type        = Job_Error_Handler::ERROR_TYPE_WARNING;
		$http_status = method_exists( $e, 'get_http_status' ) ? $e->get_http_status() : null;
		$error_data  = method_exists( $e, 'get_error_data' ) ? $e->get_error_data() : null;
		// Validate error type
		$valid_types = [ self::ERROR_TYPE_CRITICAL, self::ERROR_TYPE_WARNING, self::ERROR_TYPE_ERROR ];
		if ( ! in_array( $type, $valid_types, true ) ) {
			$type = self::ERROR_TYPE_ERROR;
		}

		$error = [
			'error_code'  => $error_code,
			'message'     => $message,
			'type'        => $type,
			'is_critical' => $type === self::ERROR_TYPE_CRITICAL,
			'warning'     => $type != self::ERROR_TYPE_CRITICAL || self::ERROR_TYPE_WARNING,
			'timestamp'   => current_time( 'mysql', true ),
		];

		// Add optional fields if provided
		if ( $http_status !== null ) {
			$error['http_status'] = $http_status;
		}

		if ( $error_data !== null ) {
			$error['error_data'] = $error_data;
		}

		return $error;
	}

	/**
	 * Handle critical error that pauses the job.
	 *
	 * @param  Job  $job  Job to pause.
	 * @param  array  $error  Error information.
	 *
	 * @return void
	 * @throws \Exception
	 * @since 1.1.0
	 */
	private function handle_critical_error( Job $job, array $error ): void {
		$job->set_status( Job::STATUS_PAUSED );
		$job->save();
	}

	/**
	 * Handle task failure.
	 *
	 * @param  Task  $task  Task that failed.
	 * @param  array  $error  Error information.
	 *
	 * @return void
	 * @throws \Exception
	 * @since 1.1.0
	 */
	private function handle_task_failure( Task $task, array $error ): void {
		// Mark task as failed if max attempts exceeded
		if ( $task->has_exceeded_max_attempts() ) {
			$task->mark_failed( $error );
			$task->save();
		} else {
			// Reset task to pending for retry and store error for reference
			$task->set_status( Task::STATUS_PENDING );
			$task->set_error( $error );
			$task->save();
		}
	}
}
