<?php

namespace SugarCalendarBookings\Integrations\Outlook;

use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use SugarCalendarBookings\Appointments\AppointmentModel;
use SugarCalendarBookings\Customers\CustomerModel;
use SugarCalendarBookings\Employees\EmployeeModel;
use SugarCalendarBookings\Integrations\OAuthRelay\OAuthConnectionModel;
use SugarCalendarBookings\Options;
use SugarCalendarBookings\Services\ServiceModel;
use SugarCalendarBookings\Support\Helpers\Date;

/**
 * Outlook event payload builder.
 *
 * Constructs the Microsoft Graph event bodies used by create_event / update_event.
 * Pure data construction — no HTTP, no DB.
 *
 * @since 1.5.0
 */
class OutlookEventPayloadBuilder {

	/**
	 * Maximum subject length supported by Graph (safety cap).
	 *
	 * @since 1.5.0
	 *
	 * @var int
	 */
	private const MAX_SUBJECT_LENGTH = 255;

	/**
	 * Title/agenda template provider.
	 *
	 * @since 1.5.0
	 *
	 * @var OutlookSmartTagPicker
	 */
	private OutlookSmartTagPicker $templates;

	/**
	 * Plugin options accessor.
	 *
	 * @since 1.5.0
	 *
	 * @var Options
	 */
	private Options $options;

	/**
	 * Constructor.
	 *
	 * @since 1.5.0
	 *
	 * @param OutlookSmartTagPicker $templates Template provider.
	 * @param Options               $options   Plugin options.
	 */
	public function __construct( OutlookSmartTagPicker $templates, Options $options ) {

		$this->templates = $templates;
		$this->options   = $options;
	}

	/**
	 * Build the Graph event body for POST /me/calendars/{id}/events.
	 *
	 * @since 1.5.0
	 *
	 * @param AppointmentModel     $appointment Appointment to mirror.
	 * @param OAuthConnectionModel $connection  Target OAuth connection.
	 *
	 * @return array Graph event body.
	 */
	public function build_create_payload( AppointmentModel $appointment, OAuthConnectionModel $connection ): array {

		$service  = ServiceModel::get( $appointment->service_id );
		$customer = CustomerModel::get( $appointment->customer_id );
		$employee = EmployeeModel::get( $appointment->employee_id );

		$subject  = $this->build_subject( $appointment, $service, $customer, $employee );
		$body     = $this->build_body_html( $appointment, $service, $customer, $employee );
		$location = $this->build_location_display( $appointment );
		$show_as  = $appointment->status === AppointmentModel::STATUS_APPROVED ? 'busy' : 'tentative';

		$payload = [
			'subject'         => $subject,
			'body'            => [
				'contentType' => 'HTML',
				'content'     => $body,
			],
			'start'           => $this->format_graph_datetime( $appointment->start_at ),
			'end'             => $this->format_graph_datetime( $appointment->end_at ),
			'location'        => [
				'displayName' => $location,
			],
			'attendees'       => $this->build_attendees( $employee, $customer ),
			'showAs'          => $show_as,
			'isOnlineMeeting' => false,
			'transactionId'   => $this->build_transaction_id( $appointment, $connection ),
		];

		/**
		 * Filter the Outlook Graph event body before create.
		 *
		 * Fires only for create requests. Update payloads have a different
		 * field set (no attendees, isOnlineMeeting, or transactionId) and
		 * are filtered via the sibling `..._update_payload` filter.
		 *
		 * @since 1.5.0
		 *
		 * @param array                $payload     Graph event body for POST.
		 * @param AppointmentModel     $appointment Appointment being mirrored.
		 * @param OAuthConnectionModel $connection  Target OAuth connection.
		 */
		return (array) apply_filters( 'sugar_calendar_bookings_integrations_outlook_create_payload', $payload, $appointment, $connection );
	}

	/**
	 * Build the PATCH body for PATCH /me/events/{id}.
	 *
	 * Only includes fields that may have changed.
	 *
	 * @since 1.5.0
	 *
	 * @param AppointmentModel     $appointment Updated appointment.
	 * @param OAuthConnectionModel $connection  Target OAuth connection.
	 *
	 * @return array PATCH body.
	 */
	public function build_update_payload( AppointmentModel $appointment, OAuthConnectionModel $connection ): array {

		$service  = ServiceModel::get( $appointment->service_id );
		$customer = CustomerModel::get( $appointment->customer_id );
		$employee = EmployeeModel::get( $appointment->employee_id );

		$payload = [
			'subject'  => $this->build_subject( $appointment, $service, $customer, $employee ),
			'body'     => [
				'contentType' => 'HTML',
				'content'     => $this->build_body_html( $appointment, $service, $customer, $employee ),
			],
			'start'    => $this->format_graph_datetime( $appointment->start_at ),
			'end'      => $this->format_graph_datetime( $appointment->end_at ),
			'location' => [
				'displayName' => $this->build_location_display( $appointment ),
			],
			'showAs'   => $appointment->status === AppointmentModel::STATUS_APPROVED ? 'busy' : 'tentative',
		];

		/**
		 * Filter the Outlook Graph event body before update.
		 *
		 * Fires only for update (PATCH) requests. Create payloads carry an
		 * extra field set and are filtered via the sibling
		 * `..._create_payload` filter.
		 *
		 * @since 1.5.0
		 *
		 * @param array                $payload     Graph event body for PATCH.
		 * @param AppointmentModel     $appointment Appointment being mirrored.
		 * @param OAuthConnectionModel $connection  Target OAuth connection.
		 */
		return (array) apply_filters( 'sugar_calendar_bookings_integrations_outlook_update_payload', $payload, $appointment, $connection );
	}

	/**
	 * Format a DateTime-like value as a Graph `{ dateTime, timeZone }` pair in UTC.
	 *
	 * @since 1.5.0
	 *
	 * @param mixed $datetime Source datetime (DateTime, DateTimeImmutable, or string).
	 *
	 * @return array
	 */
	private function format_graph_datetime( $datetime ): array {

		$utc = new DateTimeZone( 'UTC' );

		if ( $datetime instanceof DateTimeInterface ) {
			$dt = $datetime instanceof DateTimeImmutable
				? $datetime
				: DateTimeImmutable::createFromMutable( $datetime );
			$dt = $dt->setTimezone( $utc );
		} else {
			$dt = Date::create( (string) $datetime );

			if ( ! $dt ) {
				$dt = new DateTimeImmutable( 'now', $utc );
			} else {
				$dt = $dt->setTimezone( $utc );
			}
		}

		return [
			'dateTime' => $dt->format( 'Y-m-d\TH:i:s' ),
			'timeZone' => 'UTC',
		];
	}

	/**
	 * Build attendees list from employee + customer emails.
	 *
	 * @since 1.5.0
	 *
	 * @param EmployeeModel|null $employee Employee model.
	 * @param CustomerModel|null $customer Customer model.
	 *
	 * @return array
	 */
	private function build_attendees( ?EmployeeModel $employee, ?CustomerModel $customer ): array {

		$attendees = [];

		if ( $employee && ! empty( $employee->email ) ) {
			$attendees[] = [
				'emailAddress' => [
					'address' => $employee->email,
					'name'    => $employee->name,
				],
				'type'         => 'required',
			];
		}

		if ( $customer && ! empty( $customer->email ) ) {
			$attendees[] = [
				'emailAddress' => [
					'address' => $customer->email,
					'name'    => $customer->name,
				],
				'type'         => 'required',
			];
		}

		return $attendees;
	}

	/**
	 * Build the deterministic transactionId for a (appointment, connection) pair.
	 *
	 * Graph deduplicates events with the same transactionId within ~30 days,
	 * which gives us a free idempotency guarantee on retry.
	 *
	 * @since 1.5.0
	 *
	 * @param AppointmentModel     $appointment Appointment.
	 * @param OAuthConnectionModel $connection  Target connection.
	 *
	 * @return string
	 */
	private function build_transaction_id( AppointmentModel $appointment, OAuthConnectionModel $connection ): string {

		return 'scb-' . (int) $appointment->id . '-' . (int) $connection->id;
	}

	/**
	 * Build the location displayName for the Graph event.
	 *
	 * @since 1.5.0
	 *
	 * @param AppointmentModel $appointment Appointment.
	 *
	 * @return string
	 */
	private function build_location_display( AppointmentModel $appointment ): string {

		$online_location = is_array( $appointment->online_location ) ? $appointment->online_location : [];
		$provider        = $online_location['provider'] ?? '';

		if ( $provider === 'zoom' ) {
			if ( ! empty( $online_location['join_url'] ) ) {
				return (string) $online_location['join_url'];
			}

			return esc_html__( 'Zoom Meeting', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		}

		if ( $provider === 'custom_link' && ! empty( $online_location['url'] ) ) {
			return (string) $online_location['url'];
		}

		$location_model = $appointment->location ?? null;

		if ( $location_model && ! empty( $location_model->name ) ) {
			return $location_model->name;
		}

		return '';
	}

	/**
	 * Resolve the event subject from the configured template.
	 *
	 * @since 1.5.0
	 *
	 * @param AppointmentModel   $appointment Appointment.
	 * @param ServiceModel|null  $service     Service.
	 * @param CustomerModel|null $customer    Customer.
	 * @param EmployeeModel|null $employee    Employee.
	 *
	 * @return string Subject, trimmed and length-capped.
	 */
	private function build_subject( AppointmentModel $appointment, ?ServiceModel $service, ?CustomerModel $customer, ?EmployeeModel $employee ): string {

		$template     = $this->templates->get_title_template();
		$replacements = $this->build_smart_tag_replacements( $appointment, $service, $customer, $employee );

		$subject = str_replace( array_keys( $replacements ), array_values( $replacements ), $template );
		$subject = trim( preg_replace( '/\s+/', ' ', $subject ) );

		if ( $subject === '' ) {
			$subject = sprintf(
				/* translators: %1$d - appointment ID. */
				esc_html__( 'Appointment #%1$d', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
				$appointment->id
			);
		}

		return mb_substr( $subject, 0, self::MAX_SUBJECT_LENGTH );
	}

	/**
	 * Resolve the event body HTML from the configured template.
	 *
	 * @since 1.5.0
	 *
	 * @param AppointmentModel   $appointment Appointment.
	 * @param ServiceModel|null  $service     Service.
	 * @param CustomerModel|null $customer    Customer.
	 * @param EmployeeModel|null $employee    Employee.
	 *
	 * @return string Body HTML.
	 */
	private function build_body_html( AppointmentModel $appointment, ?ServiceModel $service, ?CustomerModel $customer, ?EmployeeModel $employee ): string {

		$template     = $this->templates->get_agenda_template();
		$replacements = $this->build_smart_tag_replacements( $appointment, $service, $customer, $employee );

		$body = str_replace( array_keys( $replacements ), array_values( $replacements ), $template );

		return trim( $body );
	}

	/**
	 * Build the smart-tag replacement map for title + body templates.
	 *
	 * @since 1.5.0
	 *
	 * @param AppointmentModel   $appointment Appointment.
	 * @param ServiceModel|null  $service     Service.
	 * @param CustomerModel|null $customer    Customer.
	 * @param EmployeeModel|null $employee    Employee.
	 *
	 * @return array<string, string>
	 */
	private function build_smart_tag_replacements( AppointmentModel $appointment, ?ServiceModel $service, ?CustomerModel $customer, ?EmployeeModel $employee ): array {

		$time_format = $this->options->get( 'display.time_format', get_option( 'time_format' ) );
		$date_format = get_option( 'date_format', 'M j, Y' );

		$online_location_url = '';
		$online_location     = is_array( $appointment->online_location ) ? $appointment->online_location : [];

		if ( ! empty( $online_location['join_url'] ) ) {
			$online_location_url = (string) $online_location['join_url'];
		} elseif ( ! empty( $online_location['url'] ) ) {
			$online_location_url = (string) $online_location['url'];
		}

		return [
			'{service_name}'                    => $service ? $service->name : '',
			'{service_duration}'                => $service
				? $service->duration . ' ' . esc_html__( 'min', 'sugar-calendar-bookings-scheduling-appointments-lite' )
				: '',
			'{customer_full_name}'              => $customer ? $customer->name : '',
			'{customer_name}'                   => $customer ? $customer->name : '',
			'{customer_email}'                  => $customer ? $customer->email : '',
			'{employee_full_name}'              => $employee ? $employee->name : '',
			'{start_date}'                      => $appointment->start_at
				? wp_date( $date_format, $appointment->start_at->getTimestamp() )
				: '',
			'{start_time}'                      => $appointment->start_at
				? wp_date( $time_format, $appointment->start_at->getTimestamp() )
				: '',
			'{end_time}'                        => $appointment->end_at
				? wp_date( $time_format, $appointment->end_at->getTimestamp() )
				: '',
			'{appointment_code}'                => $appointment->code ?? '',
			'{appointment_online_location_url}' => $online_location_url,
			'{site_name}'                       => get_bloginfo( 'name' ),
		];
	}
}
