<?php

namespace Prodigy\Includes\Helpers;

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

class Prodigy_Redemption_Rules_Data {
	private const INCLUDED_CATEGORY_TYPE = 'redemption-code-categories';

	public static function empty_data(): array {
		return array(
			'ends_at'               => '',
			'ends_at_label'         => '',
			'time_remaining'        => '',
			'days_until_expiration' => null,
			'total_capacity'        => 0,
			'total_selected'        => 0,
			'order_rules'           => array(),
		);
	}

	/**
	 * @param  array  $response
	 *
	 * @return array
	 */
	public static function normalize_response( array $response ): array {
		if ( empty( $response['data']['attributes'] ) || ! is_array( $response['data']['attributes'] ) ) {
			return self::empty_data();
		}

		$code_attrs            = $response['data']['attributes'];
		$ends_at_raw           = (string) ( $code_attrs['end-date'] ?? $code_attrs['ends-at'] ?? '' );
		$days_until_expiration = isset( $code_attrs['days-until-expiration'] ) ? (int) $code_attrs['days-until-expiration'] : null;
		$formatted_expiration  = self::format_expiration( $ends_at_raw, $days_until_expiration );

		return array(
			'ends_at'               => $ends_at_raw,
			'ends_at_label'         => $formatted_expiration['ends_at_label'],
			'time_remaining'        => $formatted_expiration['time_remaining'],
			'days_until_expiration' => $days_until_expiration,
			'total_capacity'        => (int) ( $code_attrs['total-available-quantity'] ?? $code_attrs['total-capacity'] ?? 0 ),
			'total_selected'        => (int) ( $code_attrs['total-selected-quantity'] ?? 0 ),
			'order_rules'           => self::normalize_order_rules( $response, $code_attrs ),
		);
	}

	/**
	 * @param  array  $response
	 * @param  array  $code_attrs
	 *
	 * @return array
	 */
	private static function normalize_order_rules( array $response, array $code_attrs ): array {
		$included_rules = self::normalize_included_category_rules( $response['included'] ?? array() );

		if ( ! empty( $included_rules ) ) {
			return $included_rules;
		}

		return array_values( (array) ( $code_attrs['order-rules'] ?? array() ) );
	}

	/**
	 * @param $included
	 *
	 * @return array
	 */
	private static function normalize_included_category_rules( $included ): array {
		if ( empty( $included ) || ! is_array( $included ) ) {
			return array();
		}

		$categories_map = array();
		$rule_items     = array();

		foreach ( $included as $item ) {
			$type = $item['type'] ?? '';
			$id   = $item['id'] ?? '';

			if ( $type === 'categories' && $id !== '' ) {
				$categories_map[ $id ] = $item;
			} elseif ( $type === self::INCLUDED_CATEGORY_TYPE ) {
				$rule_items[] = $item;
			}
		}

		$order_rules = array();

		foreach ( $rule_items as $item ) {
			$attributes = $item['attributes'] ?? array();
			if ( ! is_array( $attributes ) ) {
				continue;
			}

			$category_refs = $item['relationships']['categories']['data'] ?? array();

			if ( ! empty( $category_refs ) && ! empty( $categories_map ) ) {
				foreach ( $category_refs as $cat_ref ) {
					$cat_id = $cat_ref['id'] ?? '';
					if ( ! isset( $categories_map[ $cat_id ] ) ) {
						continue;
					}
					$category_name = self::build_category_tree_name( $categories_map[ $cat_id ], $categories_map );
					$order_rules[] = array_merge(
						self::normalize_category_rule( $attributes ),
						array( 'category-name' => $category_name )
					);
				}
				continue;
			}

			$category_items = $attributes['items'] ?? array();
			if ( is_array( $category_items ) && ! empty( $category_items ) ) {
				foreach ( $category_items as $category_item ) {
					if ( is_array( $category_item ) ) {
						$order_rules[] = self::normalize_category_rule( $category_item, $attributes );
					}
				}
				continue;
			}

			$order_rules[] = self::normalize_category_rule( $attributes );
		}

		return $order_rules;
	}

	/**
	 * @param  array  $category
	 * @param  array  $categories_map
	 *
	 * @return string
	 */
	private static function build_category_tree_name( array $category, array $categories_map ): string {
		$parts         = array();
		$ancestor_refs = $category['relationships']['ancestors']['data'] ?? array();

		foreach ( $ancestor_refs as $ancestor_ref ) {
			$ancestor_id = $ancestor_ref['id'] ?? '';
			$name        = $categories_map[ $ancestor_id ]['attributes']['name'] ?? '';
			if ( $name !== '' ) {
				$parts[] = $name;
			}
		}

		$own_name = $category['attributes']['name'] ?? '';
		if ( $own_name !== '' ) {
			$parts[] = $own_name;
		}

		return implode( ' / ', $parts );
	}

	/**
	 * @param  array  $category_item
	 * @param  array  $fallback_attributes
	 *
	 * @return array
	 */
	private static function normalize_category_rule( array $category_item, array $fallback_attributes = array() ): array {
		return array(
			'category-name'    => (string) self::get_attribute_value(
				$category_item,
				array( 'category-name', 'name', 'title', 'full-name' ),
				self::get_attribute_value( $fallback_attributes, array( 'category-name', 'name', 'title', 'full-name' ), '' )
			),
			'capacity'         => (int) self::get_attribute_value(
				$category_item,
				array( 'capacity', 'total-available-quantity', 'available-quantity', 'total-quantity', 'quantity' ),
				self::get_attribute_value(
					$fallback_attributes,
					array( 'capacity', 'total-available-quantity', 'available-quantity', 'total-quantity', 'quantity' ),
					0
				)
			),
			'ordered-quantity' => (int) self::get_attribute_value(
				$category_item,
				array( 'ordered-quantity', 'total-selected-quantity', 'selected-quantity', 'cart-quantity', 'quantity-in-cart' ),
				self::get_attribute_value(
					$fallback_attributes,
					array( 'ordered-quantity', 'total-selected-quantity', 'selected-quantity', 'cart-quantity', 'quantity-in-cart' ),
					0
				)
			),
		);
	}

	/**
	 * @param  array  $attributes
	 * @param  array  $keys
	 * @param         $default
	 *
	 * @return mixed|null
	 */
	private static function get_attribute_value( array $attributes, array $keys, $default = null ) {
		foreach ( $keys as $key ) {
			if ( array_key_exists( $key, $attributes ) && '' !== $attributes[ $key ] && null !== $attributes[ $key ] ) {
				return $attributes[ $key ];
			}
		}

		return $default;
	}

	/**
	 * @param  string    $ends_at_raw
	 * @param  int|null  $days_until_expiration
	 *
	 * @return string[]
	 */
	private static function format_expiration( string $ends_at_raw, ?int $days_until_expiration ): array {
		$expiration = array(
			'ends_at_label'  => '',
			'time_remaining' => '',
		);

		if ( $ends_at_raw ) {
			try {
				$ends_at         = new \DateTime( $ends_at_raw );
				$display_ends_at = clone $ends_at;
				$display_ends_at->setTimezone( self::get_display_timezone() );

				$expiration['ends_at_label']  = $display_ends_at->format( 'M j, Y (g:i A)' );
				$expiration['time_remaining'] = self::format_time_remaining_from_date( $ends_at );

				return $expiration;
			} catch ( \Exception $e ) {
				// Fall back to days-until-expiration when the API date cannot be parsed.
			}
		}

		if ( null !== $days_until_expiration && $days_until_expiration >= 0 ) {
			$expiration['time_remaining'] = self::format_time_remaining_parts( $days_until_expiration, 0, 0 );
		}

		return $expiration;
	}

	/**
	 * @return \DateTimeZone
	 * @throws \DateInvalidTimeZoneException
	 */
	private static function get_display_timezone(): \DateTimeZone {
		if ( \function_exists( 'wp_timezone' ) ) {
			return \wp_timezone();
		}

		return new \DateTimeZone( \date_default_timezone_get() ?: 'UTC' );
	}

	/**
	 * @param  \DateTime  $ends_at
	 *
	 * @return string
	 * @throws \Exception
	 */
	private static function format_time_remaining_from_date( \DateTime $ends_at ): string {
		$now  = new \DateTime( 'now', $ends_at->getTimezone() );
		$diff = $now->diff( $ends_at );

		if ( $diff->invert ) {
			return '';
		}

		return self::format_time_remaining_parts( (int) $diff->days, (int) $diff->h, (int) $diff->i );
	}

	/**
	 * @param  int  $days
	 * @param  int  $hours
	 * @param  int  $minutes
	 *
	 * @return string
	 */
	private static function format_time_remaining_parts( int $days, int $hours, int $minutes ): string {
		if ( $days > 0 ) {
			return sprintf( '%dd %dh %dm', $days, $hours, $minutes );
		}

		return sprintf( '%dh %dm', $hours, $minutes );
	}
}
