<?php

namespace RadiusTheme\RadiusBooking\Services\EmployeeServices;

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

/**
 * Class UserServiceSyncService
 *
 * Handles synchronization of user services
 *
 * @package RadiusTheme\RadiusBooking\Services\UserServices
 * @since 1.0.0
 */
class ServiceSyncService {

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

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

	/**
	 * Synchronizes the provided user services with the existing services.
	 *
	 * @param int $userId The ID of the user whose services are being synchronized.
	 * @param array $userServices An array of services to be synchronized for the user.
	 *
	 * @return void
	 */
	public function syncServices( int $userId, array $userServices ): void {
		$this->validateServicesData( $userServices );

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

		$this->handleServiceDeletion( $userId, $existing, $userServices );
		$this->handleServiceSync( $userId, $userServices );
	}

	/**
	 * Validates the provided services data to ensure each service includes a service ID.
	 *
	 * @param array $services The array of service data to validate.
	 *
	 * @return void
	 * @throws InvalidArgumentException If a service is missing the 'service_id' field.
	 */
	private function validateServicesData( array $services ): void {
		foreach ( $services as $service ) {
			if ( ! isset( $service['service_id'] ) ) {
				throw new InvalidArgumentException( 'Service ID is required for each service' );
			}
		}
	}

	/**
	 * Handle the deletion of user services that are no longer needed.
	 *
	 * @param int $userId The ID of the user whose services are being managed.
	 * @param array $existing An array of existing services for the user, each containing an 'id'.
	 * @param array $new An array of new services for the user, each containing an 'id'.
	 *
	 * @return void
	 */
	private function handleServiceDeletion( 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->deleteUserService( $userId, $id );
		}
	}

	/**
	 * Handles the synchronization of services for a user.
	 *
	 * @param int $userId The ID of the user whose services are being synchronized.
	 * @param array $services The list of services to process for synchronization.
	 *
	 * @return void
	 * @throws Exception If an error occurs during the synchronization process.
	 */
	private function handleServiceSync( int $userId, array $services ): void {
		foreach ( $services as $service ) {
			try {
				$this->validateServiceData( $service );
				$employeeService = $this->findOrCreateEmployeeService( $service );
				$this->updateEmployeeService( $employeeService, $userId, $service );
			} catch ( Exception $e ) {
				$this->logSyncError( $e, 'service' );
				throw $e;
			}
		}
	}

	/**
	 * Validates the provided service data to ensure required fields are present.
	 *
	 * @param array $service An array containing service information to be validated.
	 *
	 * @return void
	 * @throws InvalidArgumentException If the service data does not contain a required service ID.
	 */
	private function validateServiceData( array $service ): void {
		if ( ! isset( $service['service_id'] ) ) {
			throw new InvalidArgumentException( 'Service ID is required' );
		}
	}

	/**
	 * Finds an existing employee service or creates a new one based on the provided service data.
	 *
	 * @param array $service The data representing the employee service to find or create.
	 *                        Must include specific attributes such as an 'id' if required.
	 *
	 * @return object The employee service object that was found or created.
	 *
	 * @throws \RuntimeException If the service data contains an 'id', but no corresponding employee service is found.
	 */
	private function findOrCreateEmployeeService( array $service ): object {
		$employeeService = $this->userRepo->findOrCreateEmployeeService( $service );

		if ( isset( $service['id'] ) && ! $employeeService->id ) {
			throw new \RuntimeException( 'Employee service not found' );
		}

		return $employeeService;
	}

	/**
	 * Updates the provided employee service with new data.
	 *
	 * @param object $employeeService The employee service object to be updated.
	 * @param int $userId The ID of the user associated with the employee service.
	 * @param array $service The updated data for the employee service.
	 *
	 * @return void
	 */
	private function updateEmployeeService( object $employeeService, int $userId, array $service ): void {
		$this->userRepo->updateEmployeeService( $employeeService, $userId, $service );
	}

	/**
	 * Logs an error that occurs during the user synchronization process.
	 *
	 * @param Exception $e The exception object containing details about the error.
	 * @param string $type A description or identifier of the type of synchronization being attempted.
	 *
	 * @return void
	 */
	private function logSyncError( Exception $e, string $type ): void {
		error_log( 'Error syncing user: ' . $type . ' ' . $e->getMessage() );
	}
}
