<?php

namespace WeDevs\AppBridge\Core\Auth;

use WP_Error;
use WP_User;

/**
 * OTP-based password reset service.
 *
 * Issues a 6-digit OTP, stores its hash in user meta, and verifies it on
 * submission. Verified OTPs are exchanged for a standard WordPress
 * password-reset key so the rest of the flow uses core primitives
 * (`reset_password()`).
 */
class PasswordResetOtp {

	public const META_KEY = '_app_bridge_password_reset_otp';

	public const TTL = 10 * MINUTE_IN_SECONDS;

	public const RESEND_COOLDOWN = 60;

	public const MAX_ATTEMPTS = 5;

	/**
	 * Issue an OTP for the given user.
	 *
	 * @return array{otp:string,expires_at:int}|WP_Error
	 */
	public function issue( WP_User $user ) {
		$existing = $this->get_record( $user->ID );

		if ( $existing && ( time() - (int) ( $existing['issued_at'] ?? 0 ) ) < self::RESEND_COOLDOWN ) {
			return new WP_Error(
				'app_bridge_otp_cooldown',
				__( 'Please wait before requesting another code.', 'app-bridge' ),
				[ 'status' => 429 ]
			);
		}

		$otp        = $this->generate_code();
		$issued_at  = time();
		$expires_at = $issued_at + self::TTL;

		update_user_meta(
			$user->ID,
			self::META_KEY,
			[
				'hash'       => wp_hash_password( $otp ),
				'issued_at'  => $issued_at,
				'expires_at' => $expires_at,
				'attempts'   => 0,
			]
		);

		return [
			'otp'        => $otp,
			'expires_at' => $expires_at,
		];
	}

	/**
	 * Verify a submitted OTP and consume it on success.
	 *
	 * @return true|WP_Error
	 */
	public function verify( WP_User $user, string $otp ) {
		$record = $this->get_record( $user->ID );

		if ( ! $record ) {
			return new WP_Error(
				'app_bridge_otp_not_found',
				__( 'No active reset code. Request a new one.', 'app-bridge' ),
				[ 'status' => 400 ]
			);
		}

		if ( time() > (int) $record['expires_at'] ) {
			$this->revoke( $user->ID );

			return new WP_Error(
				'app_bridge_otp_expired',
				__( 'Reset code has expired. Request a new one.', 'app-bridge' ),
				[ 'status' => 400 ]
			);
		}

		$attempts = (int) ( $record['attempts'] ?? 0 );
		if ( $attempts >= self::MAX_ATTEMPTS ) {
			$this->revoke( $user->ID );

			return new WP_Error(
				'app_bridge_otp_too_many_attempts',
				__( 'Too many invalid attempts. Request a new code.', 'app-bridge' ),
				[ 'status' => 429 ]
			);
		}

		if ( ! wp_check_password( $otp, $record['hash'], $user->ID ) ) {
			$record['attempts'] = $attempts + 1;
			update_user_meta( $user->ID, self::META_KEY, $record );

			return new WP_Error(
				'app_bridge_otp_invalid',
				__( 'Invalid reset code.', 'app-bridge' ),
				[ 'status' => 400 ]
			);
		}

		$this->revoke( $user->ID );

		return true;
	}

	public function revoke( int $user_id ): void {
		delete_user_meta( $user_id, self::META_KEY );
	}

	/**
	 * @return array<string,mixed>|null
	 */
	private function get_record( int $user_id ): ?array {
		$record = get_user_meta( $user_id, self::META_KEY, true );

		if ( ! is_array( $record ) || empty( $record['hash'] ) ) {
			return null;
		}

		return $record;
	}

	private function generate_code(): string {
		return str_pad( (string) wp_rand( 0, 999999 ), 6, '0', STR_PAD_LEFT );
	}
}
