<?php
/**
 * Weekday Sync Service
 */

namespace RadiusTheme\RadiusBooking\Services\EmployeeServices;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}
use Exception;
use InvalidArgumentException;
use RadiusTheme\RadiusBooking\Repositories\UserRepository;

/**
 * Class UserWeekdaySyncService
 *
 * Handles synchronization of user weekdays
 *
 * @package RadiusTheme\RadiusBooking\Services\UserServices
 * @since 1.0.0
 */
class WeekdaySyncService {

	/**
	 * The user repository instance.
	 *
	 * @var UserRepository $userRepo
	 */
	private UserRepository $userRepo;

	/**
	 * The weekday relation sync service instance.
	 *
	 * @var WeekdayRelationSyncService $relationSyncService
	 */
	private WeekdayRelationSyncService $relationSyncService;

	/**
	 * Constructor method.
	 *
	 * @param UserRepository $userRepo Instance of the UserRepository.
	 * @param WeekdayRelationSyncService $relationSyncService Instance of the WeekdayRelationSyncService.
	 *
	 * @return void
	 */
	public function __construct(
		UserRepository $userRepo,
		WeekdayRelationSyncService $relationSyncService
	) {
		$this->userRepo            = $userRepo;
		$this->relationSyncService = $relationSyncService;
	}

	/**
	 * Synchronizes the weekdays for a specific user by validating the input data, comparing with existing values,
	 * and handling the necessary deletions and updates.
	 *
	 * @param int $userId The ID of the user whose weekdays are being synchronized.
	 * @param array $weekdays An array of weekdays to be synchronized for the user.
	 *
	 * @return void
	 */
	public function syncWeekdays( int $userId, array $weekdays ): void {
		$this->validateWeekdaysData( $weekdays );
		$existing = $this->userRepo->getUserWeekdays( $userId );
		$this->handleWeekdayDeletion( $userId, $existing, $weekdays );
		$this->handleWeekdaySync( $userId, $weekdays );
	}

	/**
	 * Validates the provided weekdays data by checking the presence of required keys.
	 *
	 * @param array $weekdays An array of weekdays where each must include 'day_index', 'start_time', and 'end_time'.
	 *
	 * @return void
	 *
	 * @throws InvalidArgumentException If required keys are missing in any weekday.
	 */
	private function validateWeekdaysData( array $weekdays ): void {
		foreach ( $weekdays as $weekday ) {
			if ( ! isset( $weekday['day_index'], $weekday['start_time'], $weekday['end_time'] ) ) {
				throw new InvalidArgumentException( 'Day index, start time, and end time are required for each weekday' );
			}
		}
	}

	/**
	 * Handles the deletion of weekdays no longer associated with a user.
	 *
	 * @param int $userId The ID of the user whose weekdays are being managed.
	 * @param array $existing An array of existing weekday associations, each containing an 'id'.
	 * @param array $new An array of new weekday associations, each containing an 'id'.
	 *
	 * @return void
	 */
	private function handleWeekdayDeletion( int $userId, array $existing, array $new ): void {
		$existingIds = array_filter( array_column( $existing, 'id' ) );
		$newIds      = array_filter( array_column( $new, 'id' ) );
		$toDelete    = array_diff( $existingIds, $newIds );
		foreach ( $toDelete as $id ) {
			$this->deleteUserWeekday( $userId, $id );
		}
	}

	/**
	 * Deletes a specific weekday for a user, along with its related data such as break times
	 * and time periods.
	 *
	 * @param int $userId The ID of the user whose weekday should be deleted.
	 * @param int $deleteId The ID of the weekday to be deleted.
	 *
	 * @return void
	 */
	private function deleteUserWeekday( int $userId, int $deleteId ): void {
		$employeeWeekday = $this->userRepo->findEmployeeWeekdayById( $userId, $deleteId, true );
		$weekdayId       = $employeeWeekday->get_id();

		$this->userRepo->deleteBreakTimesByWeekday( $weekdayId );
		$this->deleteTimePeriods( $weekdayId );
		$employeeWeekday->delete();
	}

	/**
	 * Deletes time periods and their associated locations and services for the specified weekday.
	 *
	 * @param int $weekdayId The ID of the weekday for which time periods should be deleted.
	 *
	 * @return void
	 */
	private function deleteTimePeriods( int $weekdayId ): void {
		$timePeriods    = $this->userRepo->getTimePeriods( $weekdayId );
		$timePeriodsIds = array_column( $timePeriods, 'id' );

		if ( ! empty( $timePeriodsIds ) ) {
			$this->userRepo->deleteTimePeriodLocations( $timePeriodsIds );
			$this->userRepo->deleteTimePeriodServices( $timePeriodsIds );
			$this->userRepo->deleteTimePeriods( $timePeriodsIds );
		}
	}

	/**
	 * Synchronizes the weekdays for a specific user by updating or creating associated weekday data
	 * and synchronizing their relationships.
	 *
	 * @param int $userId The ID of the user for whom the weekday synchronization will be performed.
	 * @param array $weekdays An array of weekdays to be synchronized for the user.
	 *
	 * @return void
	 * @throws Exception If an error occurs during the synchronization process.
	 * @throws \RuntimeException If an error occurs while saving the employee weekday.
	 */
	private function handleWeekdaySync( int $userId, array $weekdays ): void {
		foreach ( $weekdays as $weekday ) {
			try {
				$employeeWeekday = $this->userRepo->findOrCreateEmployeeWeekday( $weekday );
				$this->userRepo->updateEmployeeWeekday( $employeeWeekday, $userId, $weekday );

				$weekdayId = $employeeWeekday->get_id();
				if ( ! $weekdayId ) {
					throw new \RuntimeException( 'Failed to save employee weekday' );
				}

				$this->relationSyncService->syncWeekdayRelations( $userId, $weekdayId, $weekday );
			} catch ( Exception $e ) {
				$this->logSyncError( $e, 'weekday' );
				throw $e;
			}
		}
	}

	/**
	 * Logs an error encountered during the synchronization process.
	 *
	 * @param Exception $e The exception instance containing error details.
	 * @param string $type The type of synchronization process where the error occurred.
	 *
	 * @return void
	 */
	private function logSyncError( Exception $e, string $type ): void {
		error_log( 'Error syncing user: ' . $type . ' ' . $e->getMessage() );
	}
}
