<?php
/**
 * Account-lifecycle webhook handler (sc-ACCT-DEAUTH, 3.7.5 / 2026-05-24).
 *
 * THE PROBLEM
 * -----------
 * When a vendor's Stripe Connect account is suspended/closed (by Stripe
 * for compliance reasons OR by the vendor themselves via Dashboard), SPP
 * doesn't notice. Subsequent transfers to that destination fail with
 * `account_invalid` and accumulate as status='failed' rows in the
 * transfer log. The order may complete, the customer is charged, but no
 * money reaches the vendor — silent operational failure.
 *
 * Worse: if the vendor re-authorizes later, SPP would silently resume
 * sending without admin awareness.
 *
 * THE FIX
 * -------
 * On `account.application.deauthorized`: mark the account ID in the
 * `spp_deauthorized_accounts` wp_option (a flat array of acct_xxx ids).
 * TransferEngine consults this list before every createTransfer call
 * and skips deauthorized destinations with a clear order note.
 *
 * On `account.application.authorized` (re-auth): remove the account
 * from the list AND log a notice so admins see the lifecycle event.
 *
 * Stored as wp_option (not a schema change) because:
 *   - Account-lifecycle is sparse (most marketplaces never see it).
 *   - The list is small and read-mostly — `get_option` is faster than
 *     a SELECT per createTransfer.
 *   - No migration required.
 *
 * @package SplitPay
 * @since 3.7.5
 */

declare( strict_types=1 );

namespace SplitPay\Integrations\WooCommerce;

defined( 'ABSPATH' ) || exit;

class AccountLifecycleHandler {

	private const OPTION_KEY = 'spp_deauthorized_accounts';

	/**
	 * List of Stripe webhook event types this handler accepts.
	 *
	 * @return string[]
	 */
	public static function handledEventTypes(): array {
		return [
			'account.application.authorized',
			'account.application.deauthorized',
		];
	}

	/**
	 * Process a Stripe account-lifecycle webhook payload.
	 *
	 * @param string $eventType
	 * @param object $eventBody json_decode'd webhook body — Stripe Event object.
	 * @return array{status:string, account_id?:string, message?:string}
	 */
	public static function handle( string $eventType, $eventBody ): array {
		// account.application.* events carry the account ID in `account`
		// (top-level on the event for Connect events) OR `data.object.id`
		// depending on Stripe API version. Cover both.
		$accountId = '';
		if ( isset( $eventBody->account ) && is_string( $eventBody->account ) ) {
			$accountId = $eventBody->account;
		} elseif ( isset( $eventBody->data->object->id ) ) {
			$accountId = (string) $eventBody->data->object->id;
		}
		if ( '' === $accountId ) {
			return [ 'status' => 'error', 'message' => 'Could not resolve account ID from event' ];
		}

		switch ( $eventType ) {
			case 'account.application.deauthorized':
				self::markDeauthorized( $accountId );
				self::logAdmin( sprintf(
					/* translators: %s: Stripe Connect account ID (e.g. acct_xxx) */
					__( '[Split Pay] Connected account %s was deauthorized by Stripe. Subsequent transfers to it will be skipped. Check the vendor\'s Stripe Connect status in the Stripe Dashboard and re-onboard them if needed.', 'bsd-split-pay-stripe-connect-woo' ),
					$accountId
				) );
				return [ 'status' => 'deauthorized', 'account_id' => $accountId ];

			case 'account.application.authorized':
				self::markAuthorized( $accountId );
				self::logAdmin( sprintf(
					/* translators: %s: Stripe Connect account ID */
					__( '[Split Pay] Connected account %s was (re-)authorized. SPP will resume sending transfers to it.', 'bsd-split-pay-stripe-connect-woo' ),
					$accountId
				) );
				return [ 'status' => 'authorized', 'account_id' => $accountId ];

			default:
				return [ 'status' => 'ignored', 'message' => 'Unhandled event type: ' . $eventType ];
		}
	}

	/**
	 * Add an account to the deauthorized list. Idempotent — already-listed
	 * accounts are left in place (no duplicates).
	 */
	public static function markDeauthorized( string $accountId ): void {
		$list = get_option( self::OPTION_KEY, [] );
		if ( ! is_array( $list ) ) {
			$list = [];
		}
		if ( ! in_array( $accountId, $list, true ) ) {
			$list[] = $accountId;
			update_option( self::OPTION_KEY, array_values( array_unique( $list ) ), false );
			// audit P2-3 — observability for the suite's account-deauth
			// spec. Fires only on NEW entries (idempotent — re-receiving
			// the webhook for an already-deauthorized account does not
			// re-increment).
			if ( function_exists( 'do_action' ) ) {
				do_action( 'pts_increment_counter', 'account_deauthorized' );
			}
		}
	}

	/**
	 * Remove an account from the deauthorized list. Used on re-auth.
	 */
	public static function markAuthorized( string $accountId ): void {
		$list = get_option( self::OPTION_KEY, [] );
		if ( ! is_array( $list ) ) {
			return;
		}
		$filtered = array_values( array_filter( $list, fn( $id ) => $id !== $accountId ) );
		if ( count( $filtered ) !== count( $list ) ) {
			update_option( self::OPTION_KEY, $filtered, false );
			// audit P2-3 — re-auth observability for the suite's account-
			// deauth spec.  Mirrors markDeauthorized's counter.
			if ( function_exists( 'do_action' ) ) {
				do_action( 'pts_increment_counter', 'account_reauthorized' );
			}
		}
	}

	/**
	 * Pre-flight check used by TransferEngine before createTransfer.
	 * Returns true if the destination has been marked deauthorized.
	 */
	public static function isDeauthorized( string $accountId ): bool {
		$list = get_option( self::OPTION_KEY, [] );
		return is_array( $list ) && in_array( $accountId, $list, true );
	}

	/**
	 * Whole-list accessor (for the Transfers admin UI to surface a banner).
	 *
	 * @return string[]
	 */
	public static function deauthorizedList(): array {
		$list = get_option( self::OPTION_KEY, [] );
		return is_array( $list ) ? $list : [];
	}

	/**
	 * Log to the WC Stripe log channel via the canonical
	 * \BSD_Split_Pay_Stripe_Connect_Woo\spp_safe_wcs_log helper, which
	 * routes to ::debug() (preferred) or ::log() (legacy) or
	 * error_log() (when WC Stripe isn't loaded at all). Centralizes the
	 * three-tier fallback so a future WC Stripe API change is one fix,
	 * not one per call site.
	 */
	private static function logAdmin( string $message ): void {
		if ( function_exists( '\\BSD_Split_Pay_Stripe_Connect_Woo\\spp_safe_wcs_log' ) ) {
			\BSD_Split_Pay_Stripe_Connect_Woo\spp_safe_wcs_log( $message );
			return;
		}
		// Pre-3.7.4 load order or test bootstrap without the entry file.
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( $message ); } // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
	}
}
