<?php

namespace SugarCalendarBookings\Availability;

use DateTimeImmutable;
use DateTimeZone;
use Exception;
use SugarCalendarBookings\Employees\EmployeeModel;
use SugarCalendarBookings\Locations\LocationModel;
use SugarCalendarBookings\Options;
use SugarCalendarBookings\Plugin;
use SugarCalendarBookings\Services\ServiceModel;

/**
 * Service for managing working schedule and overrides.
 *
 * @since 1.0.0
 */
class WorkingScheduleService {

	/**
	 * Get working schedule for an employee.
	 *
	 * @since 1.0.0
	 *
	 * @param DateTimeImmutable  $from_date From date.
	 * @param DateTimeImmutable  $to_date   To date.
	 * @param ServiceModel       $service   Service model.
	 * @param LocationModel|null $location  Location model.
	 * @param EmployeeModel|null $employee  Employee model.
	 *
	 * @return array<WorkingPeriod> Working periods.
	 */
	public function get_working_periods( $from_date, $to_date, $service, $location = null, $employee = null ) {

		// TODO: Implement merge logic during "Any" employee logic implementation.
		$regular   = $this->get_regular( $service, $location, $employee );
		$overrides = $this->get_overrides( $service, $location, $employee );

		return ( new WorkingPeriodsResolver( array_merge( $regular, $overrides ), $from_date, $to_date ) )->get_periods();
	}

	/**
	 * Get working periods for multiple employees in a single resolver pass.
	 *
	 * Collects regular and override rules for every employee, then resolves
	 * all rules through one WorkingPeriodsResolver to avoid per-employee
	 * resolver overhead.
	 *
	 * @since 1.4.0
	 *
	 * @param DateTimeImmutable  $from_date From date.
	 * @param DateTimeImmutable  $to_date   To date.
	 * @param ServiceModel       $service   Service model.
	 * @param EmployeeModel[]    $employees Employee models (with meta eager-loaded).
	 * @param LocationModel|null $location  Location model.
	 *
	 * @return array<WorkingPeriod> Working periods.
	 */
	public function get_employees_working_periods( $from_date, $to_date, $service, array $employees, $location = null ) {

		$rule_sets = [];

		foreach ( $employees as $employee ) {
			$rule_sets[] = $this->get_regular( $service, $location, $employee );
			$rule_sets[] = $this->get_overrides( $service, $location, $employee );
		}

		if ( empty( $rule_sets ) ) {
			return [];
		}

		$all_rules = array_merge( ...$rule_sets );

		if ( empty( $all_rules ) ) {
			return [];
		}

		return ( new WorkingPeriodsResolver( $all_rules, $from_date, $to_date ) )->get_periods();
	}

	/**
	 * Get the default (global) regular working schedule rules from plugin options.
	 *
	 * @since 1.4.0
	 *
	 * @return array<AbstractWorkingScheduleRule>
	 */
	public function get_default_regular_rules(): array {

		$options          = Plugin::get( Options::class );
		$regular          = $options->get( 'availability.working_schedule.regular' );
		$time_zone_string = $options->get( 'availability.working_schedule.timezone' );

		if ( ! is_array( $regular ) ) {
			return [];
		}

		try {
			$time_zone = new DateTimeZone( $time_zone_string );
		} catch ( Exception $e ) {
			$time_zone = wp_timezone();
		}

		return array_filter(
			array_map(
				fn( $item ) => WorkingScheduleRuleFactory::from_array( $item, $time_zone, null, 0 ),
				$regular
			)
		);
	}

	/**
	 * Get the default (global) override working schedule rules from plugin options.
	 *
	 * @since 1.4.0
	 *
	 * @return array<AbstractWorkingScheduleRule>
	 */
	public function get_default_override_rules(): array {

		$options          = Plugin::get( Options::class );
		$overrides        = $options->get( 'availability.working_schedule.overrides' );
		$time_zone_string = $options->get( 'availability.working_schedule.timezone' );

		if ( ! is_array( $overrides ) ) {
			return [];
		}

		try {
			$time_zone = new DateTimeZone( $time_zone_string );
		} catch ( Exception $e ) {
			$time_zone = wp_timezone();
		}

		return array_filter(
			array_map(
				fn( $item ) => WorkingScheduleRuleFactory::from_array( $item, $time_zone, null, 10 ),
				$overrides
			)
		);
	}

	/**
	 * Get regular working schedule rules.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Add fallback to default working schedule.
	 *
	 * @param ServiceModel       $service  Service model.
	 * @param LocationModel|null $location Location model.
	 * @param EmployeeModel|null $employee Employee model.
	 *
	 * @return array<AbstractWorkingScheduleRule>
	 */
	private function get_regular( $service, $location = null, $employee = null ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		if ( $employee && ! $employee->use_default_working_schedule ) {
			$regular          = $employee->working_schedule['regular'] ?? [];
			$time_zone_string = $employee->working_schedule['timezone'] ?? WorkingScheduleDefaults::get_timezone();
		} elseif ( ! $service->use_default_working_schedule ) {
			$regular          = $service->working_schedule['regular'] ?? [];
			$time_zone_string = $service->working_schedule['timezone'] ?? WorkingScheduleDefaults::get_timezone();
		} else {
			$options          = Plugin::get( Options::class );
			$regular          = $options->get( 'availability.working_schedule.regular' );
			$time_zone_string = $options->get( 'availability.working_schedule.timezone' );
		}

		if ( ! is_array( $regular ) ) {
			return [];
		}

		try {
			$time_zone = new DateTimeZone( $time_zone_string );
		} catch ( Exception $e ) {
			$time_zone = wp_timezone();
		}

		return array_filter( array_map( fn( $item ) => WorkingScheduleRuleFactory::from_array( $item, $time_zone, $employee, 0 ), $regular ) );
	}

	/**
	 * Get working schedule override rules.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Add fallback to default working schedule.
	 *
	 * @param ServiceModel       $service  Service model.
	 * @param LocationModel|null $location Location model.
	 * @param EmployeeModel|null $employee Employee model.
	 *
	 * @return array<AbstractWorkingScheduleRule>
	 */
	private function get_overrides( $service, $location = null, $employee = null ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		if ( $employee && ! $employee->use_default_working_schedule ) {
			$overrides        = $employee->working_schedule['overrides'] ?? [];
			$time_zone_string = $employee->working_schedule['timezone'] ?? WorkingScheduleDefaults::get_timezone();
		} elseif ( ! $service->use_default_working_schedule ) {
			$overrides        = $service->working_schedule['overrides'] ?? [];
			$time_zone_string = $service->working_schedule['timezone'] ?? WorkingScheduleDefaults::get_timezone();
		} else {
			$options          = Plugin::get( Options::class );
			$overrides        = $options->get( 'availability.working_schedule.overrides' );
			$time_zone_string = $options->get( 'availability.working_schedule.timezone' );
		}

		if ( ! is_array( $overrides ) ) {
			return [];
		}

		try {
			$time_zone = new DateTimeZone( $time_zone_string );
		} catch ( Exception $e ) {
			$time_zone = wp_timezone();
		}

		return array_filter( array_map( fn( $item ) => WorkingScheduleRuleFactory::from_array( $item, $time_zone, $employee, 10 ), $overrides ) );
	}
}
