<?php

namespace Limb_Chatbot\Includes\Services\Job;

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\Repositories\Job_Task_Repository;

/**
 * Abstract base class for queue handlers.
 *
 * Provides common functionality for all queue handlers.
 *
 * @since 1.1.0
 */
abstract class Abstract_Job_Handler implements Job_Handler_Interface {

	/**
	 * Task repository instance.
	 *
	 * @var Job_Task_Repository
	 * @since 1.1.0
	 */
	protected Job_Task_Repository $job_task_repository;

	/**
	 * Constructor.
	 *
	 * @since 1.1.0
	 */
	public function __construct() {
		$this->job_task_repository = new Job_Task_Repository();
	}

	/**
	 * Prepare and transform job configuration before validation.
	 *
	 * Default implementation returns config unchanged.
	 * Override in child classes to normalize or convert config values.
	 *
	 * @param  array        $config        Job configuration.
	 * @param  string|null  $chatbot_uuid  Chatbot UUID.
	 *
	 * @return array Prepared configuration.
	 * @since 1.1.3
	 */
	public function prepare_config( array $config, ?string $chatbot_uuid = null ): array {
		return $config;
	}

	/**
	 * Validate job configuration before job creation.
	 *
	 * Must be implemented by child classes.
	 *
	 * @param array $config Job configuration.
	 * @return bool True if valid.
	 * @throws Exception If validation fails.
	 * @since 1.1.0
	 */
	abstract public function validate( array $config, ?string $chatbot_uuid = null ): bool;

	/**
	 * Enrich configuration after validation (default: no changes).
	 *
	 * @param  array        $config        Job configuration.
	 * @param  string|null  $chatbot_uuid  Chatbot UUID.
	 *
	 * @return array
	 * @throws Exception
	 * @since 1.1.0
	 */
	public function finalize_job_config( array $config, ?string $chatbot_uuid = null ): array {
		return $config;
	}

	/**
	 * Generate a batch of tasks for the job (chunked generation).
	 *
	 * Must be implemented by child classes.
	 *
	 * @param Job $job Job instance.
	 * @param array $config Job configuration.
	 * @param int $offset Starting offset for this batch.
	 * @param int $limit Maximum number of tasks to generate in this batch.
	 * @return int Number of tasks actually created.
	 * @throws Exception If task generation fails.
	 * @since 1.1.0
	 */
	abstract public function generate_task_batch( Job $job, array $config, int $offset, int $limit ): int;

	/**
	 * Process a single task.
	 *
	 * Must be implemented by child classes.
	 *
	 * @param Task $task Task to process.
	 * @return bool True on success, false on failure.
	 * @throws Exception If processing fails.
	 * @since 1.1.0
	 */
	abstract public function process_task( Task $task ): bool;

	/**
	 * Get the job type this handler manages.
	 *
	 * Must be implemented by child classes.
	 *
	 * @return string Job type identifier.
	 * @since 1.1.0
	 */
	abstract public function get_job_type(): string;

	/**
	 * Determine if an exception is critical and should pause the job.
	 *
	 * Default implementation checks for common critical error codes.
	 * Can be overridden by child classes for specific error handling.
	 *
	 * @param Exception $exception Exception that occurred.
	 * @return bool True if critical, false otherwise.
	 * @since 1.1.0
	 */
	public function is_critical_error( Exception $exception ): bool {
		// Critical error codes that should pause the job
		$critical_codes = [
			Error_Codes::QUOTA_EXCEED,
			Error_Codes::AUTHENTICATION_API_KEY_MISSING,
			Error_Codes::AUTHENTICATION_UNAUTHORIZED,
			Error_Codes::AI_PROVIDER_NOT_SET,
			Error_Codes::AI_MODEL_NOT_SET,
			Error_Codes::JOB_VECTOR_INDEX_RESOLUTION_FAILED,
		];

		$error_code = $exception->get_error_code();

		// Check if it's a critical error code
		if ( in_array( $error_code, $critical_codes, true ) ) {
			return true;
		}

		// Check if exception has quota-related flag
		if ( method_exists( $exception, 'is_related_to_quota' ) && $exception->is_related_to_quota() ) {
			return true;
		}

		return false;
	}

	/**
	 * Helper to create a single task.
	 *
	 * @param int $job_id Job ID.
	 * @param array $payload Task payload.
	 * @return Task|null Created task or null on failure.
	 * @since 1.1.0
	 */
	protected function create_task( int $job_id, array $payload ): ?Task {
		return $this->job_task_repository->create( [
			'job_id'  => $job_id,
			'payload' => $payload,
			'status'  => Task::STATUS_PENDING,
		] );
	}

	/**
	 * Helper to create multiple tasks.
	 *
	 * @param int $job_id Job ID.
	 * @param array $payloads Array of task payloads.
	 * @return int Number of tasks created.
	 * @since 1.1.0
	 */
	protected function create_tasks( int $job_id, array $payloads ): int {
		$count = 0;
		foreach ( $payloads as $payload ) {
			if ( $this->create_task( $job_id, $payload ) ) {
				$count++;
			}
		}
		return $count;
	}

	/**
	 * Log a message for debugging.
	 *
	 * @param mixed $message Message to log.
	 * @param string $method Method name.
	 * @return void
	 * @since 1.1.0
	 */
	protected function log( $message, string $method = '' ): void {
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			$context = $method ?: get_class( $this );
			error_log( sprintf( '[Job Handler %s] %s', $context, print_r( $message, true ) ) );
		}
	}

	public function create_sub_task( $job_id, Task $task, array $payload ): ?Task {
		return $this->job_task_repository->create( [
			'job_id'         => $job_id,
			'parent_task_id' => $task->get_id(),
			'payload'        => $payload,
			'status'         => Task::STATUS_PENDING,
		] );
	}
}

