<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Price calculator.
 *
 * Priority order: period pricing (duration rules) overrides day-by-day seasonal.
 * Within each group, higher priority wins; for equal priority, specificity wins
 * (car > category > all).
 */
class BingoCR_Pricing {

	public static function calculate( array $car, string $pickup_date, string $return_date, array $extra_ids = array(), string $discount_code = '', string $pickup_time = '', string $return_time = '' ): array {
		$days         = self::rental_days( $pickup_date, $return_date );
		$seasonal_adj = 0.0;
		$car_day_rate = (float) $car['price_per_day'];

		// Step 1: A duration rule sets the effective nightly rate (or a fixed total).
		$period             = self::match_period( $car, $days );
		$period_type        = $period ? $period['pricing_type'] : null;
		$seasonal_segments  = array();   // keyed by season name|modifier
		$seasonal_breakdown = array();

		if ( $period && 'fixed_total' === $period_type ) {
			// A fixed total is an explicit price for the whole rental, so seasonal —
			// which works night-by-night — has nothing to modify and does not apply.
			$day_rate = $car_day_rate;
			$base_pre = (float) $period['fixed_price'];
			$base     = $base_pre;
		} else {
			// Effective nightly rate: the duration rule's rate when present, else the car's.
			$day_rate = $period ? (float) $period['price_per_day'] : $car_day_rate;
			$base_pre = $day_rate * $days;

			// Step 2: Seasonal modifies each night on top of the effective rate. Always applies.
			$base = 0.0;
			$date = new DateTime( $pickup_date );
			$end  = new DateTime( $return_date );

			while ( $date < $end ) {
				$season = self::match_season( $car, $date->format( 'Y-m-d' ) );
				if ( $season ) {
					$adjusted      = round( $day_rate * ( 1 + (float) $season['modifier'] / 100 ), 4 );
					$base         += $adjusted;
					$delta         = $adjusted - $day_rate;
					$seasonal_adj += $delta;

					// Tally per season (name + modifier) for an itemized breakdown.
					$sname = isset( $season['name'] ) ? (string) $season['name'] : '';
					$skey  = $sname . '|' . $season['modifier'];
					if ( ! isset( $seasonal_segments[ $skey ] ) ) {
						$seasonal_segments[ $skey ] = array(
							'name'     => $sname,
							'modifier' => (float) $season['modifier'],
							'nights'   => 0,
							'amount'   => 0.0,
						);
					}
					$seasonal_segments[ $skey ]['nights']++;
					$seasonal_segments[ $skey ]['amount'] += $delta;
				} else {
					$base += $day_rate;
				}
				$date->modify( '+1 day' );
			}

			foreach ( $seasonal_segments as $seg ) {
				$seg['amount']        = round( $seg['amount'], 2 );
				$seasonal_breakdown[] = $seg;
			}
		}

		// Step 3: Extras (premium-only logic; gated + stripped from the free build).
		$extras_total     = 0.0;
		$extras_breakdown = array();
		if ( $extra_ids && class_exists( 'BingoCR_Premium' ) && BingoCR_License::can( 'extras' ) ) {
			$x                = BingoCR_Premium::price_extras( $extra_ids, $days );
			$extras_total     = $x['total'];
			$extras_breakdown = $x['breakdown'];
		}

		// Step 4: Discount (premium-only logic; gated + stripped from the free build).
		$discount_amount = 0.0;
		$discount_row    = null;
		if ( $discount_code !== '' && class_exists( 'BingoCR_Premium' ) && BingoCR_License::can( 'discounts' ) ) {
			$d               = BingoCR_Premium::discount( $discount_code, $days, $base + $extras_total );
			$discount_amount = $d['amount'];
			$discount_row    = $d['row'];
		}

		// Step 5: Out-of-hours fee (Pro, per occurrence: pickup + return).
		$ooh = self::out_of_hours_fee( $pickup_time, $return_time );

		return array(
			'days'             => $days,
			'day_rate'         => round( $day_rate, 2 ),
			'base_price'       => round( $base_pre, 2 ),   // effective base BEFORE seasonal
			'period_rule'      => $period ? $period['name'] : null,
			'period_type'      => $period_type,
			'seasonal_adj'     => round( $seasonal_adj, 2 ),
			'seasonal_breakdown' => $seasonal_breakdown,
			'extras'           => $extras_breakdown,
			'extras_total'     => round( $extras_total, 2 ),
			'discount_code'    => $discount_row ? $discount_code : null,
			'discount_id'      => $discount_row ? (int) $discount_row['id'] : null,
			'discount_amount'  => round( $discount_amount, 2 ),
			'out_of_hours'     => $ooh,
			'out_of_hours_fee' => $ooh['fee'],
			'total'            => round( max( 0, $base + $extras_total - $discount_amount + $ooh['fee'] ), 2 ),
		);
	}

	/**
	 * Out-of-hours fee. Thin wrapper: the actual calculation is premium-only
	 * (stripped from the free build), so the free build always returns no fee.
	 */
	public static function out_of_hours_fee( string $pickup_time, string $return_time ): array {
		$none = array( 'enabled' => false, 'fee' => 0.0, 'fee_per_occurrence' => 0.0, 'pickup_outside' => false, 'return_outside' => false );

		if ( ! class_exists( 'BingoCR_Premium' ) || ! BingoCR_License::can( 'out_of_hours' ) ) {
			return $none;
		}

		return BingoCR_Premium::out_of_hours_fee( $pickup_time, $return_time );
	}

	public static function rental_days( string $pickup, string $return_date ): int {
		$p    = new DateTime( $pickup );
		$r    = new DateTime( $return_date );
		$diff = $p->diff( $r );
		return max( 1, (int) $diff->days );
	}

	private static function match_period( array $car, int $days ): ?array {
		$rules = BingoCR_DB::get_period_pricing_for_days( $days );
		$best  = null;
		$best_score = -1;

		foreach ( $rules as $rule ) {
			if ( $rule['applies_to'] === 'car' ) {
				if ( (int) $rule['car_id'] !== (int) $car['id'] ) { continue; }
				$spec = 3;
			} elseif ( $rule['applies_to'] === 'category' ) {
				if ( $rule['category'] !== $car['category'] ) { continue; }
				$spec = 2;
			} else {
				$spec = 1;
			}
			$score = (int) $rule['priority'] * 10 + $spec;
			if ( $score > $best_score ) {
				$best_score = $score;
				$best       = $rule;
			}
		}
		return $best;
	}

	private static function match_season( array $car, string $date ): ?array {
		$seasons = BingoCR_DB::get_seasonal_prices_for_date( $date );
		$best    = null;
		$best_p  = -1;

		foreach ( $seasons as $s ) {
			if ( $s['applies_to'] === 'category' && $s['category'] !== $car['category'] ) {
				continue;
			}
			if ( (int) $s['priority'] > $best_p ) {
				$best_p = (int) $s['priority'];
				$best   = $s;
			}
		}
		return $best;
	}
}
