<?php

namespace Limb_Chatbot\Includes\Services\Jobs;

use Limb_Chatbot\Includes\Data_Objects\Job;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Factories\Job_Handler_Factory;
use Limb_Chatbot\Includes\Interfaces\Multitask_Handler_Interface;
use Limb_Chatbot\Includes\Repositories\Job_Repository;
use Limb_Chatbot\Includes\Repositories\Job_Task_Repository;
use Limb_Chatbot\Includes\Services\Jobs\Managers\Job_Processing_Limits;

/**
 * Job Tasks Generator
 *
 * Handles generation of tasks for jobs in batches.
 * Ensures accurate stats tracking and proper state transitions.
 *
 * @since 1.1.0
 */
class Job_Tasks_Generator {

	/**
	 * Default batch size for task generation.
	 *
	 * @var int
	 * @since 1.1.0
	 */
	private int $default_batch_size = 100;

	/**
	 * Job handler factory instance.
	 *
	 * @var Job_Handler_Factory
	 * @since 1.1.0
	 */
	private Job_Handler_Factory $handler_factory;

	/**
	 * Task repository instance.
	 *
	 * @var Job_Task_Repository
	 * @since 1.1.0
	 */
	private Job_Task_Repository $task_repository;

	/**
	 * Job repository instance.
	 *
	 * @var Job_Repository
	 * @since 1.1.0
	 */
	private Job_Repository $job_repository;

	/**
	 * Job stats service instance.
	 *
	 * @var Job_Stats_Service
	 * @since 1.1.0
	 */
	private Job_Stats_Service $stats_service;

	/**
	 * Processing limits manager instance.
	 *
	 * @var Job_Processing_Limits
	 * @since 1.1.0
	 */
	private Job_Processing_Limits $limits_manager;

	/**
	 * Constructor.
	 *
	 * @param  Job_Handler_Factory  $handler_factory  Handler factory.
	 * @param  Job_Task_Repository  $task_repository  Task repository.
	 * @param  Job_Repository|null  $job_repository  Job repository.
	 * @param  Job_Stats_Service|null  $stats_service  Stats service.
	 *
	 * @since 1.1.0
	 */
	public function __construct(
		Job_Handler_Factory $handler_factory,
		Job_Task_Repository $task_repository,
		?Job_Repository $job_repository = null,
		?Job_Stats_Service $stats_service = null
	) {
		$this->handler_factory = $handler_factory;
		$this->task_repository = $task_repository;
		$this->job_repository  = $job_repository ?? new Job_Repository();
		$this->stats_service   = $stats_service ?? new Job_Stats_Service( $this->task_repository );
		$this->limits_manager  = new Job_Processing_Limits();
	}

	/**
	 * Generate a batch of tasks for a job.
	 *
	 * Generates tasks in batches to prevent timeouts with large datasets.
	 * Uses actual database counts for accuracy.
	 * Handles time/memory limits gracefully - updates stats even if interrupted.
	 *
	 * @param  Job  $job  Job instance.
	 * @param  int|null  $batch_size  Number of tasks to generate in this batch.
	 *
	 * @return Job Updated job instance.
	 * @throws Exception If generation fails.
	 * @since 1.1.0
	 */
	public function generate_batch( Job $job, ?int $batch_size = null ): Job {
		$batch_size = $batch_size ?? $this->default_batch_size;

		// Start tracking limits
		$this->limits_manager->start();

		// Reload job to get latest state
		$job = $this->job_repository->find( $job->get_id() );
		if ( ! $job || ( ! $job->is_generating_tasks() && ! $job->is_pending() ) ) {
			return $job;
		}

		// Update status to GENERATING_TASKS if still pending (first time generating)
		if ( $job->is_pending() ) {
			$job->set_status( Job::STATUS_GENERATING_TASKS );
			$job->save();
		}

		// Check limits before starting
		if ( $this->limits_manager->exceeded() ) {
			// Limits already exceeded, just update stats and return
			return $job;
		}

		try {
			// Get handler for this job type
			$handler = $this->handler_factory->make( $job->get_type() );
			$config  = $job->get_config();
			$stats   = $job->get_stats() ?? [];

			// Get total expected tasks
			$total_expected = $stats['total_tasks'] ?? 0;

			// Check if handler is multitask
			$is_multitask = $handler instanceof Multitask_Handler_Interface;

			// If total not set, calculate it and initialize stats
			if ( $total_expected === 0 ) {
				$total_expected = $handler->get_total( $config, $job );
				if ( $total_expected === 0 ) {
					throw new Exception(
						Error_Codes::NOT_FOUND,
						__( 'No tasks to generate for this job', 'limb-chatbot' )
					);
				}

				// For multitask handlers, total_expected is parent tasks
				// But stats should track child tasks, so we'll update total dynamically
				// Initialize with parent task count for now (will be updated when child tasks are generated)
				$stats = $this->stats_service->get_initial_stats( $is_multitask ? $total_expected : $total_expected, $is_multitask );
				$job->set_stats( $stats );
				$job->save();
			}

			// Get actual count of generated tasks from database (more accurate)
			// For multitask handlers, count parent tasks only
			if ( $is_multitask ) {
				$actual_total_tasks = $this->task_repository->count( [
					'job_id'         => $job->get_id(),
					'parent_task_id' => null,
				] );
			} else {
				$actual_total_tasks = $this->task_repository->count( [ 'job_id' => $job->get_id() ] );
			}

			// Adjust batch size if we're near the end
			$remaining         = $total_expected - $actual_total_tasks;
			$actual_batch_size = min( $batch_size, max( 0, $remaining ) );

			// No more tasks to generate
			if ( $actual_batch_size === 0 ) {
				$this->complete_generation( $job );

				return $job;
			}

			// Generate batch of tasks
			// Note: If this is interrupted by time/memory limits, the job will
			// remain in 'generating_tasks' status and can resume in the next poll
			$handler->generate_task_batch( $job, $config, $actual_total_tasks, $actual_batch_size );
		} finally {
			// Always update stats, even if generation was interrupted
			$job = $this->job_repository->find( $job->get_id() );

			// Check if generation is complete using actual database count
			// This handles cases where generation completed but we didn't catch it
			if ( $job && $job->is_generating_tasks() ) {
				$current_total  = $this->task_repository->count( [ 'job_id' => $job->get_id() ] );
				$stats          = $job->get_stats() ?? [];
				$total_expected = $stats['total_tasks'] ?? 0;

				if ( $total_expected > 0 && $current_total >= $total_expected ) {
					$this->complete_generation( $job );
					$job = $this->job_repository->find( $job->get_id() );
				}
			}
		}

		return $job;
	}

	/**
	 * Complete task generation phase and transition to processing.
	 *
	 * Updates job status and ensures stats are accurate.
	 *
	 * @param  Job  $job  Job instance.
	 *
	 * @return void
	 * @throws Exception|\Exception
	 * @since 1.1.0
	 */
	public function complete_generation( Job $job ): void {
		// Update job status to processing
		$job->set_status( Job::STATUS_PROCESSING );

		// Set started_at if not already set
		if ( ! $job->get_started_at() ) {
			$job->set_started_at( current_time( 'mysql', true ) );
		}

		$job->save();
	}
}
