<?php
/**
 * @package Hafenstudios\Kurato
 */

namespace Hafenstudios\Kurato\Rest;

use Hafenstudios\Kurato\Db\BookingRepository;
use Hafenstudios\Kurato\Db\BookingTypeRepository;
use Hafenstudios\Kurato\Db\ContactRepository;
use Hafenstudios\Kurato\Db\HostRepository;

defined( 'ABSPATH' ) || exit;

/**
 * REST: Termine eines Kontakts (`kurato/v1/contacts/{id}/bookings`), K5c, und
 * die globale Terminliste (`kurato/v1/bookings`, CP4).
 *
 * Speist den Abschnitt "Termine" im Kontaktprofil (contact.js) sowie den Screen
 * "Termine" und die Bruecken-Spalte "Termine heute" der Cockpit-App. Lesen
 * erfordert hs_view_contacts (Muster ContactsController). Die Zeiten kommen UTC
 * aus der DB; die Formatierung in die Site-Zeitzone macht das Frontend.
 * Terminart- und Host-Namen werden angereichert, damit die Timeline sprechend ist.
 */
final class ContactBookingsController extends AbstractController {

	public const REST_NAMESPACE = 'kurato/v1';

	/** Default-Fensterbreite der globalen Liste in Tagen. */
	private const WINDOW_DAYS = 30;

	private ContactRepository $contacts;

	private BookingRepository $bookings;

	private BookingTypeRepository $types;

	private HostRepository $hosts;

	/**
	 * @param ContactRepository     $contacts Kontakt-Repository.
	 * @param BookingRepository     $bookings Buchungs-Repository.
	 * @param BookingTypeRepository $types    Terminarten-Repository.
	 * @param HostRepository        $hosts    Host-Repository.
	 */
	public function __construct( ContactRepository $contacts, BookingRepository $bookings, BookingTypeRepository $types, HostRepository $hosts ) {
		$this->contacts = $contacts;
		$this->bookings = $bookings;
		$this->types    = $types;
		$this->hosts    = $hosts;
	}

	/**
	 * Registriert die Route.
	 *
	 * @return void
	 */
	public function register_routes(): void {
		// CP4: globale Terminliste fuer den Screen "Termine" und die Bruecke.
		// Default-Fenster: die naechsten 30 Tage ab jetzt (upcoming).
		register_rest_route(
			self::REST_NAMESPACE,
			'/bookings',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'list_bookings' ),
				'permission_callback' => array( $this, 'can_view' ),
				'args'                => array(
					'upcoming' => array( 'type' => 'boolean', 'default' => true ),
					'from'     => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field' ),
					'to'       => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field' ),
				),
			)
		);

		register_rest_route(
			self::REST_NAMESPACE,
			'/contacts/(?P<id>\d+)/bookings',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_bookings' ),
				'permission_callback' => array( $this, 'can_view' ),
			)
		);
	}

	/**
	 * GET /contacts/{id}/bookings - Termine des Kontakts, neueste zuerst.
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function get_bookings( \WP_REST_Request $request ) {
		$id = (int) $request->get_param( 'id' );
		if ( null === $this->contacts->get( $id ) ) {
			return new \WP_Error(
				'kurato_contact_not_found',
				__( 'Der Kontakt wurde nicht gefunden. Bitte aktualisieren Sie die Liste.', 'kurato' ),
				array( 'status' => 404 )
			);
		}

		return new \WP_REST_Response( $this->view( $this->bookings->for_contact( $id ) ), 200 );
	}

	/**
	 * GET /bookings - bestaetigte Termine im Zeitfenster ueber alle Kontakte
	 * (CP4). Ohne Angaben: die naechsten 30 Tage ab jetzt. `upcoming=0` oeffnet
	 * das Fenster um 30 Tage nach hinten; `from`/`to` setzen die Raender explizit
	 * ('Y-m-d' oder 'Y-m-d H:i:s', UTC).
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response
	 */
	public function list_bookings( \WP_REST_Request $request ): \WP_REST_Response {
		$upcoming = (bool) $request->get_param( 'upcoming' );
		$now      = time();

		$from = self::bound( (string) $request->get_param( 'from' ), false );
		$to   = self::bound( (string) $request->get_param( 'to' ), true );
		if ( '' === $from ) {
			$from = gmdate( 'Y-m-d H:i:s', $upcoming ? $now : $now - self::WINDOW_DAYS * DAY_IN_SECONDS );
		}
		if ( '' === $to ) {
			$to = gmdate( 'Y-m-d H:i:s', $now + self::WINDOW_DAYS * DAY_IN_SECONDS );
		}

		return new \WP_REST_Response( $this->view( $this->bookings->in_range( $from, $to ) ), 200 );
	}

	/**
	 * Normalisiert eine Fenstergrenze auf 'Y-m-d H:i:s' (UTC). Ein reines Datum
	 * wird zum Tagesanfang bzw. Tagesende ergaenzt; alles andere ergibt '' (der
	 * Aufrufer setzt dann den Default).
	 *
	 * @param string $value Rohwert aus dem Request.
	 * @param bool   $end   Obergrenze (Tagesende statt Tagesanfang).
	 * @return string
	 */
	public static function bound( string $value, bool $end ): string {
		$value = trim( $value );
		if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $value ) ) {
			return $value . ( $end ? ' 23:59:59' : ' 00:00:00' );
		}
		if ( preg_match( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $value ) ) {
			return $value;
		}
		return '';
	}

	/**
	 * Reichert Buchungszeilen um Terminart- und Host-Label an (je einmal geladen)
	 * und schneidet sie auf die Felder zu, die die Oberflaeche braucht.
	 *
	 * @param array<int, array<string, mixed>> $bookings Buchungszeilen.
	 * @return array<int, array<string, mixed>>
	 */
	private function view( array $bookings ): array {
		$type_cache = array();
		$host_cache = array();
		$out        = array();
		foreach ( $bookings as $booking ) {
			$type_id = (int) $booking['type_id'];
			if ( ! array_key_exists( $type_id, $type_cache ) ) {
				$type                   = $this->types->get( $type_id );
				$type_cache[ $type_id ] = null !== $type ? (string) $type['label'] : __( '(entfernte Terminart)', 'kurato' );
			}
			$host_id = (int) $booking['host_id'];
			if ( ! array_key_exists( $host_id, $host_cache ) ) {
				$host                   = $this->hosts->get( $host_id );
				$host_cache[ $host_id ] = null !== $host ? (string) $host['label'] : '';
			}

			$row = array(
				'id'         => (int) $booking['id'],
				'type_label' => $type_cache[ $type_id ],
				'host_label' => $host_cache[ $host_id ],
				'start_utc'  => (string) $booking['start_utc'],
				'end_utc'    => (string) $booking['end_utc'],
				'status'     => (string) $booking['status'],
			);
			// Nur die globale Liste traegt den Kontaktbezug (Join in in_range).
			if ( array_key_exists( 'contact_name', $booking ) ) {
				$row['contact_id']   = null !== $booking['contact_id'] ? (int) $booking['contact_id'] : 0;
				$row['contact_name'] = (string) $booking['contact_name'];
			}
			$out[] = $row;
		}
		return $out;
	}
}
