<?php

namespace Limb_Chatbot\Includes\Data_Objects;

use Limb_Chatbot\Includes\Services\Helper;

/**
 * Represents a Task data object in the queue system.
 *
 * @since 1.0.9
 */
class Task extends WPDB_Data_Object {

	/**
	 * The name of the database table associated with this model.
	 *
	 * @since 1.0.9
	 */
	const TABLE_NAME = 'lbaic_job_tasks';

	/**
	 * The list of fillable fields for mass assignment.
	 *
	 * @since 1.0.9
	 */
	const FILLABLE = [
		'job_id',
		'parent_task_id',
		'payload',
		'status',
		'attempt_count',
		'error',
		'task_started_at',
		'task_completed_at',
	];

	/**
	 * Task status constants
	 */
	const STATUS_PENDING = 'pending';
	const STATUS_PROCESSING = 'processing';
	const STATUS_COMPLETED = 'completed';
	const STATUS_FAILED = 'failed';

	/**
	 * Maximum number of retry attempts.
	 */
	const MAX_ATTEMPTS = 1;
	const STATUS_CHILD_TASK_PENDING = 'child_task_pending';

	/**
	 * Job ID this task belongs to.
	 *
	 * @var int|null
	 * @since 1.0.9
	 */
	public ?int $job_id = null;

	/**
	 * Task payload (JSON).
	 *
	 * @var array|null
	 * @since 1.0.9
	 */
	public ?array $payload = null;

	/**
	 * Parent task ID if this is a sub-task.
	 *
	 * @var int|null
	 * @since 1.0.9
	 */
	public ?int $parent_task_id = null;

	/**
	 * Task status.
	 *
	 * @var null|string
	 * @since 1.0.9
	 */
	public ?string $status = self::STATUS_PENDING;

	/**
	 * Number of attempts made to process this task.
	 *
	 * @var int|null
	 * @since 1.0.9
	 */
	public ?int $attempt_count = null;

	/**
	 * Error message if task failed.
	 *
	 * @var array|null
	 * @since 1.0.9
	 */
	public ?array $error = null;

	/**
	 * Task started timestamp.
	 *
	 * @var string|null
	 * @since 1.0.9
	 */
	public ?string $task_started_at = null;

	/**
	 * Task completed timestamp.
	 *
	 * @var string|null
	 * @since 1.0.9
	 */
	public ?string $task_completed_at = null;

	public function __construct( $instance = null ) {
		if ( is_array( $instance ) && ! empty( $instance['payload'] ) && $this->isJson( $instance['payload'] ) ) {
			$instance['payload'] = Helper::maybe_json_decode( $instance['payload'] );
		}
		if ( is_array( $instance ) && ! empty( $instance['error'] ) && $this->isJson( $instance['error'] ) ) {
			$instance['error'] = Helper::maybe_json_decode( $instance['error'] );
		}
		parent::__construct( $instance );
	}

	/**
	 * Get task payload as array.
	 *
	 * @return array|null
	 * @since 1.1.0
	 */
	public function get_payload(): ?array {
		return $this->payload;
	}

	/**
	 * Set task payload.
	 *
	 * @param  null|array  $payload
	 *
	 * @return void
	 * @since 1.1.0
	 */
	public function set_payload( ?array $payload ): void {
		$this->payload = $payload;
	}

	/**
	 * Get task status.
	 *
	 * @return null|string
	 * @since 1.1.0
	 */
	public function get_status(): ?string {
		return $this->status;
	}

	/**
	 * Set task status.
	 *
	 * @param  null|string  $status
	 *
	 * @return void
	 * @since 1.1.0
	 */
	public function set_status( ?string $status ): void {
		$this->status = $status;
	}

	/**
	 * Get attempt count.
	 *
	 * @return null|int
	 * @since 1.1.0
	 */
	public function get_attempt_count(): ?int {
		return $this->attempt_count;
	}

	/**
	 * Increment attempt count.
	 *
	 * @return void
	 * @since 1.1.0
	 */
	public function increment_attempts(): void {
		if ( $this->attempt_count ) {
			$this->attempt_count ++;
		} else {
			$this->attempt_count = 1;
		}
	}

	/**
	 * Check if task has exceeded maximum attempts.
	 *
	 * @return bool
	 * @since 1.1.0
	 */
	public function has_exceeded_max_attempts(): bool {
		return $this->attempt_count >= self::MAX_ATTEMPTS;
	}

	/**
	 * Get error message.
	 *
	 * @return array|null
	 * @since 1.1.0
	 */
	public function get_error(): ?array {
		return $this->error;
	}

	/**
	 * Set error message.
	 *
	 * @param  null|array  $error
	 *
	 * @return void
	 * @since 1.1.0
	 */
	public function set_error( ?array $error ): void {
		$this->error = $error;
	}

	/**
	 * Mark task as processing.
	 *
	 * @return void
	 * @since 1.1.0
	 */
	public function mark_processing(): void {
		$this->status          = self::STATUS_PROCESSING;
		$this->task_started_at = current_time( 'mysql', true );
	}

	/**
	 * Mark task as completed.
	 *
	 * @return void
	 * @since 1.1.0
	 */
	public function mark_completed(): void {
		$this->status            = self::STATUS_COMPLETED;
		$this->task_completed_at = current_time( 'mysql', true );
		$this->error             = null;
	}

	/**
	 * Mark task as failed with error message.
	 *
	 * @param  array  $error
	 *
	 * @return void
	 * @since 1.1.0
	 */
	public function mark_failed( array $error ): void {
		$this->status            = self::STATUS_FAILED;
		$this->task_completed_at = current_time( 'mysql', true );
		$this->error             = $error;
	}

	/**
	 * Check if task is pending.
	 *
	 * @return bool
	 * @since 1.1.0
	 */
	public function is_pending(): bool {
		return $this->status === self::STATUS_PENDING;
	}

	/**
	 * Check if task is processing.
	 *
	 * @return bool
	 * @since 1.1.0
	 */
	public function is_processing(): bool {
		return $this->status === self::STATUS_PROCESSING;
	}

	/**
	 * Check if task is completed.
	 *
	 * @return bool
	 * @since 1.1.0
	 */
	public function is_completed(): bool {
		return $this->status === self::STATUS_COMPLETED;
	}

	/**
	 * Check if task is failed.
	 *
	 * @return bool
	 * @since 1.1.0
	 */
	public function is_failed(): bool {
		return $this->status === self::STATUS_FAILED;
	}

	/**
	 * Get task duration in seconds if completed.
	 *
	 * @return float|null
	 * @since 1.1.0
	 */
	public function get_duration(): ?float {
		if ( empty( $this->task_started_at ) || empty( $this->task_completed_at ) ) {
			return null;
		}

		$started   = strtotime( $this->task_started_at );
		$completed = strtotime( $this->task_completed_at );

		return $completed - $started;
	}

	/**
	 * Get task started at timestamp.
	 *
	 * @return string|null
	 * @since 1.1.0
	 */
	public function get_task_started_at(): ?string {
		return $this->task_started_at;
	}

	/**
	 * Get task completed at timestamp.
	 *
	 * @return string|null
	 * @since 1.1.0
	 */
	public function get_task_completed_at(): ?string {
		return $this->task_completed_at;
	}

	public function job() {
		return Job::find( $this->get_job_id() );
	}

	/**
	 * Get job ID.
	 *
	 * @return int
	 * @since 1.0.9
	 */
	public function get_job_id(): int {
		return $this->job_id;
	}

	public function get_parent_task_id() {
		return $this->parent_task_id;
	}

	public function mark_child_processing() {
		$this->status            = self::STATUS_CHILD_TASK_PENDING;
		$this->task_completed_at = current_time( 'mysql', true );
		$this->error             = null;
	}
}

