<?php
/**
 * Frontend subsystem bootstrap.
 *
 * @package Donatico\Frontend
 */

declare(strict_types=1);

namespace Donatico\Frontend;

use Donatico\Database\DonationRepository;
use Donatico\Exceptions\StripeException;
use Donatico\Helpers\Currency;
use Donatico\Interfaces\HookableInterface;
use Donatico\Loader;
use Donatico\Models\Donation;
use Donatico\Stripe\CheckoutSession;
use Donatico\Stripe\StripeClient;

/**
 * Bootstraps all frontend components and wires their hooks into the
 * shared Loader. Also owns the donatico_create_checkout AJAX endpoint.
 */
class Frontend implements HookableInterface {

	/** @var Loader */
	private Loader $loader;

	/** @var DonationRepository */
	private DonationRepository $repository;

	/**
	 * @param Loader $loader Shared hook loader.
	 */
	public function __construct( Loader $loader ) {
		$this->loader     = $loader;
		$this->repository = new DonationRepository();
	}

	/**
	 * Instantiate all frontend components and register their hooks.
	 *
	 * @return void
	 */
	public function registerHooks(): void {
		$assets         = new Assets();
		$formRenderer   = new FormRenderer( $this->repository, $assets );
		$widgetRenderer = new WidgetRenderer( $this->repository );
		$shortcodes     = new Shortcodes( $formRenderer, $widgetRenderer, $assets );

		// Asset registration + conditional enqueue via has_shortcode().
		$this->loader->addAction( 'wp_enqueue_scripts', $assets, 'enqueue' );

		// Shortcode registration.
		$this->loader->addAction( 'init', $shortcodes, 'register' );

		// Checkout AJAX — both logged-in and guests.
		$this->loader->addAction( 'wp_ajax_donatico_create_checkout', $this, 'handleCheckout' );
		$this->loader->addAction( 'wp_ajax_nopriv_donatico_create_checkout', $this, 'handleCheckout' );
	}

	// -------------------------------------------------------------------------
	// AJAX: donatico_create_checkout
	// -------------------------------------------------------------------------

	/**
	 * Handle a donation form AJAX submission.
	 *
	 * Validates the request, saves a pending Donation, creates a Stripe
	 * Checkout session, and returns the session URL for the JS redirect.
	 *
	 * @return void
	 */
	public function handleCheckout(): void {
		// 1. Security check.
		$raw_nonce = isset( $_POST['nonce'] ) ? sanitize_key( wp_unslash( $_POST['nonce'] ) ) : '';
		if ( ! wp_verify_nonce( $raw_nonce, 'donatico_checkout' ) ) {
			wp_send_json_error( array( 'message' => __( 'Security check failed. Please refresh and try again.', 'donatico' ) ), 403 );
		}

		// 2. Sanitize inputs.
		$amount_major = isset( $_POST['amount'] ) ? (float) sanitize_text_field( wp_unslash( $_POST['amount'] ) ) : 0.0;
		$donor_name   = isset( $_POST['donor_name'] ) ? sanitize_text_field( wp_unslash( $_POST['donor_name'] ) ) : '';
		$donor_email  = isset( $_POST['donor_email'] ) ? sanitize_email( wp_unslash( $_POST['donor_email'] ) ) : '';
		$message      = isset( $_POST['message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['message'] ) ) : '';
		$is_anonymous = isset( $_POST['is_anonymous'] ) && absint( $_POST['is_anonymous'] ) === 1;
		$currency     = isset( $_POST['currency'] )
			? strtoupper( sanitize_text_field( wp_unslash( $_POST['currency'] ) ) )
			: (string) get_option( 'donatico_currency', 'USD' );

		// 3. Validate.
		if ( $amount_major < 1 ) {
			wp_send_json_error( array( 'message' => __( 'Please enter a valid amount (minimum 1).', 'donatico' ) ), 422 );
		}
		if ( empty( $donor_name ) ) {
			wp_send_json_error( array( 'message' => __( 'Please enter your name.', 'donatico' ) ), 422 );
		}
		if ( empty( $donor_email ) || ! is_email( $donor_email ) ) {
			wp_send_json_error( array( 'message' => __( 'Please enter a valid email address.', 'donatico' ) ), 422 );
		}

		// 4. Persist a pending donation record.
		$donation = new Donation();
		$donation->setDonorName( $donor_name );
		$donation->setDonorEmail( $donor_email );
		$donation->setAmount( Currency::centsFromMajor( $amount_major ) );
		$donation->setCurrency( $currency );
		$donation->setMessage( $message !== '' ? $message : null );
		$donation->setIsAnonymous( $is_anonymous );
		$donation->setStatus( Donation::STATUS_PENDING );

		$donation_id = $this->repository->insert( $donation );

		if ( ! $donation_id ) {
			wp_send_json_error( array( 'message' => __( 'Could not save donation record. Please try again.', 'donatico' ) ), 500 );
		}

		$donation->setId( $donation_id );

		/**
		 * Filter additional column data to write to the donations row.
		 *
		 * Pro and third-party plugins can hook here to add column values
		 * for columns that exist in the donations table but aren't part
		 * of Free's Donation model (e.g. tribute fields added by Pro's
		 * SchemaUpgrader).
		 *
		 * @param array $extra_data  Associative column_name => value pairs
		 *                           to write via repository->update().
		 * @param array $post_data   Sanitized POST data from the form
		 *                           submission (read-only, for hooks
		 *                           to extract field values).
		 * @param int   $donation_id The newly-inserted donation's row ID.
		 */
		$extra_data = apply_filters(
			'donatico_donation_data_before_insert',
			array(),
			array(
				'donor_name'   => $donor_name,
				'donor_email'  => $donor_email,
				'amount'       => $amount_major,
				'currency'     => $currency,
				'message'      => $message,
				'is_anonymous' => $is_anonymous,
			),
			$donation_id
		);

		if ( ! empty( $extra_data ) && is_array( $extra_data ) ) {
			$this->repository->update( $donation_id, $extra_data );
		}

		// 5. Create Stripe Checkout session.
		try {
			$stripe_client = new StripeClient();
			$checkout      = new CheckoutSession( $stripe_client );
			$session       = $checkout->create( $donation );

			// Persist the Stripe session ID so the webhook can find this row.
			$this->repository->update( $donation_id, array( 'stripe_session_id' => $session->id ) );

			wp_send_json_success(
				array(
					'donation_id'  => $donation_id,
					'redirect_url' => $session->url,
				)
			);

		} catch ( StripeException $e ) {
			// Plugin configuration error (missing key, etc.) — show message to user.
			$this->repository->update( $donation_id, array( 'status' => Donation::STATUS_FAILED ) );
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
				error_log( '[Donatico] Stripe config error: ' . $e->getMessage() );
			}
			wp_send_json_error( array( 'message' => $e->getMessage() ), 500 );

		} catch ( \Stripe\Exception\AuthenticationException $e ) {
			$this->repository->update( $donation_id, array( 'status' => Donation::STATUS_FAILED ) );
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
				error_log( '[Donatico] Stripe authentication error: ' . $e->getMessage() );
			}
			wp_send_json_error( array( 'message' => __( 'Payment system configuration error. Please contact the site owner.', 'donatico' ) ), 500 );

		} catch ( \Stripe\Exception\ApiErrorException $e ) {
			$this->repository->update( $donation_id, array( 'status' => Donation::STATUS_FAILED ) );
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
				error_log( '[Donatico] Stripe API error: ' . $e->getMessage() );
			}
			wp_send_json_error( array( 'message' => __( 'Payment system error. Please try again or contact support.', 'donatico' ) ), 500 );

		} catch ( \Exception $e ) {
			$this->repository->update( $donation_id, array( 'status' => Donation::STATUS_FAILED ) );
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
				error_log( '[Donatico] Checkout error: ' . $e->getMessage() );
			}
			wp_send_json_error( array( 'message' => __( 'An unexpected error occurred. Please try again.', 'donatico' ) ), 500 );
		}
	}
}
