<?php

declare(strict_types=1);

namespace SiteFort\Modules\Cloud\Service;

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

use SiteFort\Modules\AuditLog\AuditLog;
use SiteFort\Modules\Cloud\Services\StatePush;

/**
 * Local authorization gate for Console's connection toggle. Until the gate is allowed from
 * wp-admin, an "on" request from Console is held pending instead of connecting.
 */
class ConsoleGate {

	public const LABEL_NOT_CONNECTED = 'Not Connected';
	public const LABEL_WAITING_FOR_APPROVAL = 'Waiting for Approval';
	public const LABEL_ALLOWED_NOT_CONNECTED = 'Enabled, Not Connected';
	public const LABEL_CONNECTED = 'Connected';

	/**
	 * @return array{gate_allowed:bool,pending_request:bool,connected:bool,last_request_at:int,effective_state:string,label:string}
	 */
	public static function status(): array {
		$settings = CloudSettings::get_all();

		$gate_allowed     = ! empty( $settings['console_gate_allowed'] );
		// Enforced on read so a racing command write cannot leave the pair inconsistent.
		$pending_request  = ! $gate_allowed && ! empty( $settings['console_pending_request'] );
		$connected        = ! empty( $settings['console_enabled'] );
		$last_request_at  = absint( $settings['console_last_request_at'] ?? 0 );
		$effective_state  = ( $gate_allowed && $connected ) ? 'on' : 'off';

		if ( ! $gate_allowed && ! $pending_request ) {
			$label = self::LABEL_NOT_CONNECTED;
		} elseif ( ! $gate_allowed && $pending_request ) {
			$label = self::LABEL_WAITING_FOR_APPROVAL;
		} elseif ( $gate_allowed && ! $connected ) {
			$label = self::LABEL_ALLOWED_NOT_CONNECTED;
		} else {
			$label = self::LABEL_CONNECTED;
		}

		return [
			'gate_allowed'     => $gate_allowed,
			'pending_request'  => $pending_request,
			'connected'        => $connected,
			'last_request_at'  => $last_request_at,
			'effective_state'  => $effective_state,
			'label'            => $label,
		];
	}

	/**
	 * Never reads the gate flag from Console, so no payload can open the gate remotely.
	 *
	 * @return array<string,mixed>
	 */
	public static function handle_console_command( bool $desired_state ): array {
		$settings = CloudSettings::get_all();
		$gate_allowed = ! empty( $settings['console_gate_allowed'] );

		if ( $gate_allowed ) {
			CloudSettings::update( [ 'console_enabled' => $desired_state ] );
			if ( $desired_state ) {
				StatePush::schedule_full_push();
			}
			return self::command_result();
		}

		if ( $desired_state ) {
			if ( empty( $settings['console_pending_request'] ) ) {
				CloudSettings::update( [
					'console_pending_request'  => true,
					'console_last_request_at'  => time(),
				] );
				AuditLog::add_log( 'Console Connection Requested', [], 'warning', 'settings' );
			}
			return self::command_result();
		}

		// Doubles as Console's only way to withdraw a pending request.
		if ( ! empty( $settings['console_pending_request'] ) ) {
			CloudSettings::update( [ 'console_pending_request' => false ] );
			AuditLog::add_log( 'Console Connection Request Withdrawn', [], 'warning', 'settings' );
		}

		return self::command_result();
	}

	/**
	 * Full gate state on every ack, so Console never has to infer it from the command name.
	 *
	 * @return array<string,mixed>
	 */
	private static function command_result(): array {
		$status = self::status();

		return [
			'ok'                      => true,
			'console_enabled'         => $status['connected'],
			'pending'                 => $status['pending_request'],
			'console_gate_allowed'    => $status['gate_allowed'],
			'console_pending_request' => $status['pending_request'],
		];
	}

	/**
	 * @return array{gate_allowed:bool}
	 */
	public static function set_gate( bool $allowed ): array {
		if ( $allowed ) {
			// Nothing else clears pending once the gate is open.
			CloudSettings::update( [
				'console_gate_allowed'    => true,
				'console_pending_request' => false,
			] );
			AuditLog::add_log( 'Console Gate Enabled', [], 'warning', 'settings' );
			StatePush::push_gate_status_after_response();
			return [ 'gate_allowed' => true ];
		}

		CloudSettings::update( [
			'console_gate_allowed'     => false,
			'console_enabled'          => false,
			'console_pending_request'  => false,
		] );
		AuditLog::add_log( 'Console Gate Disabled', [], 'warning', 'settings' );
		StatePush::push_gate_status_after_response();

		return [ 'gate_allowed' => false ];
	}

	/**
	 * @return array{gate_allowed:bool,connected:bool}
	 */
	public static function approve(): array {
		CloudSettings::update( [
			'console_gate_allowed'     => true,
			'console_enabled'          => true,
			'console_pending_request'  => false,
		] );
		AuditLog::add_log( 'Console Connection Approved', [], 'warning', 'settings' );
		// schedule_full_push() alone is cron-dependent and can strand Console on "waiting".
		StatePush::push_gate_status_after_response();
		StatePush::schedule_full_push();

		return [ 'gate_allowed' => true, 'connected' => true ];
	}

	/**
	 * @return array{pending_request:bool}
	 */
	public static function dismiss(): array {
		CloudSettings::update( [ 'console_pending_request' => false ] );
		AuditLog::add_log( 'Console Connection Dismissed', [], 'warning', 'settings' );
		StatePush::push_gate_status_after_response();

		return [ 'pending_request' => false ];
	}

	public static function mark_grandfathered(): void {
		CloudSettings::update( [ 'console_gate_allowed' => true ] );
		AuditLog::add_log( 'Console Connection Grandfathered', [], 'warning', 'settings' );
	}
}
