<?php

/**
 * Delete the location and service dependencies for multiple time periods.
 *
 * This method iterates over a list of time period IDs, deleting any associated
 * location and service records tied to those time periods.
 *
 * @param array $timePeriodIds Array of time period IDs whose dependencies should be deleted.
 *
 * @return void
 * @since 1.0.0
 */
namespace RadiusTheme\RadiusBooking\Services\EmployeeServices;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}
use RadiusTheme\RadiusBooking\Models\EmployeeSpecialDayTimePeriod;
use RadiusTheme\RadiusBooking\Models\EmployeeSpecialDayTimePeriodLocation;
use RadiusTheme\RadiusBooking\Models\EmployeeSpecialDayTimePeriodService;
use RadiusTheme\RadiusBooking\Repositories\UserRepository;

/**
 * Class SpecialDaySyncService
 */
class SpecialDaySyncService {
	/**
	 * The user repository instance.
	 *
	 * @var UserRepository
	 */

	private UserRepository $userRepo;

	/**
	 * SpecialDaySyncService constructor.
	 *
	 * @param UserRepository $userRepo The user repository instance.
	 */
	public function __construct( UserRepository $userRepo ) {
		$this->userRepo = $userRepo;
	}

	/**
	 * Sync special days for a user by validating, deleting, and updating records.
	 *
	 * This method first validates the input data, then deletes removed special days,
	 * and finally updates or creates the remaining ones.
	 *
	 * @param int $userId The ID of the user.
	 * @param array $specialDays An array of special day data to sync.
	 *
	 * @return void
	 * @throws \Exception If any step in the sync process fails.
	 * @since 1.0.0
	 */
	public function syncSpecialDays( int $userId, array $specialDays ): void {
		$this->validateSpecialDaysData( $specialDays );
		$existing = $this->userRepo->getUserSpecialDays( $userId );
		$this->handleSpecialDayDeletion( $userId, $existing, $specialDays );
		$this->handleSpecialDaySync( $userId, $specialDays );
	}
	/**
	 * Delete user special days that are no longer present in the new list.
	 *
	 * Compares the IDs of existing special days with the new list and deletes
	 * those that are not included in the new data.
	 *
	 * @since 1.0.0
	 *
	 * @param int   $userId      The ID of the user.
	 * @param array $existing    An array of existing special day records.
	 * @param array $specialDays The new array of special day data.
	 *
	 * @return void
	 */
	public function handleSpecialDayDeletion( $userId, $existing, $specialDays ) {
		$existingIds = array_column( $existing, 'id' );
		$newIds      = array_column( $specialDays, 'id' );
		$toDelete    = array_diff( $existingIds, $newIds );
		foreach ( $toDelete as $id ) {
			$this->deleteUserSpecialDay( $userId, $id );
		}
	}

	/**
	 * Delete a user's special day and its associated time periods and dependencies.
	 *
	 * This method finds the special day by user ID and special day ID, deletes its time periods,
	 * and then deletes the special day itself.
	 *
	 * @since 1.0.0
	 *
	 * @param int $userId    The ID of the user.
	 * @param int $deleteId  The ID of the special day to delete.
	 *
	 * @return void
	 */
	public function deleteUserSpecialDay( $userId, $deleteId ) {
		$employeeSpecialDay = $this->userRepo->findSpecialDayById( $userId, $deleteId, true );
		$specialDayId       = $employeeSpecialDay->get_id();
		$this->deleteTimePeriod( $specialDayId );
		$employeeSpecialDay->delete();
	}

	/**
	 * Delete all time periods and their location/service dependencies for a given special day.
	 *
	 * This method retrieves all time periods linked to the special day and deletes them
	 * along with their associated location and service records.
	 *
	 * @since 1.0.0
	 *
	 * @param int $specialDayId The ID of the special day whose time periods are to be deleted.
	 *
	 * @return void
	 */
	public function deleteTimePeriod( $specialDayId ): void {
		$timePeriods = $this->userRepo->getSpecialDayTimePeriods( $specialDayId );
		if ( ! empty( $timePeriods ) ) {
			$timePeriodIds = array_column( $timePeriods, 'id' );
			if ( ! empty( $timePeriodIds ) ) {
				$this->deleteLocationAndServiceDependencies( $timePeriodIds );
			}
			EmployeeSpecialDayTimePeriod::forceDeleteMany( $timePeriodIds );
		}
	}

	/**
	 * Sync special day records and their associated time periods for a user.
	 *
	 * For each special day in the input array:
	 * - Validates the data.
	 * - Finds or creates the special day record.
	 * - Updates it with the new data.
	 * - Retrieves the special day ID.
	 * - If time periods are provided, syncs them as well.
	 *
	 * Exceptions during processing are logged and rethrown.
	 *
	 * @since 1.0.0
	 *
	 * @param int   $userId      The ID of the user.
	 * @param array $specialDays An array of special day data, each possibly including 'time_periods'.
	 *
	 * @return void
	 *
	 * @throws \Exception If any step in the sync process fails.
	 */
	public function handleSpecialDaySync( $userId, $specialDays ) {
		foreach ( $specialDays as $day ) {
			try {
				$this->validateSpecialDayData( $day );
				$employeeSpecialDay = $this->userRepo->findOrNewSpecialDay( $day );
				$this->userRepo->updateSpecialDay( $employeeSpecialDay, $userId, $day );

				$special_day_id = $employeeSpecialDay->get_id();
				if ( ! $special_day_id ) {
					throw new \Exception( 'Failed to retrieve special day ID after update' );
				}
				if ( isset( $day['time_periods'] ) ) {
					$this->syncSpecialDayTimePeriods( $special_day_id, $day['time_periods'] );
				}
			} catch ( \Exception $e ) {
				error_log( 'Error syncing special day: ' . $e->getMessage() );
				throw $e;
			}
		}
	}
	/**
	 * Sync all time periods associated with a special day.
	 *
	 * This method retrieves existing time periods for the special day,
	 * deletes the ones not present in the new list, and updates or creates the rest.
	 *
	 * @since 1.0.0
	 *
	 * @param int   $specialDayId The ID of the special day.
	 * @param array $timePeriods  Array of time period data to sync.
	 *
	 * @return void
	 */
	public function syncSpecialDayTimePeriods( $specialDayId, array $timePeriods ): void {

		$existing = $this->userRepo->getSpecialDayTimePeriods( $specialDayId );

		$this->handleSpecialDayTimePeriodDeletion( $specialDayId, $existing, $timePeriods );
		$this->handleSpecialDayTimePeriodSync( $specialDayId, $timePeriods );
	}
	/**
	 * Delete special day time periods that are not included in the new time period list.
	 *
	 * Compares the IDs of existing time periods with the new time period data and deletes
	 * any that are missing from the new list. Also deletes associated location and service
	 * dependencies before removing the time periods.
	 *
	 * @since 1.0.0
	 *
	 * @param int   $specialDayId The ID of the special day.
	 * @param array $existing     Array of existing time period records (with 'id' keys).
	 * @param array $timePeriods  Array of new time period data (with optional 'id' keys).
	 *
	 * @return void
	 */
	public function handleSpecialDayTimePeriodDeletion( $specialDayId, $existing, $timePeriods ) {
		$existingIds = array_column( $existing, 'id' );
		$newIds      = array_column( $timePeriods, 'id' );
		$toDelete    = array_diff( $existingIds, $newIds );

		if ( ! empty( $toDelete ) ) {
			$this->deleteLocationAndServiceDependencies( $toDelete );
			EmployeeSpecialDayTimePeriod::forceDeleteMany( $toDelete );
		}
	}

	/**
	 * Delete all location and service dependencies for the given time period IDs.
	 *
	 * This method ensures that both associated locations and services
	 * for the given special day time periods are force deleted.
	 *
	 * @since 1.0.0
	 *
	 * @param array $timePeriodIds Array of special day time period IDs.
	 *
	 * @return void
	 */
	public function deleteLocationAndServiceDependencies( array $timePeriodIds ): void {
		$this->deleteLocation( $timePeriodIds );
		$this->deleteService( $timePeriodIds );
	}
	/**
	 * Delete all location records associated with the given time period IDs.
	 *
	 * This method fetches all EmployeeSpecialDayTimePeriodLocation records
	 * linked to the given time periods and force deletes them.
	 *
	 * @since 1.0.0
	 *
	 * @param array $deleteIds Array of special day time period IDs.
	 *
	 * @return void
	 */
	public function deleteLocation( $deleteIds ): void {
		$timePeriodLocations = EmployeeSpecialDayTimePeriodLocation::whereIn( 'special_day_time_period_id', $deleteIds )->get();

		if ( ! empty( $timePeriodLocations ) ) {
			$locationIds = array_column( $timePeriodLocations, 'id' );
			EmployeeSpecialDayTimePeriodLocation::forceDeleteMany( $locationIds );
		}
	}

	/**
	 * Delete all service records associated with the given special day time period IDs.
	 *
	 * This method fetches and force-deletes all related EmployeeSpecialDayTimePeriodService records.
	 *
	 * @since 1.0.0
	 *
	 * @param array $deleteIds Array of special day time period IDs.
	 *
	 * @return void
	 */
	public function deleteService( $deleteIds ): void {
		$timePeriodServices = EmployeeSpecialDayTimePeriodService::whereIn( 'special_day_time_period_id', $deleteIds )->get();

		if ( ! empty( $timePeriodServices ) ) {
			$serviceIds = array_column( $timePeriodServices, 'id' );
			EmployeeSpecialDayTimePeriodService::forceDeleteMany( $serviceIds );
		}
	}

	/**
	 * Sync the time periods of a special day, including their locations and services.
	 *
	 * This method loops through each time period:
	 * - Finds or creates the record.
	 * - Updates it with new data.
	 * - Retrieves its ID.
	 * - Then syncs its locations and services.
	 *
	 * Exceptions during processing are logged and rethrown.
	 *
	 * @since 1.0.0
	 *
	 * @param int   $specialDayId The ID of the special day.
	 * @param array $timePeriods  Array of time period data (each may include locations/services).
	 *
	 * @return void
	 *
	 * @throws \Exception If syncing any time period fails.
	 */
	public function handleSpecialDayTimePeriodSync( $specialDayId, $timePeriods ): void {
		foreach ( $timePeriods as $period ) {
			try {
				$employeeTimePeriod = $this->userRepo->findOrNewSpecialDayTimePeriod( $period );
				$this->userRepo->updateSpecialDayTimePeriod( $employeeTimePeriod, $specialDayId, $period );

				$time_period_id = $employeeTimePeriod->get_id();
				$this->syncLocationSevice( $time_period_id, $period ?? array() );
			} catch ( \Exception $e ) {
				error_log( 'Error syncing special day time period: ' . $e->getMessage() );
				throw $e;
			}
		}
	}

	/**
	 * Sync the locations and services for a given special day time period.
	 *
	 * This method checks if 'locations' and 'services' are present in the period data
	 * and delegates their handling to the appropriate update methods.
	 *
	 * @since 1.0.0
	 *
	 * @param int   $timePeriodId The ID of the special day time period.
	 * @param array $period       The time period data which may include:
	 *                            - 'locations': array of location data
	 *                            - 'services' : array of service data
	 *
	 * @return void
	 */
	public function syncLocationSevice( $timePeriodId, array $period ): void {
		if ( isset( $period['locations'] ) ) {
			$this->updateTimePeriodLocation( $timePeriodId, $period['locations'] );
		}
		if ( isset( $period['services'] ) ) {
			$this->updateTimePeriodService( $timePeriodId, $period['services'] );
		}
	}

	/**
	 * Update locations for a specific special day time period.
	 *
	 * This method:
	 * - Compares the existing location IDs with the new ones.
	 * - Deletes any that are no longer included.
	 * - Updates or creates each new location entry.
	 *
	 * @since 1.0.0
	 *
	 * @param int   $timePeriodId The ID of the special day time period.
	 * @param array $locations    An array of location data, each containing an optional 'id' key.
	 *
	 * @return void
	 */
	public function updateTimePeriodLocation( $timePeriodId, $locations ): void {

		$existing          = $this->userRepo->getSpecialDayTimePeriodLocations( $timePeriodId );
		$existingLocations = array_column( $existing, 'id' );
		$newLocations      = array_column( $locations, 'id' );
		$toDelete          = array_diff( $existingLocations, $newLocations );
		if ( ! empty( $toDelete ) ) {
			EmployeeSpecialDayTimePeriodLocation::forceDeleteMany( $toDelete );

		}

		foreach ( $locations as $location ) {
			$specialLocation = $this->userRepo->findOrNewSpecialDayTimePeriodLocations( $location );
			$this->userRepo->updateSpecialTimePeriodLocations( $specialLocation, $timePeriodId, $location );

		}
	}

	/**
	 * Update services for a specific special day time period.
	 *
	 * This method:
	 * - Compares existing service IDs with new ones.
	 * - Deletes services no longer in the provided list.
	 * - Updates or creates new service records as needed.
	 *
	 * @since 1.0.0
	 *
	 * @param int   $timePeriodId The ID of the special day time period.
	 * @param array $services     Array of service data, each possibly containing an 'id' key.
	 *
	 * @return void
	 */
	public function updateTimePeriodService( $timePeriodId, $services ): void {
		$existing         = $this->userRepo->getSpecialDayTimePeriodServices( $timePeriodId );
		$existingServices = array_column( $existing, 'id' );
		$newServices      = array_column( $services, 'id' );
		$toDelete         = array_diff( $existingServices, $newServices );
		if ( ! empty( $toDelete ) ) {
			EmployeeSpecialDayTimePeriodService::forceDeleteMany( $toDelete );
		}

		foreach ( $services as $service ) {
			$specialService = $this->userRepo->findOrNewSpecialDayTimePeriodService( $service );
			$this->userRepo->updateSpecialDayTimePeriodService( $specialService, $timePeriodId, $service );
		}
	}

	/**
	 * Validate data for a single special day.
	 *
	 * Ensures that the special day array contains the required keys: 'start_date' and 'end_date'.
	 * Throws an InvalidArgumentException if validation fails.
	 *
	 * @since 1.0.0
	 *
	 * @param array $day The special day data to validate.
	 *
	 * @return void
	 *
	 * @throws \InvalidArgumentException If required keys are missing.
	 */
	public function validateSpecialDayData( array $day ): void {
		if ( ! isset( $day['start_date'] ) || ! isset( $day['end_date'] ) ) {
			throw new \InvalidArgumentException( 'Each special day must have a start date, end date, and name' );
		}
	}

	/**
	 * Validate an array of special day data.
	 *
	 * Iterates through each special day in the array and ensures required keys exist.
	 * Throws an InvalidArgumentException if any special day is missing required data.
	 *
	 * @since 1.0.0
	 *
	 * @param array $specialDays Array of special day data arrays.
	 *
	 * @return void
	 *
	 * @throws \InvalidArgumentException If any special day is missing required keys.
	 */
	public function validateSpecialDaysData( array $specialDays ): void {
		foreach ( $specialDays as $day ) {
			if ( ! isset( $day['start_date'] ) || ! isset( $day['end_date'] ) ) {
				throw new \InvalidArgumentException( 'Each special day must have a date and name' );
			}
		}
	}
}
