<?php
/**
 * Birthday service stub for managing birthday rewards.
 *
 * This base class provides constants, pure validation/formatting methods,
 * and no-op stubs for all business-logic methods. The real implementation
 * lives in ProBirthdayService which overrides the stubs in Pro builds.
 *
 * @package RewardsWP
 */

namespace Awesomemotive\Rewardswp\Services;

use Awesomemotive\Rewardswp\Models\Member;
use WP_Error;

/**
 * Birthday Service (base stub).
 *
 * @since 1.3.0
 * @since 1.4.0 Converted to stub; full implementation moved to ProBirthdayService.
 */
class BirthdayService {

	/**
	 * Minimum days before birthday to allow setting.
	 *
	 * @since 1.3.0
	 */
	public const MIN_DAYS_BEFORE_BIRTHDAY = 30;

	/**
	 * Meta key for storing birthday date.
	 *
	 * @since 1.3.0
	 */
	public const BIRTHDAY_META_KEY = 'birthday';

	/**
	 * Meta key for tracking when birthday was first set.
	 *
	 * @since 1.3.0
	 */
	public const BIRTHDAY_SET_AT_META_KEY = 'birthday_set_at';

	/**
	 * Meta key for tracking when birthday was last updated.
	 *
	 * @since 1.4.0
	 */
	public const BIRTHDAY_UPDATED_AT_META_KEY = 'birthday_updated_at';

	/**
	 * Validate a birthday date.
	 *
	 * @since 1.3.0
	 *
	 * @param int $month The birth month (1-12).
	 * @param int $day   The birth day (1-31).
	 *
	 * @return true|WP_Error True if valid, WP_Error if invalid.
	 */
	public function validate_birthday( int $month, int $day ) {
		if ( $month < 1 || $month > 12 ) {
			return new WP_Error(
				'invalid_month',
				__( 'Month must be between 1 and 12.', 'rewardswp' )
			);
		}

		$days_in_month = cal_days_in_month( CAL_GREGORIAN, $month, 2000 );
		if ( $day < 1 || $day > $days_in_month ) {
			return new WP_Error(
				'invalid_day',
				sprintf(
					/* translators: %d: maximum days in month */
					__( 'Day must be between 1 and %d for the selected month.', 'rewardswp' ),
					$days_in_month
				)
			);
		}

		return true;
	}

	/**
	 * Validate birthday format only (month/day ranges).
	 *
	 * Unlike validate_birthday(), this does NOT enforce the 30-day minimum gap.
	 * Intended for admin use where date proximity restrictions don't apply.
	 *
	 * @since 1.4.0
	 *
	 * @param int $month The birth month (1-12).
	 * @param int $day   The birth day (1-31).
	 *
	 * @return true|WP_Error True if valid, WP_Error if invalid.
	 */
	public function validate_birthday_format( int $month, int $day ) {
		if ( $month < 1 || $month > 12 ) {
			return new WP_Error(
				'invalid_month',
				__( 'Month must be between 1 and 12.', 'rewardswp' )
			);
		}

		$days_in_month = cal_days_in_month( CAL_GREGORIAN, $month, 2000 );
		if ( $day < 1 || $day > $days_in_month ) {
			return new WP_Error(
				'invalid_day',
				sprintf(
					/* translators: %d: maximum days in month */
					__( 'Day must be between 1 and %d for the selected month.', 'rewardswp' ),
					$days_in_month
				)
			);
		}

		return true;
	}

	/**
	 * Set a member's birthday.
	 *
	 * @since 1.3.0
	 *
	 * @param Member $member The member.
	 * @param int    $month  The birth month (1-12).
	 * @param int    $day    The birth day (1-31).
	 *
	 * @return true|WP_Error True on success, WP_Error on failure.
	 */
	public function set_birthday( Member $member, int $month, int $day ) {
		return new WP_Error( 'birthday_not_available', __( 'Birthday rewards require a Pro license.', 'rewardswp' ) );
	}

	/**
	 * Update a member's birthday with security guards.
	 *
	 * @since 1.4.0
	 *
	 * @param Member $member The member.
	 * @param int    $month  The birth month (1-12).
	 * @param int    $day    The birth day (1-31).
	 *
	 * @return true|WP_Error True on success, WP_Error on failure.
	 */
	public function update_birthday( Member $member, int $month, int $day ) {
		return new WP_Error( 'birthday_not_available', __( 'Birthday rewards require a Pro license.', 'rewardswp' ) );
	}

	/**
	 * Check if a member's birthday has already been edited.
	 *
	 * @since 1.4.0
	 *
	 * @param Member $member The member.
	 *
	 * @return bool
	 */
	public function has_been_edited( Member $member ): bool {
		return false;
	}

	/**
	 * Check if the member's birthday was set far enough in advance to qualify for points.
	 *
	 * @since 1.4.0
	 *
	 * @param Member $member The member.
	 *
	 * @return bool
	 */
	public function was_birthday_set_early_enough( Member $member ): bool {
		return false;
	}

	/**
	 * Get the next reward date for a given birthday.
	 *
	 * @since 1.4.0
	 *
	 * @param int $month The birth month (1-12).
	 * @param int $day   The birth day (1-31).
	 *
	 * @return string
	 */
	public function get_next_reward_date( int $month, int $day ): string {
		return '';
	}

	/**
	 * Get reward preview data for a birthday.
	 *
	 * @since 1.4.0
	 *
	 * @param int $month The birth month (1-12).
	 * @param int $day   The birth day (1-31).
	 *
	 * @return array|null
	 */
	public function get_reward_preview( int $month, int $day ): ?array {
		return null;
	}

	/**
	 * Get a notice message for birthday edit if applicable.
	 *
	 * @since 1.4.0
	 *
	 * @param Member $member The member.
	 *
	 * @return string|null
	 */
	public function get_birthday_edit_notice( Member $member ): ?string {
		return null;
	}

	/**
	 * Get a member's birthday.
	 *
	 * @since 1.3.0
	 *
	 * @param Member $member The member.
	 *
	 * @return string|null The birthday in MM-DD format, or null if not set.
	 */
	public function get_birthday( Member $member ): ?string {
		$birthday = $member->get_meta( self::BIRTHDAY_META_KEY );
		return ! empty( $birthday ) ? $birthday : null;
	}

	/**
	 * Check if a member has a birthday set.
	 *
	 * @since 1.3.0
	 *
	 * @param Member $member The member.
	 *
	 * @return bool
	 */
	public function has_birthday( Member $member ): bool {
		return ! empty( $member->get_meta( self::BIRTHDAY_META_KEY ) );
	}

	/**
	 * Check if today is a member's birthday.
	 *
	 * @since 1.3.0
	 *
	 * @param Member $member The member.
	 *
	 * @return bool
	 */
	public function is_birthday_today( Member $member ): bool {
		return false;
	}

	/**
	 * Check if birthday points have been awarded this year.
	 *
	 * @since 1.3.0
	 *
	 * @param Member $member The member.
	 *
	 * @return bool
	 */
	public function has_birthday_points_this_year( Member $member ): bool {
		return false;
	}

	/**
	 * Check if a member has ever received birthday points (any year).
	 *
	 * @since 1.4.0
	 *
	 * @param Member $member The member.
	 *
	 * @return bool
	 */
	public function has_ever_received_birthday_points( Member $member ): bool {
		return false;
	}

	/**
	 * Award birthday points to a member.
	 *
	 * @since 1.3.0
	 *
	 * @param Member $member The member to award points to.
	 *
	 * @return bool
	 */
	public function award_birthday_points( Member $member ): bool {
		return false;
	}

	/**
	 * Process birthday points for all eligible members.
	 *
	 * @since 1.3.0
	 *
	 * @param int $page Current page for pagination.
	 *
	 * @return void
	 */
	public function process_birthday_points( int $page = 1 ): void {
		// No-op in base (Lite) — Pro overrides this.
	}

	/**
	 * Format birthday for display.
	 *
	 * Uses a translatable hardcoded format (matching WordPress core's admin
	 * convention) rather than the site's date_format option, which is intended
	 * for front-end display. Day/month order is detected from the site setting
	 * so day-first locales (UK, EU) see "15 March" while month-first locales
	 * (US) see "March 15".
	 *
	 * @since 1.3.0
	 *
	 * @param string $birthday The birthday in MM-DD format.
	 *
	 * @return string Formatted birthday (e.g., "March 15" or "15 March").
	 */
	public function format_birthday_for_display( string $birthday ): string {
		if ( empty( $birthday ) ) {
			return '';
		}

		$parts = explode( '-', $birthday );
		if ( count( $parts ) !== 2 ) {
			return $birthday;
		}

		$date = \DateTime::createFromFormat( 'm-d', $birthday );
		if ( ! $date ) {
			return $birthday;
		}

		// Detect day-first locales from the WP date format setting.
		$day_first = (bool) preg_match( '/^[jd]/', get_option( 'date_format', 'F j, Y' ) );
		$format    = $day_first ? __( 'j F', 'rewardswp' ) : __( 'F j', 'rewardswp' );

		return date_i18n( $format, $date->getTimestamp() );
	}

	/**
	 * Get birthday day data for the member (persists all day).
	 *
	 * @since 1.4.0
	 *
	 * @param Member $member The member.
	 *
	 * @return array|null
	 */
	public function get_birthday_day_data( Member $member ): ?array {
		return null;
	}
}
