<?php

namespace RadiusTheme\RadiusBooking\Services\EmployeeServices;

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

/**
 * Class UserDaysOffSyncService
 *
 * Handles synchronization of user days off
 *
 * @package RadiusTheme\RadiusBooking\Services\UserServices
 * @since 1.0.0
 */
class DaysOffSyncService {

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

	/**
	 * Constructor
	 *
	 * @param UserRepository $userRepo  User repository instance.
	 */
	public function __construct( UserRepository $userRepo ) {
		$this->userRepo = $userRepo;
	}

	/**
	 * Synchronizes the user's days off by validating the provided data,
	 * handling deletion of removed days off, and syncing the updated days off.
	 *
	 * @param int $userId The ID of the user whose days off are being synchronized.
	 * @param array $daysOff An array containing the updated days off data.
	 *
	 * @return void
	 */
	public function syncDaysOff( int $userId, array $daysOff ): void {
		$this->validateDaysOffData( $daysOff );

		$existing = $this->userRepo->getUserDaysOff( $userId );

		$this->handleDaysOffDeletion( $userId, $existing, $daysOff );
		$this->handleDaysOffSync( $userId, $daysOff );
	}

	/**
	 * Validates the provided days off data to ensure that each entry contains a start date and an end date.
	 *
	 * @param array $daysOff An array of days off, where each entry should include 'start_date' and 'end_date' keys.
	 *
	 * @return void
	 *
	 * @throws InvalidArgumentException If any day off entry is missing the 'start_date' or 'end_date' keys.
	 */
	private function validateDaysOffData( array $daysOff ): void {
		foreach ( $daysOff as $dayOff ) {
			if ( ! isset( $dayOff['start_date'], $dayOff['end_date'] ) ) {
				throw new InvalidArgumentException( 'Start date and end date are required for each day off' );
			}
		}
	}

	/**
	 * Handles the deletion of user days off by identifying which existing days off
	 * are no longer present in the new list and removing them.
	 *
	 * @param int $userId The ID of the user whose days off are being managed.
	 * @param array $existing The current list of days off for the user, represented as an array of associative arrays containing at least an 'id' key.
	 * @param array $new The updated list of days off for the user, represented as an array of associative arrays containing at least an 'id' key.
	 *
	 * @return void
	 */
	private function handleDaysOffDeletion( int $userId, array $existing, array $new ): void {
		$existingIds = array_column( $existing, 'id' );
		$newIds      = array_column( $new, 'id' );
		$toDelete    = array_diff( $existingIds, $newIds );

		foreach ( $toDelete as $id ) {
			$this->userRepo->deleteUserDayOff( $userId, $id );
		}
	}

	/**
	 * Synchronizes the user's days off by finding existing records or creating new ones,
	 * and updating them with the given data. Logs and throws any errors encountered during the process.
	 *
	 * @param int $userId The ID of the user whose days off are being synchronized.
	 * @param array $daysOff The list of days off to be synchronized, represented as an array of associative arrays containing relevant day off data.
	 *
	 * @return void
	 * @throws Exception If an error occurs during the synchronization process.
	 */
	private function handleDaysOffSync( int $userId, array $daysOff ): void {
		foreach ( $daysOff as $dayOff ) {
			try {
				$employeeDayOff = $this->userRepo->findOrCreateEmployeeDayOff( $dayOff );
				$this->userRepo->updateEmployeeDayOff( $employeeDayOff, $userId, $dayOff );
			} catch ( Exception $e ) {
				$this->logSyncError( $e, 'day off' );
				throw $e;
			}
		}
	}

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