<?php
/**
 * Service API Controller
 *
 * Handles CRUD operations for services with authentication, permissions, and validation.
 * Extends BaseController to inherit standard REST API functionality.
 *
 * @package RadiusTheme\RadiusBooking\Controllers
 */

namespace RadiusTheme\RadiusBooking\Controllers;

use RadiusTheme\RadiusBooking\Abstracts\BaseController;
use RadiusTheme\RadiusBooking\Core\Api\ApiResponse;
use RadiusTheme\RadiusBooking\Core\Api\Middleware\AuthMiddleware;
use RadiusTheme\RadiusBooking\Core\Api\Middleware\PermissionMiddleware;
use RadiusTheme\RadiusBooking\Core\Api\Validation\Rules\IntegerRule;
use RadiusTheme\RadiusBooking\Core\Api\Validation\Rules\RequiredRule;
use RadiusTheme\RadiusBooking\Core\Container\Container;
use RadiusTheme\RadiusBooking\Core\Support\Collection;
use RadiusTheme\RadiusBooking\Models\Category;
use RadiusTheme\RadiusBooking\Models\EmployeeService;
use RadiusTheme\RadiusBooking\Models\Service;
use RadiusTheme\RadiusBooking\Models\User;
use RadiusTheme\RadiusBooking\Repositories\ServiceRepository;
use RadiusTheme\RadiusBooking\Repositories\UserRepository;
use RadiusTheme\RadiusBooking\Resources\ServiceResource;
use RadiusTheme\RadiusBooking\Services\EmployeeServices\ServiceSyncService;
use RadiusTheme\RadiusBooking\Services\UserService;
use WP_REST_Request;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

/**
 * ServiceController Class
 *
 * Provides RESTful API endpoints for service management:
 * - GET /services (index) - List all services with pagination
 * - GET /services/{id} (show) - Get single service
 * - POST /services (store) - Create new service
 * - PUT /services/{id} (update) - Update existing service
 * - DELETE /services/{id} (destroy) - Delete service
 */
class ServiceController extends BaseController {

	/**
	 * ServiceController constructor.
	 *
	 * Initializes the controller with ServiceRepository and applies middleware:
	 * - AuthMiddleware: Ensures user authentication
	 * - PermissionMiddleware: Requires 'manage_options' capability for service management
	 */
	public function __construct() {
		parent::__construct( Container::resolve( ServiceRepository::class ) );

		// Apply authentication middleware

		// Apply permission middleware - only users with manage_options can manage services
		$this->middleware[] = new PermissionMiddleware( 'manage_options' );
		add_action( 'radius_booking_item_created_service', array( $this, 'handleServiceCreated' ), 10, 4 );
		add_action( 'radius_booking_item_updated_service', array( $this, 'handleServiceUpdated' ), 10, 3 );
	}

	/**
	 * Transform a single service item for API response.
	 *
	 * Uses ServiceResource to format the service data according to API specifications.
	 *
	 * @param mixed $item The service item to transform
	 *
	 * @return array Transformed service data
	 */
	protected function transformItem( $item ): array {
		return ( new ServiceResource() )->transform( $item );
	}

	/**
	 * Transform a collection of services for API response.
	 *
	 * @param array $items Collection of service items to transform
	 *
	 * @return array Transformed collection of services
	 */
	protected function transformCollection( array $items ): array {
		return ServiceResource::collection( $items );
	}

	/**
	 * Get the validation rules for the specified request and context.
	 *
	 * Provides a set of validation rules to ensure the data adheres to expected formats and constraints.
	 *
	 * @param mixed $request The incoming request containing data to validate
	 * @param string $context The context for the validation, defaults to 'create'
	 *
	 * @return array An array of validation rules
	 */
	protected function getValidationRules( $request, string $context = 'create' ): array {
		$rules = array(
			'name'        => array( new RequiredRule() ),
			'duration'    => array( new RequiredRule(), new IntegerRule() ),
//			'price'       => array( new RequiredRule() ),
			'category_id' => array( new IntegerRule() ),
		);

		return $rules;
	}

	/**
	 * Get the resource type identifier.
	 *
	 * Used for hooks, filters, and resource identification throughout the system.
	 *
	 * @return string Resource type identifier
	 */
	protected function getResourceType(): string {
		return 'service';
	}

	/**
	 * Retrieve all available services.
	 *
	 * Fetches a list of all services and returns a standardized API response.
	 *
	 * @param WP_REST_Request $request The REST request object containing request details.
	 *
	 * @return WP_HTTP_Response API response containing the list of services and a success message.
	 */
	public function getAllServices( WP_REST_Request $request ) {
		$categories = $this->fetchAllServices();

		return ApiResponse::success( array( $categories ), 'Resource All fetch successfully' )->send();
	}

	/**
	 * Fetch all services grouped by categories.
	 *
	 * This method retrieves all categories with their associated services, including details such as
	 * service capacity, price, duration, settings, and recurring payment information. Results are
	 * structured into an array where each category includes its associated services.
	 *
	 * @return array An array of categories, where each category contains its ID, name, and a list of associated services.
	 */
	/**
	 * Fetch all services grouped by their categories.
	 *
	 * Services without a category are grouped under "Uncategorized".
	 * Each category contains an array of services with full details
	 * including pricing, duration, recurring settings, and deposit info.
	 *
	 * @return array Array of categories with their associated services.
	 */
	public function fetchAllServices() {
		$services = Service::query();

		$services_with_category = $services->leftJoin(
			'radius_booking_categories',
			'radius_booking_categories.id',
			'=',
			'radius_booking_services.category_id'
		)->select(
			radius_table_prefix() . 'categories.id as category_id',
			radius_table_prefix() . 'categories.name as category_name',
			radius_table_prefix() . 'services.id as service_id',
			radius_table_prefix() . 'services.name as service_name',
			radius_table_prefix() . 'services.min_capacity as min_capacity',
			radius_table_prefix() . 'services.max_capacity as max_capacity',
			radius_table_prefix() . 'services.price as price',
			radius_table_prefix() . 'services.duration as duration',
			radius_table_prefix() . 'services.settings as settings',
			radius_table_prefix() . 'services.recurring_cycle as recurring_cycle',
			radius_table_prefix() . 'services.recurring_sub as recurring_sub',
			radius_table_prefix() . 'services.recurring_payment as recurring_payment',
			radius_table_prefix() . 'services.deposit_payment as deposit_payment',
			radius_table_prefix() . 'services.deposit_per_person as deposit_per_person',
			radius_table_prefix() . 'services.deposit as deposit',
			radius_table_prefix() . 'services.full_payment as full_payment',
			radius_table_prefix() . 'services.deposit_type as deposit_type',
			radius_table_prefix() . 'services.aggregated_price as aggregated_price',
			radius_table_prefix() . 'services.bringing_anyone as bringing_anyone',
			radius_table_prefix() . 'services.max_extra_people as max_extra_people',
			radius_table_prefix() . 'services.picture_thumb_path as picture_thumb_path',
			radius_table_prefix() . 'services.picture_full_path as picture_full_path',
			radius_table_prefix() . 'services.description as description',
			radius_table_prefix() . 'services.is_visible as is_visible',
			radius_table_prefix() . 'services.source_type as source_type',
			radius_table_prefix() . 'services.listing_id as listing_id',
		)->get();

		// Collect all service IDs to batch-load assigned employees.
		$service_ids = array();
		foreach ( $services_with_category as $row ) {
			if ( ! empty( $row->service_id ) ) {
				$service_ids[] = $row->service_id;
			}
		}

		$staff_map = $this->getStaffMapForServices( $service_ids );

		$grouped = array();
		$uncategorized_key = 'uncategorized';

		foreach ( $services_with_category as $row ) {
			// Use 'uncategorized' key for services without a category.
			$catId = ! empty( $row->category_id ) ? $row->category_id : $uncategorized_key;

			if ( ! isset( $grouped[ $catId ] ) ) {
				$grouped[ $catId ] = array(
					'id'       => $row->category_id ?? 0,
					'name'     => $row->category_name ?? __( 'Uncategorized', 'radius-booking' ),
					'services' => array(),
				);
			}

			if ( ! empty( $row->service_id ) ) {
				$service = array(
					'service_id'         => $row->service_id,
					'id'                 => $row->service_id,
					'name'               => $row->service_name,
					'description'        => $row->description,
					'min_capacity'       => $row->min_capacity,
					'max_capacity'       => $row->max_capacity,
					'price'              => $row->price,
					'duration'           => $row->duration,
					'picture_thumb_path' => $row->picture_thumb_path,
					'picture_full_path' => $row->picture_full_path,
					'settings'           => maybe_unserialize( $row->settings ),
					'category_id'        => $row->category_id ?? 0,
					'is_visible'         => $row->is_visible,
					'currency'           => array(
						'currency_symbol' => get_rtrb_currency_symbol(),
						'currency'        => get_rtrb_currency(),
					),
					'recurring'          => array(
						'cycle'   => $row->recurring_cycle,
						'sub'     => $row->recurring_sub,
						'payment' => $row->recurring_payment,
					),
				);

				if ( ! empty( $row->deposit_payment ) ) {
					$service['deposit'] = array(
						'deposit_payment'    => $row->deposit_payment,
						'deposit_type'       => $row->deposit_type,
						'deposit_per_person' => $row->deposit_per_person,
						'deposit'            => $row->deposit,
						'full_payment'       => $row->full_payment,
					);
				}

				$service['bringing_anyone']  = $row->bringing_anyone;
				$service['aggregated_price'] = $row->aggregated_price;
				$service['max_extra_people'] = $row->max_extra_people;
				$service['source_type']      = $row->source_type ?? 'manual';
				$service['listing_id']       = $row->listing_id ?? null;
				$service['staff']            = $staff_map[ $row->service_id ] ?? array();

				$grouped[ $catId ]['services'][] = $service;
			}
		}

		// Move uncategorized to the end if it exists.
		if ( isset( $grouped[ $uncategorized_key ] ) ) {
			$uncategorized = $grouped[ $uncategorized_key ];
			unset( $grouped[ $uncategorized_key ] );
			$grouped[ $uncategorized_key ] = $uncategorized;
		}

		return array_values( $grouped );
	}

	/**
	 * Batch-load assigned employees for a list of service IDs.
	 *
	 * Returns a map of service_id => [ { id, name, image_url }, ... ].
	 *
	 * @param array $service_ids List of service IDs.
	 *
	 * @return array Keyed by service_id.
	 */
	private function getStaffMapForServices( array $service_ids ): array {
		if ( empty( $service_ids ) ) {
			return array();
		}

		global $wpdb;

		$prefix       = radius_table_prefix();
		$placeholders = implode( ',', array_fill( 0, count( $service_ids ), '%d' ) );

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$results = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT es.service_id, u.id AS user_id, p.first_name, p.last_name, p.image_id
				FROM {$prefix}employees_services AS es
				INNER JOIN {$prefix}users AS u ON u.id = es.user_id
				LEFT JOIN {$prefix}user_profiles AS p ON p.user_id = u.id
				WHERE es.service_id IN ($placeholders)", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				...$service_ids
			)
		);

		$map = array();

		foreach ( $results as $row ) {
			$name = trim( ( $row->first_name ?? '' ) . ' ' . ( $row->last_name ?? '' ) );
			if ( '' === $name ) {
				$name = __( 'Staff', 'radius-booking' );
			}

			$image_url = '';
			if ( ! empty( $row->image_id ) ) {
				$image_url = wp_get_attachment_image_url( (int) $row->image_id, 'thumbnail' ) ?: '';
			}

			$map[ $row->service_id ][] = array(
				'id'        => (int) $row->user_id,
				'name'      => $name,
				'image_url' => $image_url,
			);
		}

		return $map;
	}

	public function handleServiceCreated( $data, $item, $request, $controller ) {
		$assigned_employee_ids = $request->get_param( 'assigned_employee_id' );
		$this->syncServiceEmployees( $item, $assigned_employee_ids );
	}

	public function handleServiceUpdated( $item, $request, $controller ) {
		$assigned_employee_ids = $request->get_param( 'assigned_employee_id' );
		$this->maybeApplyPriceDelta( $item, $request );
		$this->syncServiceEmployees( $item, $assigned_employee_ids );
	}

	/**
	 * Apply the service price change to existing employee overrides as a delta.
	 *
	 * Only runs when the admin opted in via `apply_price_to_employees`. Uses the
	 * `original_price` sent from the form to compute the delta — `$item->price`
	 * already holds the new value at this point in the lifecycle.
	 *
	 * @param object          $item    Updated service model.
	 * @param WP_REST_Request $request The incoming request.
	 *
	 * @return void
	 */
	private function maybeApplyPriceDelta( $item, $request ): void {
		if ( ! filter_var( $request->get_param( 'apply_price_to_employees' ), FILTER_VALIDATE_BOOLEAN ) ) {
			return;
		}

		$original = $request->get_param( 'original_price' );
		if ( ! is_numeric( $original ) ) {
			return;
		}

		$delta = (float) $item->price - (float) $original;
		if ( 0.0 === $delta ) {
			return;
		}

		$this->repository->applyPriceDeltaToEmployees( (int) $item->id, $delta );
	}

	/**
	 * Override destroy to clean up Classified Listing meta when deleting a CL-linked service.
	 *
	 * @param WP_REST_Request $request The REST request.
	 *
	 * @return \WP_REST_Response
	 */
	public function destroy( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, array( $this, 'handleDestroy' ) );
	}

	/**
	 * Handler for the single-service delete request.
	 *
	 * Removes the service row and, when the service is linked to a Classified
	 * Listing, also clears the listing's booking meta keys.
	 *
	 * @param WP_REST_Request $request The REST request.
	 *
	 * @return \WP_REST_Response
	 */
	public function handleDestroy( WP_REST_Request $request ) {
		$id = $request->get_param( 'id' );

		if ( ! $this->repository->exists( $id ) ) {
			return ApiResponse::notFound()->send();
		}

		try {
			$service = Service::find( $id );

			// If linked to a Classified Listing, remove booking meta from the listing.
			$this->cleanupClassifiedListingMeta( $service );

			$this->repository->delete( $id );

			do_action( 'radius_booking_item_deleted_' . $this->getResourceType(), $id, $request, $this );

			return ApiResponse::success( array(), 'Resource deleted successfully' )->send();
		} catch ( \Exception $e ) {
			return ApiResponse::error( $e->getMessage(), 500 )->send();
		}
	}

	/**
	 * Bulk delete services by IDs.
	 *
	 * Mirrors the single delete semantics for each ID: removes Classified
	 * Listing booking meta for any CL-linked service and fires the standard
	 * `radius_booking_item_deleted_service` action per row, so listeners
	 * (cache invalidation, integrations, etc.) keep working as expected.
	 *
	 * Expects an `ids` query/body param containing an array of numeric IDs.
	 *
	 * @param WP_REST_Request $request The REST request containing the `ids` parameter.
	 *
	 * @return \WP_REST_Response API response with deleted count or error.
	 */
	public function bulkDelete( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, array( $this, 'handleBulkDelete' ) );
	}

	/**
	 * Handler for the bulk-delete request.
	 *
	 * Validates the `ids` payload, cleans up per-row side effects, performs
	 * a single batched delete via the model, and fires the deletion action
	 * for each removed ID.
	 *
	 * @param WP_REST_Request $request The REST request containing the `ids` parameter.
	 *
	 * @return \WP_REST_Response
	 */
	public function handleBulkDelete( WP_REST_Request $request ) {
		$ids = $request->get_param( 'ids' );

		if ( empty( $ids ) || ! is_array( $ids ) || ! array_filter( $ids, 'is_numeric' ) ) {
			return ApiResponse::notFound( 'Invalid IDs' )->send();
		}

		// Normalize to a unique list of positive integers.
		$ids = array_values(
			array_unique(
				array_filter(
					array_map( 'intval', $ids ),
					static function ( $id ) {
						return $id > 0;
					}
				)
			)
		);

		if ( empty( $ids ) ) {
			return ApiResponse::notFound( 'Invalid IDs' )->send();
		}

		try {
			// Load services first so we can run per-row cleanup before deletion.
			$services = Service::whereIn( 'id', $ids )->get();

			foreach ( $services as $service ) {
				$this->cleanupClassifiedListingMeta( $service );
			}

			$deletedCount = Service::deleteMany( $ids );

			foreach ( $ids as $id ) {
				do_action( 'radius_booking_item_deleted_' . $this->getResourceType(), $id, $request, $this );
			}

			return ApiResponse::success( array( 'deleted_count' => $deletedCount ), 'Resources deleted successfully' )->send();
		} catch ( \Exception $e ) {
			return ApiResponse::error( $e->getMessage(), 500 )->send();
		}
	}

	/**
	 * Remove the booking-related post meta from a linked Classified Listing.
	 *
	 * No-op if the service is not linked to a listing. Centralized so the
	 * single delete and bulk delete paths cannot drift.
	 *
	 * @param mixed $service Service model instance (or null).
	 *
	 * @return void
	 */
	private function cleanupClassifiedListingMeta( $service ): void {
		if ( ! $service || empty( $service->listing_id ) ) {
			return;
		}

		$listing_id = (int) $service->listing_id;
		delete_post_meta( $listing_id, '_rtrb_booking_enabled' );
		delete_post_meta( $listing_id, '_rtrb_booking_duration' );
		delete_post_meta( $listing_id, '_rtrb_booking_category_id' );
	}

	private function syncServiceEmployees( $item, $assigned_employee_ids ) {
//		if(empty($assigned_employee_ids)) return;

		$user_repository = Container::resolve( UserRepository::class );
		// Common service data
		$service_data = array(
			'service_id'     => $item->id,
			'price'          => $item->price,
			'min_capacity'   => $item->min_capacity,
			'max_capacity'   => $item->max_capacity,
			'custom_pricing' => maybe_unserialize( $item->custom_pricing ?? array() ),
		);

		// ---- FETCH EXISTING EMPLOYEES ----
		$existing_employee_ids = $this->repository->getExistingEmployeesIds( $item->id );
		$existing_user_ids     = array_column( $existing_employee_ids, 'user_id' );

		if ( !empty( $existing_user_ids ) ) {
			// ---- DELETE REMOVED EMPLOYEES ----
			$ids_to_delete = array_diff( $existing_user_ids, $assigned_employee_ids );

			foreach ( $ids_to_delete as $user_id ) {
				$this->repository->deleteEmployeeFromService( $item->id, $user_id );
			}
		}

		// ---- CREATE OR UPDATE ASSIGNED EMPLOYEES ----
		if(is_array($assigned_employee_ids) && !empty($assigned_employee_ids)){
			foreach ( $assigned_employee_ids as $employee_id ) {
				$exist = $this->repository->existsServiceAndEmployee( $item->id, $employee_id );
				if ( $exist ) {
					continue;
				}
				$employeeService = $user_repository->findOrCreateEmployeeService(
					$service_data
				);
				$user_repository->updateEmployeeService(
					$employeeService,
					$employee_id,
					$service_data
				);
			}
		}

	}
}
