<?php
/**
 * Stats service for daily detection statistics tracking.
 *
 * @package Carticy\CheckoutShield\Services
 */

declare(strict_types=1);

namespace Carticy\CheckoutShield\Services;

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

/**
 * Handles daily statistics tracking for checkout shield detection events.
 *
 * Stores stats by date with automatic 30-day retention.
 */
final class StatsService {

	/**
	 * Option key for daily stats storage.
	 */
	public const OPTION_KEY = 'carticy_checkout_shield_daily_stats';

	/**
	 * Number of days to retain stats.
	 */
	private const RETENTION_DAYS = 30;

	/**
	 * Valid status types.
	 */
	private const VALID_STATUSES = array( 'blocked', 'passed', 'learning', 'bypassed' );

	/**
	 * Cached stats array.
	 *
	 * @var array|null
	 */
	private ?array $stats_cache = null;

	/**
	 * Record a detection event for today.
	 *
	 * @param string $status The detection status (blocked, passed, learning, bypassed).
	 */
	public function record_event( string $status ): void {
		if ( ! in_array( $status, self::VALID_STATUSES, true ) ) {
			return;
		}

		$stats = $this->load_stats();
		$today = $this->get_today();

		// Initialize today's entry if it doesn't exist.
		if ( ! isset( $stats[ $today ] ) ) {
			$stats[ $today ] = $this->get_empty_day();
		}

		// Increment the counter.
		++$stats[ $today ][ $status ];

		// Cleanup old data before saving.
		$this->cleanup_old_data( $stats );

		$this->save_stats( $stats );
	}

	/**
	 * Get daily stats for the specified number of days.
	 *
	 * Returns an array with dates as keys, ordered from oldest to newest.
	 *
	 * @param int $days Number of days to retrieve (default 7).
	 * @return array<string, array{blocked: int, passed: int, learning: int, bypassed: int}>
	 */
	public function get_daily_stats( int $days = 7 ): array {
		$stats  = $this->load_stats();
		$result = array();

		// Generate dates from oldest to newest.
		for ( $i = $days - 1; $i >= 0; $i-- ) {
			$date            = gmdate( 'Y-m-d', strtotime( "-{$i} days" ) );
			$result[ $date ] = $stats[ $date ] ?? $this->get_empty_day();
		}

		return $result;
	}

	/**
	 * Get aggregated totals for the specified number of days.
	 *
	 * @param int $days Number of days to aggregate (default 30).
	 * @return array{blocked: int, passed: int, learning: int, bypassed: int, total: int}
	 */
	public function get_totals( int $days = 30 ): array {
		$daily_stats = $this->get_daily_stats( $days );
		$totals      = array(
			'blocked'  => 0,
			'passed'   => 0,
			'learning' => 0,
			'bypassed' => 0,
		);

		foreach ( $daily_stats as $day_stats ) {
			foreach ( self::VALID_STATUSES as $status ) {
				$totals[ $status ] += $day_stats[ $status ] ?? 0;
			}
		}

		$totals['total'] = array_sum( $totals );

		return $totals;
	}

	/**
	 * Get the maximum daily total across the specified days.
	 *
	 * Useful for calculating chart scale.
	 *
	 * @param int $days Number of days to check.
	 * @return int
	 */
	public function get_max_daily_total( int $days = 7 ): int {
		$daily_stats = $this->get_daily_stats( $days );
		$max         = 0;

		foreach ( $daily_stats as $day_stats ) {
			$day_total = array_sum( $day_stats );
			if ( $day_total > $max ) {
				$max = $day_total;
			}
		}

		return $max;
	}

	/**
	 * Get today's date in Y-m-d format.
	 *
	 * @return string
	 */
	private function get_today(): string {
		return gmdate( 'Y-m-d' );
	}

	/**
	 * Get an empty day stats array.
	 *
	 * @return array{blocked: int, passed: int, learning: int, bypassed: int}
	 */
	private function get_empty_day(): array {
		return array(
			'blocked'  => 0,
			'passed'   => 0,
			'learning' => 0,
			'bypassed' => 0,
		);
	}

	/**
	 * Load stats from the database.
	 *
	 * @return array<string, array{blocked: int, passed: int, learning: int, bypassed: int}>
	 */
	private function load_stats(): array {
		if ( null !== $this->stats_cache ) {
			return $this->stats_cache;
		}

		$stats = get_option( self::OPTION_KEY, array() );

		if ( ! is_array( $stats ) ) {
			$stats = array();
		}

		$this->stats_cache = $stats;

		return $stats;
	}

	/**
	 * Save stats to the database.
	 *
	 * @param array $stats The stats array to save.
	 */
	private function save_stats( array $stats ): void {
		$this->stats_cache = $stats;
		update_option( self::OPTION_KEY, $stats, false );
	}

	/**
	 * Remove stats older than the retention period.
	 *
	 * @param array $stats Stats array (passed by reference).
	 */
	private function cleanup_old_data( array &$stats ): void {
		$cutoff_date = gmdate( 'Y-m-d', strtotime( '-' . self::RETENTION_DAYS . ' days' ) );

		foreach ( array_keys( $stats ) as $date ) {
			if ( $date < $cutoff_date ) {
				unset( $stats[ $date ] );
			}
		}
	}

	/**
	 * Reset all stats (for testing or admin action).
	 */
	public function reset_stats(): void {
		$this->stats_cache = null;
		delete_option( self::OPTION_KEY );
	}
}
