<?php

namespace SugarCalendarBookings\Support\Helpers;

use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use Exception;
use SugarCalendarBookings\Options;
use SugarCalendarBookings\Plugin;

/**
 * Date helper class for robust date parsing and formatting.
 *
 * @since 1.0.0
 */
class Date {

	/**
	 * Cached default timezone string.
	 *
	 * @since 1.3.1
	 *
	 * @var string|null
	 */
	private static ?string $timezone_cache = null;

	/**
	 * The datetime format for the database.
	 *
	 * @since 1.0.0
	 *
	 * @var string
	 */
	const DB_DATETIME_FORMAT = 'Y-m-d H:i:s';

	/**
	 * Create a DateTimeImmutable object in UTC timezone.
	 *
	 * @since 1.0.0
	 *
	 * @param string|int|null $datetime Date/time string or timestamp. Defaults to 'now' in UTC timezone.
	 *
	 * @return DateTimeImmutable|null
	 */
	public static function create( $datetime = null ) {

		$timezone = new DateTimeZone( 'UTC' );

		try {
			return new DateTimeImmutable( is_null( $datetime ) ? 'now' : $datetime, $timezone );
		} catch ( Exception $e ) {
			return null;
		}
	}

	/**
	 * Convert a date to UTC timezone.
	 *
	 * @since 1.0.0
	 *
	 * @param string|DateTimeImmutable $datetime Date string in Atom format or DateTimeImmutable instance.
	 *
	 * @return DateTimeImmutable
	 */
	public static function to_utc( $datetime ) {

		if ( ! $datetime instanceof DateTimeImmutable ) {
			// Check if Atom format (datetime string has timezone).
			if ( strpos( $datetime, 'T' ) !== false ) {
				$datetime = new DateTimeImmutable( $datetime );
			} else {
				$datetime = new DateTimeImmutable( $datetime, self::get_default_timezone() );
			}
		}

		return $datetime->setTimezone( new DateTimeZone( 'UTC' ) );
	}

	/**
	 * Format date to the display format in site timezone.
	 *
	 * @since 1.0.0
	 *
	 * @param DateTimeImmutable $date     Date instance.
	 * @param string|null       $format   Date format.
	 * @param DateTimeZone|null $timezone Timezone.
	 *
	 * @return string
	 */
	public static function format_display_date( $date, $format = null, $timezone = null ) {

		if ( ! $date instanceof DateTimeInterface ) {
			return '';
		}

		if ( $format === null ) {
			$options = Plugin::get( Options::class );

			$format = $options->get( 'display.date_format' ) . ' ' . $options->get( 'display.time_format' );
		}

		if ( $timezone === null ) {
			$timezone = self::get_default_timezone();
		}

		return $date->setTimezone( $timezone )->format( $format );
	}

	/**
	 * Format date range for display.
	 *
	 * @since 1.1.0
	 *
	 * @param string      $start_date Start date (Y-m-d format).
	 * @param string      $end_date   End date (Y-m-d format).
	 * @param string|null $format     Date format. If null, uses plugin date format setting.
	 *
	 * @return string Formatted date or date range string.
	 */
	public static function format_date_range( $start_date, $end_date, $format = null ) {

		if ( empty( $start_date ) ) {
			return '';
		}

		// Get date format from plugin settings if not provided.
		if ( $format === null ) {
			$options = Plugin::get( Options::class );
			$format  = $options->get( 'display.date_format' );
		}

		// Create DateTimeImmutable objects.
		$start = self::create( $start_date );
		$end   = self::create( $end_date );

		if ( ! $start ) {
			return '';
		}

		// Single date.
		if ( $start_date === $end_date ) {
			return self::format_display_date( $start, $format );
		}

		// Date range.
		return sprintf(
		/* translators: %1$s - start date; %2$s - end date. */
			esc_html__( '%1$s - %2$s', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			self::format_display_date( $start, $format ),
			$end ? self::format_display_date( $end, $format ) : ''
		);
	}

	/**
	 * Check if a timezone string is a named IANA timezone.
	 *
	 * @since 1.3.1
	 *
	 * @param string $timezone Timezone string.
	 *
	 * @return bool
	 */
	private static function is_named_timezone( $timezone ) {

		return strpos( $timezone, '/' ) !== false || $timezone === 'UTC';
	}

	/**
	 * Get the default timezone string.
	 *
	 * @since 1.0.0
	 *
	 * @return string
	 */
	public static function get_default_timezone_string() {

		if ( self::$timezone_cache !== null ) {
			return self::$timezone_cache;
		}

		// Read directly from wp_options to avoid circular dependency.
		// Options::get() calls filter_options() which calls this method.
		$options  = get_option( Options::OPTION_NAME, [] );
		$timezone = $options['display']['timezone'] ?? '';

		if ( ! empty( $timezone ) ) {
			self::$timezone_cache = self::utc_offset_to_php_timezone( $timezone );
		} else {
			self::$timezone_cache = wp_timezone_string();
		}

		return self::$timezone_cache;
	}

	/**
	 * Reset the cached timezone string.
	 *
	 * Call after updating the timezone option to ensure subsequent calls
	 * return the updated value (important for Action Scheduler contexts).
	 *
	 * @since 1.3.1
	 */
	public static function reset_timezone_cache(): void {

		self::$timezone_cache = null;
	}

	/**
	 * Check if a timezone value is valid.
	 *
	 * Accepts named timezones (America/New_York), UTC, and UTC offsets (UTC+5, UTC-9.5).
	 *
	 * @since 1.3.1
	 *
	 * @param string $timezone Timezone value.
	 *
	 * @return bool
	 */
	public static function is_valid_timezone( $timezone ) {

		static $identifiers = null;

		if ( $identifiers === null ) {
			$identifiers = array_flip( timezone_identifiers_list() );
		}

		// Named timezone (e.g., America/New_York, UTC).
		if ( self::is_named_timezone( $timezone ) ) {
			return isset( $identifiers[ $timezone ] );
		}

		// UTC offset (e.g., UTC+5, UTC+5.5, UTC-8).
		if ( strpos( $timezone, 'UTC' ) === 0 ) {
			$offset = (float) str_replace( 'UTC', '', $timezone );

			return in_array( $offset, Time::get_valid_timezone_offsets(), true );
		}

		// PHP offset format (e.g., +05:00, -09:30).
		if ( preg_match( '/^[+-]\d{2}:\d{2}$/', $timezone ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Convert UTC offset format (UTC+5, UTC-9.5) to PHP DateTimeZone-compatible format (+05:00).
	 *
	 * Named timezones are returned as-is.
	 *
	 * @since 1.3.1
	 *
	 * @param string $timezone Timezone string.
	 *
	 * @return string
	 */
	public static function utc_offset_to_php_timezone( $timezone ) {

		// Convert 'UTC+5.5' → '+05:30'.
		if ( preg_match( '/^UTC([+-]\d+(?:\.\d+)?)$/', $timezone, $matches ) ) {
			$offset_hours = (float) $matches[1];
			$sign         = $offset_hours >= 0 ? '+' : '-';
			$abs          = abs( $offset_hours );
			$hours        = (int) $abs;
			$minutes      = (int) ( ( $abs - $hours ) * 60 );

			return sprintf( '%s%02d:%02d', $sign, $hours, $minutes );
		}

		return $timezone;
	}

	/**
	 * Convert PHP timezone offset format (+05:00) to WordPress UTC offset format (UTC+5).
	 *
	 * Named timezones are returned as-is.
	 *
	 * @since 1.3.1
	 *
	 * @param string $timezone Timezone string.
	 *
	 * @return string
	 */
	public static function php_timezone_to_utc_offset( $timezone ) {

		// Named timezones pass through.
		if ( self::is_named_timezone( $timezone ) ) {
			return $timezone;
		}

		if ( ! preg_match( '/^([+-])(\d{2}):(\d{2})$/', $timezone, $matches ) ) {
			return $timezone;
		}

		$sign    = $matches[1];
		$hours   = (int) $matches[2];
		$minutes = (int) $matches[3];

		if ( $hours === 0 && $minutes === 0 ) {
			return 'UTC+0';
		}

		$offset = $sign . $hours;

		if ( $minutes > 0 ) {
			$decimal = round( $minutes / 60, 2 );
			$offset .= ltrim( (string) $decimal, '0' );
		}

		return 'UTC' . $offset;
	}

	/**
	 * Get the default display timezone.
	 *
	 * @since 1.0.0
	 *
	 * @return DateTimeZone
	 */
	public static function get_default_timezone() {

		return new DateTimeZone( self::get_default_timezone_string() );
	}
}
