<?php
/** Durable local bindings for ContentOS Auto queue-v2 jobs. */

defined( 'ABSPATH' ) || exit;

class Costudio_Remote_Jobs {
	public const TABLE = 'costudio_remote_jobs';
	private const OPEN_STATES = [ 'new', 'submitted', 'pending', 'processing', 'transport_wait' ];

	public static function install(): void {
		global $wpdb;
		$table   = $wpdb->prefix . self::TABLE;
		$charset = $wpdb->get_charset_collate();
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		dbDelta( "CREATE TABLE {$table} (
			id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
			production_id BIGINT UNSIGNED NOT NULL,
			production_uid VARCHAR(100) NOT NULL DEFAULT '',
			agent_id TINYINT UNSIGNED NOT NULL,
			attempt_id CHAR(36) NOT NULL,
			idempotency_key CHAR(64) NOT NULL,
			job_id CHAR(36) NOT NULL DEFAULT '',
			poll_token_ciphertext TEXT DEFAULT NULL,
			resume_json TEXT DEFAULT NULL,
			queue_version TINYINT UNSIGNED NOT NULL DEFAULT 2,
			state VARCHAR(24) NOT NULL DEFAULT 'new',
			first_seen_at DATETIME NOT NULL,
			last_poll_at DATETIME DEFAULT NULL,
			terminal_at DATETIME DEFAULT NULL,
			superseded_at DATETIME DEFAULT NULL,
			created_at DATETIME NOT NULL,
			updated_at DATETIME NOT NULL,
			PRIMARY KEY (id),
			UNIQUE KEY attempt_id (attempt_id),
			UNIQUE KEY idempotency_key (idempotency_key),
			KEY current_attempt (production_id, agent_id, state, id),
			KEY job_id (job_id),
			KEY updated_at (updated_at)
		) ENGINE=InnoDB {$charset};" );
	}

	public static function current_or_create( int $production_id, int $agent_id ): array {
		global $wpdb;
		$table = $wpdb->prefix . self::TABLE;
		$row = $wpdb->get_row( $wpdb->prepare(
			"SELECT * FROM {$table} WHERE production_id=%d AND agent_id=%d AND state IN ('new','submitted','pending','processing','transport_wait') ORDER BY id DESC LIMIT 1",
			$production_id, $agent_id
		), ARRAY_A );
		if ( $row ) {
			return self::hydrate( $row );
		}
		$attempt_id = wp_generate_uuid4();
		$production_uid = class_exists( 'Costudio_Production' ) ? Costudio_Production::tracking_uid_for( $production_id ) : '';
		$idempotency_key = hash( 'sha256', $production_uid . '|' . $production_id . '|' . $agent_id . '|' . $attempt_id );
		$now = current_time( 'mysql', true );
		$wpdb->insert( $table, [
			'production_id'  => $production_id,
			'production_uid' => $production_uid,
			'agent_id'       => $agent_id,
			'attempt_id'     => $attempt_id,
			'idempotency_key'=> $idempotency_key,
			'state'          => 'new',
			'first_seen_at'  => $now,
			'created_at'     => $now,
			'updated_at'     => $now,
		] );
		return self::hydrate( (array) $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE attempt_id=%s", $attempt_id ), ARRAY_A ) );
	}

	public static function current( int $production_id, int $agent_id = 0 ): ?array {
		global $wpdb;
		$table = $wpdb->prefix . self::TABLE;
		$sql = "SELECT * FROM {$table} WHERE production_id=%d";
		$args = [ $production_id ];
		if ( $agent_id > 0 ) {
			$sql .= ' AND agent_id=%d';
			$args[] = $agent_id;
		}
		$sql .= " AND state IN ('new','submitted','pending','processing','transport_wait') ORDER BY id DESC LIMIT 1";
		$row = $wpdb->get_row( $wpdb->prepare( $sql, $args ), ARRAY_A );
		return $row ? self::hydrate( $row ) : null;
	}

	public static function bind( string $attempt_id, string $job_id, string $poll_token, int $queue_version = 2 ): void {
		global $wpdb;
		$wpdb->update( $wpdb->prefix . self::TABLE, [
			'job_id'                 => $job_id,
			'poll_token_ciphertext'  => Costudio_Standalone_Crypto::encrypt( $poll_token ),
			'queue_version'           => $queue_version,
			'state'                   => 'submitted',
			'updated_at'              => current_time( 'mysql', true ),
		], [ 'attempt_id' => $attempt_id ] );
	}

	public static function observe( string $attempt_id, string $state ): void {
		global $wpdb;
		$terminal = in_array( $state, [ 'completed', 'failed', 'cancelled', 'uncertain', 'superseded' ], true );
		$data = [
			'state'        => sanitize_key( $state ),
			'last_poll_at' => current_time( 'mysql', true ),
			'updated_at'   => current_time( 'mysql', true ),
		];
		if ( $terminal ) {
			$data['terminal_at'] = current_time( 'mysql', true );
		}
		$wpdb->update( $wpdb->prefix . self::TABLE, $data, [ 'attempt_id' => $attempt_id ] );
	}

	public static function save_resume_state( string $attempt_id, array $state ): void {
		global $wpdb;
		$safe = [
			'consecutive_5xx' => max( 0, (int) ( $state['consecutive_5xx'] ?? 0 ) ),
			'sleep_seconds'   => max( 1, (int) ( $state['sleep_seconds'] ?? 15 ) ),
			'last_status'     => sanitize_key( (string) ( $state['last_status'] ?? 'pending' ) ),
			'first_seen_at'   => max( 0, (int) ( $state['first_seen_at'] ?? time() ) ),
		];
		$wpdb->update( $wpdb->prefix . self::TABLE, [
			'resume_json' => wp_json_encode( $safe ),
			'updated_at'  => current_time( 'mysql', true ),
		], [ 'attempt_id' => $attempt_id ] );
	}

	public static function mark_transport_wait( string $attempt_id ): void {
		self::observe( $attempt_id, 'transport_wait' );
	}

	public static function supersede_from( int $production_id, int $from_agent ): void {
		global $wpdb;
		$table = $wpdb->prefix . self::TABLE;
		$now   = current_time( 'mysql', true );
		$states= "'new','submitted','pending','processing','transport_wait'";
		$wpdb->query( $wpdb->prepare(
			"UPDATE {$table} SET state='superseded', superseded_at=%s, terminal_at=%s, updated_at=%s
			 WHERE production_id=%d AND agent_id >= %d AND state IN ({$states})",
			$now, $now, $now, $production_id, $from_agent
		) );
	}

	public static function delete_for_production( int $production_id ): void {
		global $wpdb;
		$wpdb->delete( $wpdb->prefix . self::TABLE, [ 'production_id' => $production_id ] );
	}

	public static function has_live_job( int $production_id ): bool {
		return null !== self::current( $production_id );
	}

	/** Best-effort remote cancellation; local cancellation must never block on it. */
	public static function request_cancel( int $production_id ): bool {
		$current = self::current( $production_id );
		if ( ! $current || empty( $current['job_id'] ) || empty( $current['poll_token'] ) || 2 !== (int) $current['queue_version'] ) {
			return false;
		}
		$url = trailingslashit( COSTUDIO_API_URL ) . 'wp-json/contentosapp-api/v1/ai/job/' . rawurlencode( (string) $current['job_id'] ) . '/cancel';
		$response = wp_remote_post( $url, [
			'timeout'   => 10,
			'sslverify' => function_exists( 'costudio_sslverify' ) ? costudio_sslverify( 'remote_job_cancel' ) : true,
			'headers'   => [
				'Accept'           => 'application/json',
				'X-FOS-Poll-Token' => (string) $current['poll_token'],
			],
		] );
		if ( is_wp_error( $response ) ) {
			return false;
		}
		$ok = (int) wp_remote_retrieve_response_code( $response ) >= 200 && (int) wp_remote_retrieve_response_code( $response ) < 300;
		if ( $ok ) {
			self::observe( (string) $current['attempt_id'], 'cancelled' );
		}
		return $ok;
	}

	private static function hydrate( array $row ): array {
		$row['poll_token'] = Costudio_Standalone_Crypto::decrypt( (string) ( $row['poll_token_ciphertext'] ?? '' ) );
		$resume = json_decode( (string) ( $row['resume_json'] ?? '' ), true );
		$row['resume'] = is_array( $resume ) ? $resume : [];
		return $row;
	}
}
