<?php
/**
 * Fundraiser Cache
 *
 * Handles caching of donation data at the fundraiser level using postmeta.
 * Cache is automatically updated when donations are created, updated, or deleted.
 *
 * @package AmbassadorHub\Fundraisers
 */

namespace AmbassadorHub\Fundraisers;

use AmbassadorHub\Donations\DonationsModule;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare

/**
 * Handles donation caching for fundraisers.
 *
 * Stores aggregated donation data in postmeta to avoid repeated database queries.
 */
final class FundraiserCache {
	/**
	 * Meta key for cached donations data.
	 */
	private const META_KEY_DONATIONS_CACHE = '_ambh_donations_cache';

	/**
	 * Meta key for cache timestamp.
	 */
	private const META_KEY_CACHE_TIMESTAMP = '_ambh_donations_cache_timestamp';

	/**
	 * Initialize cache hooks.
	 *
	 * @return void
	 */
	public static function init(): void {
		// Update cache when donations are modified.
		add_action( 'ambh_donation_created', array( self::class, 'on_donation_created' ), 10, 2 );
		add_action( 'ambh_donation_updated', array( self::class, 'on_donation_updated' ), 10, 3 );
		add_action( 'ambh_donation_deleted', array( self::class, 'on_donation_deleted' ), 10, 2 );
	}

	/**
	 * Handle donation creation.
	 *
	 * @param int   $donation_id Donation ID.
	 * @param array $data        Donation data.
	 *
	 * @return void
	 */
	public static function on_donation_created( int $donation_id, array $data ): void {
		if ( isset( $data['fundraiser_id'] ) && $data['fundraiser_id'] > 0 ) {
			self::update_cache( (int) $data['fundraiser_id'] );
		}
	}

	/**
	 * Handle donation update.
	 *
	 * @param int    $donation_id Donation ID.
	 * @param array  $data        Updated data.
	 * @param object $donation    Original donation object.
	 *
	 * @return void
	 */
	public static function on_donation_updated( int $donation_id, array $data, object $donation ): void {
		// Update cache for original fundraiser.
		if ( isset( $donation->fundraiser_id ) && $donation->fundraiser_id > 0 ) {
			self::update_cache( (int) $donation->fundraiser_id );
		}

		// If fundraiser was changed, update cache for new fundraiser too.
		if ( isset( $data['fundraiser_id'] ) && $data['fundraiser_id'] > 0 && $data['fundraiser_id'] !== $donation->fundraiser_id ) {
			self::update_cache( (int) $data['fundraiser_id'] );
		}
	}

	/**
	 * Handle donation deletion.
	 *
	 * @param int    $donation_id Donation ID.
	 * @param object $donation    Deleted donation object.
	 *
	 * @return void
	 */
	public static function on_donation_deleted( int $donation_id, object $donation ): void {
		if ( isset( $donation->fundraiser_id ) && $donation->fundraiser_id > 0 ) {
			self::update_cache( (int) $donation->fundraiser_id );
		}
	}

	/**
	 * Update donation cache for a fundraiser.
	 *
	 * Calculates and stores aggregated donation data in postmeta.
	 *
	 * @param int $fundraiser_id Fundraiser post ID.
	 *
	 * @return bool True on success, false on failure.
	 */
	public static function update_cache( int $fundraiser_id ): ?array {
		global $wpdb;

		// Verify fundraiser exists.
		if ( FundraisersModule::POST_TYPE !== get_post_type( $fundraiser_id ) ) {
			return null;
		}

		$table_name = DonationsModule::get_table_name();

		// Get aggregated donation data.
		$cache_data = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT 
					COUNT(*) as total_count,
					COALESCE(SUM(CASE WHEN status = 'completed' THEN amount ELSE 0 END), 0) as total_amount,
					COALESCE(SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END), 0) as completed_count,
					COALESCE(SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END), 0) as pending_count,
					COALESCE(SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END), 0) as failed_count,
					MAX(created_at) as last_donation_date
				FROM {$table_name}
				WHERE fundraiser_id = %d",
				$fundraiser_id
			),
			ARRAY_A
		);

		if ( null === $cache_data ) {
			return null;
		}

		// Convert to proper types.
		$cache_data = array(
			'total_count'        => (int) $cache_data['total_count'],
			'total_amount'       => (float) $cache_data['total_amount'],
			'completed_count'    => (int) $cache_data['completed_count'],
			'pending_count'      => (int) $cache_data['pending_count'],
			'failed_count'       => (int) $cache_data['failed_count'],
			'last_donation_date' => $cache_data['last_donation_date'],
		);

		// Store cache.
		update_post_meta( $fundraiser_id, self::META_KEY_DONATIONS_CACHE, $cache_data );
		update_post_meta( $fundraiser_id, self::META_KEY_CACHE_TIMESTAMP, current_time( 'mysql' ) );

		/**
		 * Fires after fundraiser donation cache is updated.
		 *
		 * @since 0.3.0
		 *
		 * @param int   $fundraiser_id Fundraiser ID.
		 * @param array $cache_data    Cached data.
		 */
		do_action( 'ambh_fundraiser_cache_updated', $fundraiser_id, $cache_data );

		return $cache_data;
	}

	/**
	 * Get cached donation data for a fundraiser.
	 *
	 * @param int $fundraiser_id Fundraiser post ID.
	 *
	 * @return array|null Cached data or null if not found.
	 */
	public static function get_cache( int $fundraiser_id ): ?array {
		$cache = get_post_meta( $fundraiser_id, self::META_KEY_DONATIONS_CACHE, true );

		if ( empty( $cache ) || ! is_array( $cache ) ) {
			return null;
		}

		return $cache;
	}

	/**
	 * Get or generate cache for a fundraiser.
	 *
	 * Returns cached data if available, otherwise generates and caches it.
	 *
	 * @param int $fundraiser_id Fundraiser post ID.
	 *
	 * @return array Cached donation data.
	 */
	public static function get_or_update_cache( int $fundraiser_id ): array {
		$cache = self::get_cache( $fundraiser_id );

		if ( null !== $cache ) {
			return $cache;
		}

		// Generate cache if it doesn't exist.
		return self::update_cache( $fundraiser_id ) ?? array(
			'total_count'        => 0,
			'total_amount'       => 0.0,
			'completed_count'    => 0,
			'pending_count'      => 0,
			'failed_count'       => 0,
			'last_donation_date' => null,
		);
	}

	/**
	 * Clear cache for a fundraiser.
	 *
	 * @param int $fundraiser_id Fundraiser post ID.
	 *
	 * @return bool True on success, false on failure.
	 */
	public static function clear_cache( int $fundraiser_id ): bool {
		$result1 = delete_post_meta( $fundraiser_id, self::META_KEY_DONATIONS_CACHE );
		$result2 = delete_post_meta( $fundraiser_id, self::META_KEY_CACHE_TIMESTAMP );

		return $result1 && $result2;
	}

	/**
	 * Get cache timestamp.
	 *
	 * @param int $fundraiser_id Fundraiser post ID.
	 *
	 * @return string|null Timestamp or null if not cached.
	 */
	public static function get_cache_timestamp( int $fundraiser_id ): ?string {
		$timestamp = get_post_meta( $fundraiser_id, self::META_KEY_CACHE_TIMESTAMP, true );

		return ! empty( $timestamp ) ? $timestamp : null;
	}

	/**
	 * Check if cache exists and is valid.
	 *
	 * @param int $fundraiser_id Fundraiser post ID.
	 *
	 * @return bool True if cache exists, false otherwise.
	 */
	public static function has_cache( int $fundraiser_id ): bool {
		return null !== self::get_cache( $fundraiser_id );
	}

	/**
	 * Refresh cache for all fundraisers.
	 *
	 * Useful for bulk operations or data migrations.
	 *
	 * @return int Number of caches updated.
	 */
	public static function refresh_all_caches(): int {
		$fundraisers = get_posts(
			array(
				'post_type'      => FundraisersModule::POST_TYPE,
				'posts_per_page' => -1,
				'post_status'    => 'any',
				'fields'         => 'ids',
			)
		);

		$count = 0;

		foreach ( $fundraisers as $fundraiser_id ) {
			if ( self::update_cache( $fundraiser_id ) ) {
				++$count;
			}
		}

		return $count;
	}
}
