<?php

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Niwa_AI_Admin_Autonomy_Actor_Runtime {
    const ACTOR_MESSAGE_LEASE_TTL = 600;
    const ACTOR_MESSAGE_HEARTBEAT_INTERVAL = 30;

    private $control_plane;
    private $session_resolver;
    private $message_runner;
    private $turn_time_limit = 420;
    private $callbacks = array();

    public function __construct( $control_plane = null, $telegram = null, $session_resolver = null, $message_runner = null, $turn_time_limit = 420, $callbacks = array() ) {
        $this->control_plane     = $control_plane;
        $this->session_resolver  = $session_resolver;
        $this->message_runner    = $message_runner;
        $this->turn_time_limit   = max( 60, intval( $turn_time_limit ) );
        $this->callbacks         = is_array( $callbacks ) ? $callbacks : array();
    }

    public function enqueue_message( $message, $callback_acknowledged = false, $actor_id = '' ) {
        if (
            ! $this->control_plane
            || ! method_exists( $this->control_plane, 'enqueue_actor_message' )
            || ! is_callable( $this->session_resolver )
        ) {
            return array();
        }

        $session_id = trim( (string) call_user_func( $this->session_resolver, $message ) );
        if ( '' === $session_id ) {
            return array();
        }

        $meta = array(
            'callback_acknowledged' => ! empty( $callback_acknowledged ),
        );
        $actor_id = substr( preg_replace( '/[^a-zA-Z0-9_\-]/', '', trim( (string) $actor_id ) ), 0, 96 );
        if ( '' !== $actor_id ) {
            $meta['actor_id'] = $actor_id;
        }
        if ( ! empty( $message['context_payload'] ) && is_array( $message['context_payload'] ) ) {
            $meta['context_payload'] = $message['context_payload'];
            unset( $message['context_payload'] );
        }
        if ( ! empty( $message['continuation_context'] ) && is_array( $message['continuation_context'] ) ) {
            $meta['continuation_context'] = $message['continuation_context'];
            unset( $message['continuation_context'] );
        }
        foreach ( array(
            'continuation_depth',
            'continuation_root_actor_message_id',
            'continuation_root_update_id',
            'continuation_root_message_id',
            'continuation_parent_actor_message_id',
        ) as $continuation_meta_key ) {
            if ( array_key_exists( $continuation_meta_key, $message ) ) {
                $meta[ $continuation_meta_key ] = $message[ $continuation_meta_key ];
                unset( $message[ $continuation_meta_key ] );
            }
        }

        return (array) $this->control_plane->enqueue_actor_message(
            $session_id,
            $message,
            $meta
        );
    }

    public function process_runtime( $limit = 3 ) {
        if (
            ! $this->control_plane
            || ! method_exists( $this->control_plane, 'list_sessions_with_pending_actor_messages' )
            || ! method_exists( $this->control_plane, 'claim_next_actor_message' )
            || ! method_exists( $this->control_plane, 'complete_actor_message' )
        ) {
            return array(
                'success'            => true,
                'processed_sessions' => 0,
                'processed_messages' => 0,
                'pending'            => false,
                'errors'             => array(),
            );
        }

        $processed_sessions = 0;
        $processed_messages = 0;
        $errors             = array();
        $sessions           = (array) $this->control_plane->list_sessions_with_pending_actor_messages( max( 1, min( 10, intval( $limit ) ) ) );

        foreach ( $sessions as $record ) {
            $session_id = isset( $record['session_id'] ) ? trim( (string) $record['session_id'] ) : '';
            if ( '' === $session_id ) {
                continue;
            }

            $lease_ttl             = $this->get_runtime_lease_ttl();
            $run_lock              = 'actor-run:' . $session_id;
            $session_lock_acquired = false;
            if ( ! $this->control_plane->acquire_session_lane( $run_lock, $lease_ttl ) ) {
                continue;
            }

            $entry       = array();
            $checkpointed = false;
            $execution_failed = false;
            try {
                if ( ! $this->control_plane->acquire_session_lane( $session_id, $lease_ttl ) ) {
                    continue;
                }
                $session_lock_acquired = true;

                $entry = (array) $this->control_plane->claim_next_actor_message( $session_id, $lease_ttl );
                if ( empty( $entry['id'] ) ) {
                    continue;
                }

                $validation = $this->validate_actor_message_entry( $session_id, $entry );
                if ( empty( $validation['success'] ) ) {
                    $execution_failed = true;
                    $error            = ! empty( $validation['error'] ) ? (string) $validation['error'] : 'actor_entry_validation_failed';
                    $errors[]         = $error;
                    $this->requeue_failed_active_message( $session_id, $entry, new RuntimeException( $error ) );
                    continue;
                }
                if ( array_key_exists( 'valid', $validation ) && empty( $validation['valid'] ) ) {
                    continue;
                }

                if ( $this->actor_entry_has_pending_admin_ui_failure( $entry ) ) {
                    $processed_sessions++;
                    $failure_reason = (string) ( $entry['admin_ui_failure_reason'] ?? 'Admin UI actor recovery failed.' );
                    $failure_outcome = $this->fail_or_defer_bound_admin_ui_job( $session_id, $entry, $failure_reason );
                    if ( 'terminalized' === $failure_outcome ) {
                        $processed_messages++;
                    } else {
                        $execution_failed = true;
                        $errors[] = 'Admin UI actor failure transition was deferred for retry.';
                        if ( 'failed' === $failure_outcome ) {
                            $errors[] = 'Admin UI actor failure recovery could not be persisted safely.';
                        }
                    }
                    continue;
                }

                $processed_sessions++;
                $processed_messages++;

                $result        = $this->run_message( $session_id, $entry );
                if ( ! empty( $result['deferred'] ) ) {
                    $execution_failed = true;
                    $processed_messages = max( 0, $processed_messages - 1 );
                    if (
                        ! method_exists( $this->control_plane, 'defer_active_actor_message' )
                        || ! $this->control_plane->defer_active_actor_message(
                            $session_id,
                            (string) $entry['id'],
                            max( 1, intval( $result['defer_seconds'] ?? 5 ) )
                        )
                    ) {
                        $errors[] = 'Actor message could not be deferred safely.';
                    }
                    continue;
                }
                $record_result = $this->record_actor_message_result( $session_id, $entry, $result );
                if ( $this->actor_entry_requires_scheduled_result( $entry ) && empty( $record_result['success'] ) ) {
                    $execution_failed = true;
                    $error            = ! empty( $record_result['error'] )
                        ? (string) $record_result['error']
                        : ( ! empty( $record_result['reason'] ) ? (string) $record_result['reason'] : 'scheduled_actor_result_record_failed' );
                    $errors[]         = $error;
                    if ( $this->actor_entry_is_bound_admin_ui_job( $entry ) ) {
                        $failure_outcome = $this->fail_or_defer_bound_admin_ui_job( $session_id, $entry, $error );
                        if ( 'terminalized' === $failure_outcome ) {
                            $execution_failed = false;
                        } else {
                            $processed_messages = max( 0, $processed_messages - 1 );
                            if ( 'failed' === $failure_outcome ) {
                                $errors[] = 'Admin UI actor failure recovery could not be persisted safely.';
                            }
                        }
                    } else {
                        $this->requeue_failed_active_message( $session_id, $entry, new RuntimeException( $error ) );
                    }
                    continue;
                }
                if ( ! empty( $result['continuation'] ) && is_array( $result['continuation'] ) ) {
                    $checkpointed = $this->checkpoint_continuation( $session_id, $entry, $result['continuation'] );
                    $cancelled_during_run = $this->entry_was_cancelled_during_run( $session_id, $entry );
                    if ( ! $checkpointed && ! $cancelled_during_run ) {
                        $fallback_enqueued = $this->enqueue_continuation( $session_id, $entry, $result['continuation'] );
                    }
                    if ( ! $checkpointed && empty( $fallback_enqueued ) ) {
                        $execution_failed = true;
                        $error            = $cancelled_during_run
                            ? 'Soft-deadline continuation was cancelled before it could be checkpointed.'
                            : 'Soft-deadline continuation could not be checkpointed or enqueued.';
                        $errors[]         = $error;
                        if ( $this->actor_entry_is_bound_admin_ui_job( $entry ) ) {
                            $failure_outcome = $this->fail_or_defer_bound_admin_ui_job( $session_id, $entry, $error );
                            if ( 'terminalized' === $failure_outcome ) {
                                $execution_failed = false;
                            } else {
                                $processed_messages = max( 0, $processed_messages - 1 );
                                if ( 'failed' === $failure_outcome ) {
                                    $errors[] = 'Admin UI actor failure recovery could not be persisted safely.';
                                }
                            }
                        } else {
                            $this->requeue_failed_active_message( $session_id, $entry, new RuntimeException( $error ) );
                        }
                        continue;
                    }
                    if ( $checkpointed || ! empty( $fallback_enqueued ) ) {
                        $this->request_continuation_wake( $session_id, $entry, $checkpointed ? 'checkpointed' : 'fallback_enqueued' );
                    }
                }
                if ( empty( $result['success'] ) && ! empty( $result['error'] ) ) {
                    $error    = (string) $result['error'];
                    $errors[] = $error;
                    if ( $this->actor_entry_is_bound_admin_ui_job( $entry ) && empty( $result['continuation'] ) ) {
                        $execution_failed = true;
                        $failure_outcome  = $this->fail_or_defer_bound_admin_ui_job( $session_id, $entry, $error );
                        if ( 'terminalized' === $failure_outcome ) {
                            $execution_failed = false;
                        } else {
                            $processed_messages = max( 0, $processed_messages - 1 );
                            if ( 'failed' === $failure_outcome ) {
                                $errors[] = 'Admin UI actor failure recovery could not be persisted safely.';
                            }
                        }
					} elseif ( empty( $result['continuation'] ) && ! empty( $result['successful_write_execution'] ) ) {
						// A retry could duplicate a mutation whose execution is
						// already proven even though final-response verification
						// failed. Complete the actor entry and keep the evidence.
						$execution_failed = false;
					} elseif ( empty( $result['continuation'] ) ) {
						$execution_failed = true;
						$processed_messages = max( 0, $processed_messages - 1 );
						$this->requeue_failed_active_message( $session_id, $entry, new RuntimeException( $error ) );
                    }
                }
            } catch ( Throwable $e ) {
                $execution_failed = true;
                $errors[] = $e->getMessage();
                error_log( '[Niwa AI Actor Runtime] ' . $e->getMessage() );
				if ( $this->actor_entry_is_bound_admin_ui_job( $entry ) ) {
					$failure_outcome = $this->fail_or_defer_bound_admin_ui_job( $session_id, $entry, $e->getMessage() );
					if ( 'terminalized' === $failure_outcome ) {
						$execution_failed = false;
					} else {
                        $processed_messages = max( 0, $processed_messages - 1 );
                        if ( 'failed' === $failure_outcome ) {
                            $errors[] = 'Admin UI actor failure recovery could not be persisted safely.';
                        }
					}
				} else {
					$this->requeue_failed_active_message( $session_id, $entry, $e );
				}
            } finally {
                if ( ! empty( $entry['id'] ) && ! $checkpointed && ! $execution_failed ) {
                    if ( ! $this->control_plane->complete_actor_message( $session_id, (string) $entry['id'] ) ) {
                        $errors[] = 'Actor message completion could not be persisted safely.';
                        if (
                            $this->actor_entry_is_bound_admin_ui_job( $entry )
                            && $this->defer_bound_admin_ui_failure( $session_id, $entry, 'Admin UI actor completion state could not be persisted.' )
                        ) {
                            $processed_messages = max( 0, $processed_messages - 1 );
                        }
                    }
                }
                if ( $session_lock_acquired ) {
                    $this->control_plane->release_session_lane( $session_id );
                }
                $this->control_plane->release_session_lane( $run_lock );
            }
        }

        return array(
            'success'            => empty( $errors ),
            'processed_sessions' => $processed_sessions,
            'processed_messages' => $processed_messages,
            'pending'            => $this->has_pending_messages(),
            'errors'             => $errors,
        );
    }

    public function has_pending_messages() {
        return $this->control_plane
            && method_exists( $this->control_plane, 'has_pending_actor_messages' )
            && ! empty( $this->control_plane->has_pending_actor_messages() );
    }

    private function get_runtime_lease_ttl() {
        return max( self::ACTOR_MESSAGE_LEASE_TTL, $this->turn_time_limit + 120 );
    }

    private function run_message( $session_id, $entry ) {
        if ( ! is_callable( $this->message_runner ) ) {
            return array(
                'success' => false,
                'error'   => 'Actor runtime runner callback is not available.',
            );
        }

        $result = call_user_func( $this->message_runner, $session_id, $entry );
        return is_array( $result ) ? $result : array();
    }

    private function record_actor_message_result( $session_id, $entry, $result ) {
        if ( empty( $this->callbacks['record_scheduled_actor_result'] ) || ! is_callable( $this->callbacks['record_scheduled_actor_result'] ) ) {
            if ( $this->actor_entry_requires_scheduled_result( $entry ) ) {
                return array(
                    'success' => false,
                    'error'   => 'scheduled_actor_result_callback_missing',
                );
            }

            return array(
                'success' => true,
                'ignored' => true,
                'reason'  => 'scheduled_actor_result_callback_missing',
            );
        }

        try {
            $recorded = call_user_func(
                $this->callbacks['record_scheduled_actor_result'],
                (string) $session_id,
                is_array( $entry ) ? $entry : array(),
                is_array( $result ) ? $result : array()
            );

            return is_array( $recorded )
                ? $recorded
                : array(
                    'success' => true,
                );
        } catch ( Throwable $e ) {
            error_log( '[Niwa AI Actor Runtime] Failed to record scheduled actor result: ' . $e->getMessage() );

            return array(
                'success' => false,
                'error'   => $e->getMessage(),
            );
        }
    }

    private function validate_actor_message_entry( $session_id, $entry ) {
        if ( ! $this->actor_entry_requires_scheduled_result( $entry ) ) {
            return array(
                'success' => true,
                'valid'   => true,
            );
        }

        if ( empty( $this->callbacks['validate_scheduled_actor_entry'] ) || ! is_callable( $this->callbacks['validate_scheduled_actor_entry'] ) ) {
            return array(
                'success' => false,
                'error'   => 'scheduled_actor_validation_callback_missing',
            );
        }

        try {
            $validated = call_user_func(
                $this->callbacks['validate_scheduled_actor_entry'],
                (string) $session_id,
                is_array( $entry ) ? $entry : array()
            );

            return is_array( $validated )
                ? $validated
                : array(
                    'success' => true,
                    'valid'   => true,
                );
        } catch ( Throwable $e ) {
            error_log( '[Niwa AI Actor Runtime] Failed to validate scheduled actor entry: ' . $e->getMessage() );

            return array(
                'success' => false,
                'error'   => $e->getMessage(),
            );
        }
    }

    private function actor_entry_requires_scheduled_result( $entry ) {
        $entry = is_array( $entry ) ? $entry : array();
        if ( ! empty( $entry['message']['scheduled_job'] ) && is_array( $entry['message']['scheduled_job'] ) ) {
            return true;
        }
        if ( ! empty( $entry['context_payload']['scheduled_actor_result_required'] ) ) {
            return true;
        }
        if ( ! empty( $entry['context_payload']['scheduled_job'] ) && is_array( $entry['context_payload']['scheduled_job'] ) ) {
            return true;
        }
        if ( ! empty( $entry['continuation_context']['scheduled_job'] ) && is_array( $entry['continuation_context']['scheduled_job'] ) ) {
            return true;
        }

        return false;
    }

	private function actor_entry_is_bound_admin_ui_job( $entry ) {
		$entry   = is_array( $entry ) ? $entry : array();
		$message = isset( $entry['message'] ) && is_array( $entry['message'] ) ? $entry['message'] : array();
		$context = isset( $entry['context_payload'] ) && is_array( $entry['context_payload'] ) ? $entry['context_payload'] : array();
		return 'admin_ui' === sanitize_key( (string) ( $message['channel'] ?? '' ) )
			&& '' !== trim( (string) ( $context['admin_ui_job_id'] ?? '' ) );
	}

    private function actor_entry_has_pending_admin_ui_failure( $entry ) {
        return $this->actor_entry_is_bound_admin_ui_job( $entry )
            && ! empty( $entry['admin_ui_failure_pending'] );
    }

    private function fail_bound_admin_ui_job( $session_id, $entry, $error ) {
        if ( empty( $this->callbacks['fail_admin_ui_actor_job'] ) || ! is_callable( $this->callbacks['fail_admin_ui_actor_job'] ) ) {
            return false;
        }

        try {
            return ! empty( call_user_func( $this->callbacks['fail_admin_ui_actor_job'], $session_id, $entry, $error ) );
        } catch ( Throwable $callback_error ) {
            error_log( '[Niwa AI Actor Runtime] Failed to terminalize admin UI actor job: ' . $callback_error->getMessage() );
            return false;
        }
    }

    private function fail_or_defer_bound_admin_ui_job( $session_id, $entry, $error ) {
        if ( $this->fail_bound_admin_ui_job( $session_id, $entry, $error ) ) {
            return 'terminalized';
        }

        return $this->defer_bound_admin_ui_failure( $session_id, $entry, $error )
            ? 'deferred'
            : 'failed';
    }

    private function defer_bound_admin_ui_failure( $session_id, $entry, $error ) {
        if ( ! $this->control_plane || ! method_exists( $this->control_plane, 'defer_active_actor_message' ) ) {
            return false;
        }

        $attempts = max( 1, intval( $entry['admin_ui_failure_attempts'] ?? 0 ) + 1 );
        return ! empty( $this->control_plane->defer_active_actor_message(
            $session_id,
            (string) ( $entry['id'] ?? '' ),
            min( 30, $attempts * 5 ),
            array(
                'admin_ui_failure_pending'  => true,
                'admin_ui_failure_attempts' => $attempts,
                'admin_ui_failure_reason'   => $error,
            )
        ) );
    }

    private function requeue_failed_active_message( $session_id, $entry, Throwable $exception ) {
        if ( ! $this->control_plane || ! method_exists( $this->control_plane, 'requeue_active_actor_message' ) ) {
            return;
        }

        $entry      = is_array( $entry ) ? $entry : array();
        $message_id = isset( $entry['id'] ) ? trim( (string) $entry['id'] ) : '';
        if ( '' === $session_id || '' === $message_id ) {
            return;
        }

        try {
            $this->control_plane->requeue_active_actor_message(
                $session_id,
                $message_id,
                'Actor runtime runner threw before completion: ' . $exception->getMessage()
            );
        } catch ( Throwable $requeue_error ) {
            error_log( '[Niwa AI Actor Runtime] Failed to requeue thrown actor message: ' . $requeue_error->getMessage() );
        }
    }

    private function checkpoint_continuation( $session_id, $entry, $continuation ) {
        if ( ! $this->control_plane || ! method_exists( $this->control_plane, 'checkpoint_active_actor_message' ) ) {
            return false;
        }

        $entry      = is_array( $entry ) ? $entry : array();
        $message_id = isset( $entry['id'] ) ? (string) $entry['id'] : '';
        if ( '' === $session_id || '' === $message_id || empty( $continuation ) || ! is_array( $continuation ) ) {
            return false;
        }

        $result = (array) $this->control_plane->checkpoint_active_actor_message(
            $session_id,
            $message_id,
            $continuation,
            isset( $continuation['cancelled_reason'] ) ? (string) $continuation['cancelled_reason'] : 'soft_deadline'
        );

        return ! empty( $result['success'] );
    }

    private function entry_was_cancelled_during_run( $session_id, $entry ) {
        if ( ! $this->control_plane || ! method_exists( $this->control_plane, 'is_actor_message_cancelled' ) ) {
            return false;
        }

        $entry      = is_array( $entry ) ? $entry : array();
        $message_id = isset( $entry['id'] ) ? (string) $entry['id'] : '';
        if ( '' === $message_id ) {
            return false;
        }

        return ! empty(
            $this->control_plane->is_actor_message_cancelled(
                $session_id,
                $message_id,
                isset( $entry['cancel_generation'] ) ? intval( $entry['cancel_generation'] ) : 0
            )
        );
    }

    private function enqueue_continuation( $session_id, $entry, $continuation ) {
        if ( ! $this->control_plane || ! method_exists( $this->control_plane, 'enqueue_actor_message' ) ) {
            return false;
        }

        $entry        = is_array( $entry ) ? $entry : array();
        $entry_message = isset( $entry['message'] ) && is_array( $entry['message'] ) ? $entry['message'] : array();
        $chat_id = isset( $continuation['chat_id'] ) ? (string) $continuation['chat_id'] : '';
        if ( '' === $session_id || '' === $chat_id ) {
            return false;
        }

        $origin_update_id  = intval( isset( $continuation['update_id'] ) ? $continuation['update_id'] : ( isset( $entry_message['update_id'] ) ? $entry_message['update_id'] : 0 ) );
        $origin_message_id = intval( isset( $continuation['message_id'] ) ? $continuation['message_id'] : ( isset( $entry_message['message_id'] ) ? $entry_message['message_id'] : 0 ) );
        $root_actor_id     = isset( $entry['continuation_root_actor_message_id'] ) && '' !== trim( (string) $entry['continuation_root_actor_message_id'] )
            ? trim( (string) $entry['continuation_root_actor_message_id'] )
            : ( isset( $entry['id'] ) ? (string) $entry['id'] : '' );

        $queued = $this->control_plane->enqueue_actor_message(
            $session_id,
            array(
                'chat_id'    => $chat_id,
                'channel'    => isset( $continuation['channel'] ) ? sanitize_key( (string) $continuation['channel'] ) : ( isset( $entry_message['channel'] ) ? sanitize_key( (string) $entry_message['channel'] ) : 'telegram' ),
                'message_id' => $origin_message_id,
                'update_id'  => $origin_update_id,
                'text'       => '[continuation]',
                'user_role'  => isset( $continuation['user_role'] ) ? (string) $continuation['user_role'] : 'admin',
            ),
            array(
                'source'               => 'actor_continuation',
                'durable'              => true,
                'context_payload'      => isset( $continuation['context_payload'] ) ? (array) $continuation['context_payload'] : array(),
                'continuation_context' => isset( $continuation['context_snapshot'] ) ? (array) $continuation['context_snapshot'] : array(),
                'continuation_depth'   => intval( isset( $entry['continuation_depth'] ) ? $entry['continuation_depth'] : 0 ) + 1,
                'continuation_root_actor_message_id' => $root_actor_id,
                'continuation_root_update_id' => intval( isset( $entry['continuation_root_update_id'] ) ? $entry['continuation_root_update_id'] : $origin_update_id ),
                'continuation_root_message_id' => intval( isset( $entry['continuation_root_message_id'] ) ? $entry['continuation_root_message_id'] : $origin_message_id ),
                'continuation_parent_actor_message_id' => isset( $entry['id'] ) ? (string) $entry['id'] : '',
            )
        );

        return ! empty( $queued['id'] );
    }

    private function request_continuation_wake( $session_id, $entry, $checkpoint_mode ) {
        if ( empty( $this->callbacks['request_runner_wake'] ) || ! is_callable( $this->callbacks['request_runner_wake'] ) ) {
            return;
        }

        try {
            call_user_func(
                $this->callbacks['request_runner_wake'],
                array(
                    'reason'          => 'soft_deadline_continuation',
                    'session_id'      => (string) $session_id,
                    'actor_id'        => isset( $entry['id'] ) ? (string) $entry['id'] : '',
                    'checkpoint_mode' => (string) $checkpoint_mode,
                )
            );
        } catch ( Throwable $e ) {
            error_log( '[Niwa AI Actor Runtime] Failed to wake soft-deadline continuation: ' . $e->getMessage() );
        }
    }
}
