<?php
/**
 * AjaxController — Ghost Purge AJAX endpoints.
 *
 * Endpoints (all require `delete_users` + nonce):
 *   - platform_helm_gp_init     Build candidate list, persist job, return totals
 *   - platform_helm_gp_batch    Delete next N users in the job (wp_delete_user)
 *   - platform_helm_gp_dry_run  Return candidate users without deleting anything
 *
 * Every endpoint validates the role argument against an allow-list derived
 * from wp_roles()->get_names() with `administrator` explicitly excluded, and
 * verifies the months argument is within [1, 120].
 *
 * @package Platform\Helm\Modules\GhostPurge
 */

declare( strict_types = 1 );

namespace Platform\Helm\Modules\GhostPurge;

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

final class AjaxController {

	public const NONCE_ACTION = 'platform_helm_gp';

	private const MIN_MONTHS = 1;
	private const MAX_MONTHS = 120; // 10 years.

	public function __construct( private UserFinder $finder ) {}

	public function register_hooks(): void {
		add_action( 'wp_ajax_platform_helm_gp_init',    [ $this, 'ajax_init' ] );
		add_action( 'wp_ajax_platform_helm_gp_batch',   [ $this, 'ajax_batch' ] );
		add_action( 'wp_ajax_platform_helm_gp_dry_run', [ $this, 'ajax_dry_run' ] );
	}

	/* ----------------------------------------------------------------- */

	public function ajax_init(): void {
		$this->verify();

		$role     = $this->sanitize_role();
		$months   = $this->sanitize_months();
		$reassign = $this->sanitize_reassign();

		$user_ids = $this->finder->find_inactive_user_ids( $role, $months );

		$job_id  = wp_generate_password( 16, false, false );
		$payload = [
			'role'      => $role,
			'months'    => $months,
			'reassign'  => $reassign,
			'user_ids'  => array_values( $user_ids ),
			'cursor'    => 0,
			'created_at' => time(),
			'totals'    => [
				'deleted' => 0,
				'failed'  => 0,
			],
		];
		set_transient(
			GhostPurgeModule::JOB_TRANSIENT . $job_id,
			$payload,
			GhostPurgeModule::JOB_TTL_SECONDS
		);

		wp_send_json_success( [
			'job_id'      => $job_id,
			'total_users' => count( $user_ids ),
			'batch_size'  => GhostPurgeModule::BATCH_SIZE,
			'role'        => $role,
			'months'      => $months,
		] );
	}

	public function ajax_batch(): void {
		$this->verify();

		$job_id = isset( $_POST['job_id'] ) ? sanitize_key( wp_unslash( (string) $_POST['job_id'] ) ) : '';
		if ( '' === $job_id ) {
			wp_send_json_error( [ 'message' => __( 'Missing job id.', 'platform-helm' ) ], 400 );
		}

		$key  = GhostPurgeModule::JOB_TRANSIENT . $job_id;
		$data = get_transient( $key );
		if ( ! is_array( $data ) || ! isset( $data['user_ids'], $data['cursor'], $data['reassign'] ) ) {
			wp_send_json_error( [ 'message' => __( 'Job expired or not found. Please restart.', 'platform-helm' ) ], 410 );
		}

		$cursor    = (int) $data['cursor'];
		$user_ids  = (array) $data['user_ids'];
		$reassign  = (int) $data['reassign'];
		$slice     = array_slice( $user_ids, $cursor, GhostPurgeModule::BATCH_SIZE );
		$total     = count( $user_ids );

		if ( function_exists( 'set_time_limit' ) ) {
			@set_time_limit( 120 );
		}
		if ( ! function_exists( 'wp_delete_user' ) ) {
			require_once ABSPATH . 'wp-admin/includes/user.php';
		}

		$batch_result = [
			'deleted' => 0,
			'failed'  => 0,
			'errors'  => [],
		];

		foreach ( $slice as $user_id ) {
			$user_id = (int) $user_id;
			if ( $user_id <= 0 ) {
				continue;
			}
			$user = get_userdata( $user_id );
			if ( ! $user ) {
				$batch_result['failed']++;
				continue;
			}
			// Defensive: never delete administrators.
			if ( in_array( 'administrator', (array) $user->roles, true ) ) {
				$batch_result['failed']++;
				$batch_result['errors'][] = sprintf( 'Skipped admin user %d.', $user_id );
				continue;
			}

			$ok = wp_delete_user( $user_id, $reassign > 0 ? $reassign : null );
			if ( $ok ) {
				$batch_result['deleted']++;
			} else {
				$batch_result['failed']++;
				$batch_result['errors'][] = sprintf( 'wp_delete_user(%d) returned false.', $user_id );
			}
		}

		$new_cursor                     = $cursor + count( $slice );
		$data['cursor']                  = $new_cursor;
		$data['totals']['deleted']      += $batch_result['deleted'];
		$data['totals']['failed']       += $batch_result['failed'];

		$done = ( $new_cursor >= $total );
		if ( $done ) {
			delete_transient( $key );
		} else {
			set_transient( $key, $data, GhostPurgeModule::JOB_TTL_SECONDS );
		}

		wp_send_json_success( [
			'job_id'        => $job_id,
			'cursor'        => $new_cursor,
			'total'         => $total,
			'done'          => $done,
			'batch_deleted' => $batch_result['deleted'],
			'batch_failed'  => $batch_result['failed'],
			'batch_errors'  => $batch_result['errors'],
			'totals'        => $data['totals'],
		] );
	}

	public function ajax_dry_run(): void {
		$this->verify();

		$role   = $this->sanitize_role();
		$months = $this->sanitize_months();

		$user_ids = $this->finder->find_inactive_user_ids( $role, $months );

		$preview = [];
		$cap     = 50; // Preview cap — full count is in `total`.
		foreach ( array_slice( $user_ids, 0, $cap ) as $id ) {
			$u = get_userdata( (int) $id );
			if ( ! $u ) {
				continue;
			}
			$last_login = (int) get_user_meta( $u->ID, GhostPurgeModule::LAST_LOGIN_META, true );
			$preview[] = [
				'id'              => (int) $u->ID,
				'login'           => (string) $u->user_login,
				'email'           => (string) $u->user_email,
				'display_name'    => (string) $u->display_name,
				'roles'           => (array) $u->roles,
				'user_registered' => (string) $u->user_registered,
				'last_login'      => $last_login > 0 ? wp_date( 'Y-m-d H:i', $last_login ) : '',
			];
		}

		wp_send_json_success( [
			'total'   => count( $user_ids ),
			'preview' => $preview,
			'capped'  => count( $user_ids ) > $cap,
			'role'    => $role,
			'months'  => $months,
		] );
	}

	/* ----------------------------------------------------------------- */

	private function verify(): void {
		check_ajax_referer( self::NONCE_ACTION, 'nonce' );
		\Platform\Helm\Core\Authorization::require_master_admin_ajax();
	}

	private function sanitize_role(): string {
		$role = isset( $_POST['role'] ) ? sanitize_key( wp_unslash( (string) $_POST['role'] ) ) : '';
		$allowed = array_keys( $this->finder->purgeable_roles() );
		if ( ! in_array( $role, $allowed, true ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid role.', 'platform-helm' ) ], 400 );
		}
		return $role;
	}

	private function sanitize_months(): int {
		$months = isset( $_POST['months'] ) ? (int) $_POST['months'] : GhostPurgeModule::DEFAULT_MONTHS;
		if ( $months < self::MIN_MONTHS || $months > self::MAX_MONTHS ) {
			wp_send_json_error( [
				'message' => sprintf(
					/* translators: 1: min months, 2: max months */
					__( 'Months must be between %1$d and %2$d.', 'platform-helm' ),
					self::MIN_MONTHS,
					self::MAX_MONTHS
				),
			], 400 );
		}
		return $months;
	}

	private function sanitize_reassign(): int {
		$reassign = isset( $_POST['reassign'] ) ? (int) $_POST['reassign'] : 0;
		if ( $reassign <= 0 ) {
			return 0;
		}
		$u = get_userdata( $reassign );
		if ( ! $u ) {
			return 0;
		}
		return (int) $u->ID;
	}
}
