<?php

namespace SugarCalendarBookings\Appointments\Admin;

use DateTime;
use SugarCalendarBookings\Appointments\AppointmentModel;
use SugarCalendarBookings\Customers\CustomerModel;
use SugarCalendarBookings\Locations\OnlineLocation;
use SugarCalendarBookings\Options;
use SugarCalendarBookings\Plugin;
use SugarCalendarBookings\Services\ServiceModel;
use SugarCalendarBookings\Support\Helpers\Date;
use SugarCalendarBookings\Support\Helpers\Time;

/**
 * Service for adding new and editing appointment form.
 *
 * @since 1.0.0
 */
class AppointmentFormService {

	/**
	 * Get sanitization and validation rules.
	 *
	 * @since 1.0.0
	 *
	 * @param array $additional_rules Additional rules.
	 *
	 * @return array
	 */
	public function rules( $additional_rules = [] ) {

		return array_merge(
			[
				'service_id'          => 'required|id',
				'location'            => 'required|array',
				'location.type'       => 'required|string|in:in_person,online',
				'location.id'         => 'required_if:type,in_person|id',
				'location.uid'        => 'required_if:type,online|string',
				'employee_id'         => 'required|id',
				'customer_id'         => 'required|id',
				'status'              => 'required|string|in:approved,pending,cancelled,done',
				'start_at'            => 'required|string|date_format:' . DateTime::ATOM,
				'end_at'              => 'required|string|date_format:' . DateTime::ATOM,
				'ignore_availability' => 'boolean',
				'customer_timezone'   => 'required|string|timezone',
				'internal_notes'      => 'string',
			],
			$additional_rules
		);
	}

	/**
	 * Get form meta data.
	 *
	 * @since 1.0.0
	 *
	 * @return array
	 */
	private function get_form_meta_data() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.NestingLevel.MaxExceeded

		$options = Plugin::get( Options::class );

		// TODO: Add limit and implement load more functionality in the select.
		$services = ServiceModel::query()
			->with(
				[
					'locations' => fn( $q ) => $q->select( [ 'id', 'name' ] ),
					'employees' => fn( $q ) => $q->select( [ 'id', 'name' ] )->where( 'status', '=', 'enabled', '%s' )->order_by( 'name' ),
					'meta'      => fn( $q ) => $q->where( 'meta_key', '=', 'online_locations', '%s' ),
				]
			)
			->select( [ 'id', 'name', 'color', 'duration' ] )
			->where( 'status', '=', 'enabled', '%s' )
			->order_by( 'name' )
			->get();

		$locations_per_service = [];

		foreach ( $services as $service ) {
			$locations_per_service[ $service->id ] = [];

			if ( ! empty( $service->locations ) ) {
				foreach ( $service->locations as $location ) {
					$locations_per_service[ $service->id ][] = [
						'type'  => 'in_person',
						'id'    => $location->id,
						'name'  => $location->name,
						'order' => (int) $location->pivot->location_order,
					];
				}
			}

			if ( ! empty( $service->online_locations ) ) {
				foreach ( $service->online_locations as $location ) {
					$online_location = OnlineLocation::from_array( $location );

					if ( $online_location ) {
						$locations_per_service[ $service->id ][] = [
							'type'  => 'online',
							'uid'   => $online_location->get_uid(),
							'name'  => $online_location->get_display_name(),
							'order' => $online_location->get_order(),
						];
					}
				}
			}

			// Sort locations by order.
			usort(
				$locations_per_service[ $service->id ],
				fn( $a, $b ) => $a['order'] - $b['order']
			);
		}

		$employees_per_service = [];

		foreach ( $services as $service ) {
			$employees_per_service[ $service->id ] = [];

			if ( ! empty( $service->employees ) ) {
				foreach ( $service->employees as $employee ) {
					$employees_per_service[ $service->id ][] = [
						'id'   => $employee->id,
						'name' => $employee->name,
					];
				}
			}
		}

		// TODO: Add limit and implement load more functionality in the select.
		$customers = CustomerModel::query()
			->select( [ 'id', 'name' ] )
			->order_by( 'name' )
			->get();

		$data = [
			'services'              => array_map(
				fn( $service ) => [
					'id'       => $service->id,
					'name'     => $service->name,
					'color'    => $service->color,
					'duration' => $service->duration,
				],
				$services
			),
			'customers'             => array_map(
				fn( $customer ) => [
					'id'   => $customer->id,
					'name' => $customer->name,
				],
				$customers
			),
			'locations_per_service' => $locations_per_service,
			'employees_per_service' => $employees_per_service,
			'display_timezone'      => Date::get_default_timezone_string(),
			'display_start_of_week' => $options->get( 'display.start_of_week' ),
			'display_date_format'   => $options->get( 'display.date_format' ),
			'display_time_format'   => $options->get( 'display.time_format' ),
			'default_status'        => $options->get( 'appointments.default_status' ),
		];

		return $data;
	}

	/**
	 * Get data for appointment creation form.
	 *
	 * @since 1.0.0
	 *
	 * @return array
	 */
	public function get_create_form_data() {

		return [
			'meta' => $this->get_form_meta_data(),
		];
	}

	/**
	 * Get data for appointment edit form.
	 *
	 * @since 1.0.0
	 *
	 * @param AppointmentModel $appointment Appointment model instance.
	 *
	 * @return array
	 */
	public function get_edit_form_data( $appointment ) {

		$location = null;

		if ( ! empty( $appointment->location_id ) ) {
			// Get the location model to include the name.
			$location_model = $appointment->location;
			$location       = [
				'type' => 'in_person',
				'id'   => $appointment->location_id,
				'name' => $location_model ? $location_model->name : '',
			];
		} elseif (
			! empty( $appointment->online_location ) &&
			isset( $appointment->online_location['uid'] )
		) {
			$online_location = OnlineLocation::from_array( $appointment->online_location );

			$location = [
				'type' => 'online',
				'uid'  => $appointment->online_location['uid'],
				'name' => $online_location ? $online_location->get_display_name() : '',
			];
		}

		$duration        = Time::convert_duration( $appointment->get_duration() );
		$ignore_duration = false;

		if ( $appointment->get_duration() !== $appointment->service->duration ) {
			$ignore_duration = true;
		}

		$data = [
			'id'                  => $appointment->id,
			'service_id'          => $appointment->service_id,
			'location'            => $location,
			'employee_id'         => $appointment->employee_id,
			'customer_id'         => $appointment->customer_id,
			'status'              => $appointment->status,
			'start_at'            => $appointment->start_at->format( DateTime::ATOM ),
			'end_at'              => $appointment->end_at->format( DateTime::ATOM ),
			'ignore_availability' => (bool) $appointment->ignore_availability,
			'ignore_duration'     => $ignore_duration,
			'duration'            => $duration['value'],
			'duration_unit'       => $duration['unit'],
			'internal_notes'      => $appointment->internal_notes,
		];

		return [
			'appointment' => $data,
			'meta'        => $this->get_form_meta_data(),
		];
	}
}
