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

namespace Hafenstudios\Kurato\Admin;

use Hafenstudios\Core\Core;
use Hafenstudios\Kurato\Booking\BookingWeek;
use Hafenstudios\Kurato\Db\BookingRepository;
use Hafenstudios\Kurato\Db\BookingTypeRepository;
use Hafenstudios\Kurato\Db\ContactRepository;
use Hafenstudios\Kurato\Db\HostRepository;

defined( 'ABSPATH' ) || exit;

/**
 * Buchungsliste (Etappe K5b, K5-KONZEPT Abschnitt 7).
 *
 * Serverseitige Wochenansicht der Buchungen (Muster ListsPage/CampaignsPage): vor-
 * und zurueckblaettern je Kalenderwoche (Montag-Start, Site-Zeitzone), Filter nach
 * Ansprechpartner, Status-Badge und Betreiber-Storno. Die Zeitrechnung (Wochen-
 * fenster, ISO-Kalenderwoche) liegt im reinen Helfer BookingWeek; die Anzeige
 * rechnet die UTC-Zeiten der Datenbank ueber wp_date in die Site-Zeitzone.
 *
 * Der Betreiber-Storno ruft BookingRepository::cancel und feuert danach die Action
 * `kurato_booking_cancelled` mit (booking_id, 'admin'); die Storno-Mail an den
 * Besucher haengt K5c an diese Action (hier NICHT gebaut).
 *
 * Rechte: Ansehen und Stornieren ueber `hs_manage_contacts`. Der Storno ist per
 * Nonce und Capability abgesichert und endet in `wp_safe_redirect`.
 */
final class BookingsPage {

	public const SLUG          = 'kurato-bookings';
	public const ACTION_CANCEL = 'kurato_cancel_booking';

	private const CAP = 'hs_manage_contacts';

	private BookingRepository $bookings;

	private HostRepository $hosts;

	private BookingTypeRepository $types;

	private ContactRepository $contacts;

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

	/**
	 * Registriert die admin-post-Handler.
	 *
	 * @return void
	 */
	public function register(): void {
		add_action( 'admin_post_' . self::ACTION_CANCEL, array( $this, 'handle_cancel' ) );
	}

	/**
	 * Rendert die Buchungsliste.
	 *
	 * @return void
	 */
	public function render(): void {
		$this->guard();

		$offset      = $this->requested_offset();
		$host_filter = $this->requested_host();
		$week        = BookingWeek::range( new \DateTimeImmutable( 'now', wp_timezone() ), $offset );
		$from_utc    = BookingWeek::to_utc_string( $week['start'] );
		$to_utc      = BookingWeek::to_utc_string( $week['end'] );

		echo '<div class="wrap hs-kurato-bookings">';
		echo '<h1 class="wp-heading-inline">' . esc_html__( 'Termine', 'kurato' ) . '</h1>';
		echo '<hr class="wp-header-end" />';

		$this->render_notice();
		$this->render_milestone();

		$hosts = $this->hosts->all();
		if ( array() === $hosts ) {
			$this->render_empty_hosts();
			echo '</div>';
			return;
		}

		$this->render_toolbar( $offset, $host_filter, $week, $hosts );

		$rows = $this->collect_bookings( $host_filter, $hosts, $from_utc, $to_utc );
		if ( array() === $rows ) {
			$this->render_empty_week();
			echo '</div>';
			return;
		}

		$this->render_table( $rows, $offset, $host_filter );
		echo '</div>';
	}

	/**
	 * Kein Host vorhanden: Verweis auf die Verwaltung.
	 *
	 * @return void
	 */
	private function render_empty_hosts(): void {
		$image = Core::instance()->hans()->image_url( 30 );
		echo '<div class="hs-empty-state" style="text-align:center;padding:48px 0;">';
		if ( '' !== $image ) {
			echo '<img src="' . esc_url( $image ) . '" alt="" width="160" height="160" style="height:auto;" />';
		}
		echo '<p style="font-size:15px;margin:16px 0;">' . esc_html__( 'Noch keine Buchungsseite. Legen Sie zuerst einen Ansprechpartner mit Terminart an.', 'kurato' ) . '</p>';
		echo '<a class="button button-primary" href="' . esc_url( admin_url( 'admin.php?page=' . HostsPage::SLUG ) ) . '">' . esc_html__( 'Zu den Buchungsseiten', 'kurato' ) . '</a>';
		echo '</div>';
	}

	/**
	 * Hans-Meilenstein bei der ersten eingegangenen Buchung. Wird genau einmal
	 * gezeigt (persistiert per Option): das Event booking_created setzt
	 * `kurato_first_booking_at`; ist die Notice noch nicht quittiert, erscheint sie
	 * hier und wird danach als gesehen markiert.
	 *
	 * @return void
	 */
	private function render_milestone(): void {
		if ( false === get_option( 'kurato_first_booking_at', false ) ) {
			return;
		}
		if ( get_option( 'kurato_first_booking_seen', false ) ) {
			return;
		}
		update_option( 'kurato_first_booking_seen', 1 );

		$image = Core::instance()->hans()->image_url( 27 );
		echo '<div class="notice notice-success hs-hans-milestone"><div style="display:flex;gap:14px;align-items:center;padding:6px 0;">';
		if ( '' !== $image ) {
			echo '<img src="' . esc_url( $image ) . '" alt="" width="64" height="64" style="height:auto;flex:0 0 auto;" />';
		}
		echo '<div><p style="margin:0 0 4px;font-size:15px;"><strong>' . esc_html__( 'Ihre erste Terminbuchung ist da.', 'kurato' ) . '</strong></p>';
		echo '<p style="margin:0;">' . esc_html__( 'Glueckwunsch. Ab jetzt landen neue Buchungen automatisch hier und im Kontaktprofil.', 'kurato' ) . '</p></div>';
		echo '</div></div>';
	}

	/**
	 * Leere Woche.
	 *
	 * @return void
	 */
	private function render_empty_week(): void {
		$image = Core::instance()->hans()->image_url( 30 );
		echo '<div class="hs-empty-state" style="text-align:center;padding:40px 0;">';
		if ( '' !== $image ) {
			echo '<img src="' . esc_url( $image ) . '" alt="" width="140" height="140" style="height:auto;" />';
		}
		echo '<p style="font-size:15px;margin:16px 0;">' . esc_html__( 'In dieser Woche gibt es keine Termine.', 'kurato' ) . '</p>';
		echo '</div>';
	}

	/**
	 * Wochen-Navigation und Host-Filter.
	 *
	 * @param int                                                                             $offset      Aktueller Wochen-Offset.
	 * @param int                                                                             $host_filter Aktiver Host-Filter (0 = alle).
	 * @param array{start:\DateTimeImmutable,end:\DateTimeImmutable,iso_year:int,iso_week:int} $week        Wochenfenster.
	 * @param array<int, array<string, mixed>>                                                $hosts       Alle Hosts.
	 * @return void
	 */
	private function render_toolbar( int $offset, int $host_filter, array $week, array $hosts ): void {
		$prev = $this->list_url( $offset - 1, $host_filter );
		$next = $this->list_url( $offset + 1, $host_filter );
		$this_week = $this->list_url( 0, $host_filter );

		// Enddatum ist exklusiv (naechster Montag); fuer die Anzeige der Sonntag davor.
		$end_display = $week['end']->modify( '-1 day' );
		$range_label = sprintf(
			/* translators: 1: Startdatum, 2: Enddatum */
			__( '%1$s bis %2$s', 'kurato' ),
			$this->format_day( $week['start'] ),
			$this->format_day( $end_display )
		);

		echo '<div class="hs-kurato-filters">';
		echo '<a class="button" href="' . esc_url( $prev ) . '">&laquo; ' . esc_html__( 'Vorige Woche', 'kurato' ) . '</a>';
		echo '<strong>' . esc_html( sprintf( /* translators: 1: KW-Nummer, 2: Jahr */ __( 'KW %1$d / %2$d', 'kurato' ), (int) $week['iso_week'], (int) $week['iso_year'] ) ) . '</strong>';
		echo '<span class="hs-muted">' . esc_html( $range_label ) . '</span>';
		echo '<a class="button" href="' . esc_url( $next ) . '">' . esc_html__( 'Naechste Woche', 'kurato' ) . ' &raquo;</a>';
		if ( 0 !== $offset ) {
			echo '<a href="' . esc_url( $this_week ) . '">' . esc_html__( 'Zur aktuellen Woche', 'kurato' ) . '</a>';
		}
		echo '</div>';

		// Host-Filter (GET-Formular).
		echo '<form method="get" class="hs-kurato-filters">';
		echo '<input type="hidden" name="page" value="' . esc_attr( self::SLUG ) . '" />';
		echo '<input type="hidden" name="week_offset" value="' . esc_attr( (string) $offset ) . '" />';
		echo '<label>' . esc_html__( 'Ansprechpartner', 'kurato' ) . ' <select name="host" onchange="this.form.submit();">';
		echo '<option value="0"' . selected( $host_filter, 0, false ) . '>' . esc_html__( 'Alle', 'kurato' ) . '</option>';
		foreach ( $hosts as $host ) {
			$hid = (int) $host['id'];
			echo '<option value="' . esc_attr( (string) $hid ) . '"' . selected( $host_filter, $hid, false ) . '>' . esc_html( (string) $host['label'] ) . '</option>';
		}
		echo '</select></label>';
		echo '<noscript><button type="submit" class="button">' . esc_html__( 'Filtern', 'kurato' ) . '</button></noscript>';
		echo '</form>';
	}

	/**
	 * Die Buchungstabelle.
	 *
	 * @param array<int, array<string, mixed>> $rows        Buchungen (mit Anzeige-Feldern), nach Start sortiert.
	 * @param int                              $offset      Wochen-Offset (fuer Redirect nach Storno).
	 * @param int                              $host_filter Host-Filter (fuer Redirect nach Storno).
	 * @return void
	 */
	private function render_table( array $rows, int $offset, int $host_filter ): void {
		echo '<table class="wp-list-table widefat fixed striped">';
		echo '<thead><tr>';
		echo '<th scope="col">' . esc_html__( 'Zeit', 'kurato' ) . '</th>';
		echo '<th scope="col">' . esc_html__( 'Terminart', 'kurato' ) . '</th>';
		echo '<th scope="col">' . esc_html__( 'Ansprechpartner', 'kurato' ) . '</th>';
		echo '<th scope="col">' . esc_html__( 'Kontakt', 'kurato' ) . '</th>';
		echo '<th scope="col">' . esc_html__( 'Status', 'kurato' ) . '</th>';
		echo '<th scope="col">' . esc_html__( 'Aktion', 'kurato' ) . '</th>';
		echo '</tr></thead><tbody>';

		foreach ( $rows as $row ) {
			echo '<tr>';
			echo '<td>' . esc_html( $this->format_datetime( (string) $row['start_utc'] ) ) . '</td>';
			echo '<td>' . esc_html( (string) $row['type_label'] ) . '</td>';
			echo '<td>' . esc_html( (string) $row['host_label'] ) . '</td>';
			echo '<td>' . $this->contact_cell( $row ) . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- intern escaped.
			echo '<td>' . $this->status_badge( (string) $row['status'] ) . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Badge intern escaped.
			echo '<td>';
			if ( 'confirmed' === (string) $row['status'] ) {
				echo $this->cancel_button( (int) $row['id'], $offset, $host_filter ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- intern escaped.
			} else {
				echo '<span class="hs-muted">&mdash;</span>';
			}
			echo '</td>';
			echo '</tr>';
		}

		echo '</tbody></table>';
	}

	/**
	 * Kontakt-Zelle: Name/E-Mail, verlinkt ins Kontaktprofil, wenn contact_id gesetzt.
	 *
	 * @param array<string, mixed> $row Buchung mit Anzeige-Feldern.
	 * @return string
	 */
	private function contact_cell( array $row ): string {
		$name  = (string) $row['contact_name'];
		$email = (string) $row['contact_email'];
		$label = '' !== $name ? $name : ( '' !== $email ? $email : __( '(anonymisiert)', 'kurato' ) );

		$html = '<strong>' . esc_html( $label ) . '</strong>';
		if ( '' !== $email && $email !== $label ) {
			$html .= '<br /><span class="hs-muted">' . esc_html( $email ) . '</span>';
		}

		$contact_id = ! empty( $row['contact_id'] ) ? (int) $row['contact_id'] : 0;
		if ( $contact_id > 0 ) {
			$html = '<a href="' . esc_url( ContactPage::url( $contact_id ) ) . '">' . $html . '</a>';
		}
		return $html;
	}

	/**
	 * Sammelt die Buchungen der Woche (ein Host oder alle) mit Anzeige-Feldern,
	 * nach Startzeit sortiert.
	 *
	 * @param int                              $host_filter Host-Filter (0 = alle).
	 * @param array<int, array<string, mixed>> $hosts       Alle Hosts.
	 * @param string                           $from_utc    Fensteranfang (UTC).
	 * @param string                           $to_utc      Fensterende (UTC).
	 * @return array<int, array<string, mixed>>
	 */
	private function collect_bookings( int $host_filter, array $hosts, string $from_utc, string $to_utc ): array {
		$host_labels = array();
		foreach ( $hosts as $host ) {
			$host_labels[ (int) $host['id'] ] = (string) $host['label'];
		}

		$target_ids = $host_filter > 0 ? array( $host_filter ) : array_keys( $host_labels );

		$rows        = array();
		$type_cache  = array();
		foreach ( $target_ids as $hid ) {
			$hid = (int) $hid;
			foreach ( $this->bookings->bookings_for_host_between( $hid, $from_utc, $to_utc ) 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' );
				}

				$contact_name  = '';
				$contact_email = '';
				if ( ! empty( $booking['contact_id'] ) ) {
					$contact = $this->contacts->get( (int) $booking['contact_id'] );
					if ( null !== $contact ) {
						$contact_name  = trim( (string) ( $contact['display_name'] ?? '' ) );
						$contact_email = (string) ( $contact['email'] ?? '' );
					}
				}

				$booking['type_label']    = $type_cache[ $type_id ];
				$booking['host_label']    = $host_labels[ $hid ] ?? '';
				$booking['contact_name']  = $contact_name;
				$booking['contact_email'] = $contact_email;
				$rows[]                   = $booking;
			}
		}

		usort(
			$rows,
			static function ( $a, $b ) {
				return strcmp( (string) $a['start_utc'], (string) $b['start_utc'] );
			}
		);
		return $rows;
	}

	/**
	 * Betreiber-Storno.
	 *
	 * @return void
	 */
	public function handle_cancel(): void {
		$this->guard();
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- ID nur zur Nonce-Aktion; Nonce direkt danach geprueft.
		$id = isset( $_POST['booking'] ) ? absint( wp_unslash( $_POST['booking'] ) ) : 0;
		check_admin_referer( self::ACTION_CANCEL . '_' . $id );

		// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce oben geprueft.
		$offset      = isset( $_POST['week_offset'] ) ? (int) wp_unslash( $_POST['week_offset'] ) : 0;
		$host_filter = isset( $_POST['host'] ) ? absint( wp_unslash( $_POST['host'] ) ) : 0;
		// phpcs:enable

		$booking = $this->bookings->get( $id );
		if ( null !== $booking && 'confirmed' === (string) $booking['status'] && $this->bookings->cancel( $id ) ) {
			/**
			 * Eine Buchung wurde storniert. K5c haengt hier die Storno-Mail an den
			 * Besucher (und ICS-CANCEL) an.
			 *
			 * @param int    $booking_id Buchungs-ID.
			 * @param string $source     Ausloeser ('admin' beim Betreiber-Storno).
			 */
			do_action( 'kurato_booking_cancelled', $id, 'admin' );
			$this->redirect( 'cancelled', $offset, $host_filter );
		}

		$this->redirect( 'cancel_failed', $offset, $host_filter );
	}

	// --- Helfer ------------------------------------------------------------

	/**
	 * Storno-Schaltflaeche (admin-post-Formular mit Rueckfrage).
	 *
	 * @param int $booking_id  Buchungs-ID.
	 * @param int $offset      Wochen-Offset (Kontext erhalten).
	 * @param int $host_filter Host-Filter (Kontext erhalten).
	 * @return string
	 */
	private function cancel_button( int $booking_id, int $offset, int $host_filter ): string {
		$html  = '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" style="display:inline;" onsubmit="return confirm(\'' . esc_js( __( 'Diesen Termin wirklich stornieren? Der Besucher wird per E-Mail informiert.', 'kurato' ) ) . '\');">';
		$html .= '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_CANCEL ) . '" />';
		$html .= '<input type="hidden" name="booking" value="' . esc_attr( (string) $booking_id ) . '" />';
		$html .= '<input type="hidden" name="week_offset" value="' . esc_attr( (string) $offset ) . '" />';
		$html .= '<input type="hidden" name="host" value="' . esc_attr( (string) $host_filter ) . '" />';
		$html .= wp_nonce_field( self::ACTION_CANCEL . '_' . $booking_id, '_wpnonce', true, false );
		$html .= '<button type="submit" class="button-link submitdelete">' . esc_html__( 'Stornieren', 'kurato' ) . '</button>';
		$html .= '</form>';
		return $html;
	}

	/**
	 * Status-Badge (hs-shield-Muster).
	 *
	 * @param string $status Status.
	 * @return string
	 */
	private function status_badge( string $status ): string {
		$labels = array(
			'confirmed' => __( 'Bestaetigt', 'kurato' ),
			'cancelled' => __( 'Storniert', 'kurato' ),
		);
		$classes = array(
			'confirmed' => 'hs-shield--green',
			'cancelled' => 'hs-shield--grey',
		);
		$label = $labels[ $status ] ?? $status;
		$class = $classes[ $status ] ?? 'hs-shield--grey';
		return '<span class="hs-shield ' . esc_attr( $class ) . '">' . esc_html( $label ) . '</span>';
	}

	/**
	 * Formatiert eine UTC-'Y-m-d H:i:s'-Zeit in der Site-Zeitzone (Datum + Uhrzeit).
	 *
	 * @param string $utc UTC-Zeit.
	 * @return string
	 */
	private function format_datetime( string $utc ): string {
		$ts = strtotime( $utc . ' UTC' );
		if ( false === $ts ) {
			return $utc;
		}
		if ( function_exists( 'wp_date' ) ) {
			return (string) wp_date( 'D, d.m.Y H:i', $ts );
		}
		return date_i18n( 'D, d.m.Y H:i', $ts );
	}

	/**
	 * Formatiert ein Datum (ohne Uhrzeit) fuer die Wochen-Kopfzeile.
	 *
	 * @param \DateTimeImmutable $date Datum (Site-Zeit).
	 * @return string
	 */
	private function format_day( \DateTimeImmutable $date ): string {
		if ( function_exists( 'wp_date' ) ) {
			return (string) wp_date( 'd.m.Y', $date->getTimestamp() );
		}
		return $date->format( 'd.m.Y' );
	}

	/**
	 * Gewuenschter Wochen-Offset aus der URL (in vernuenftige Grenzen geklemmt).
	 *
	 * @return int
	 */
	private function requested_offset(): int {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nur Auswahl der angezeigten Woche.
		$raw = isset( $_GET['week_offset'] ) ? (int) wp_unslash( $_GET['week_offset'] ) : 0;
		if ( $raw < -520 ) {
			return -520;
		}
		if ( $raw > 520 ) {
			return 520;
		}
		return $raw;
	}

	/**
	 * Gewuenschter Host-Filter aus der URL.
	 *
	 * @return int
	 */
	private function requested_host(): int {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nur Auswahl des Filters.
		return isset( $_GET['host'] ) ? absint( wp_unslash( $_GET['host'] ) ) : 0;
	}

	/**
	 * URL der Liste mit Woche und Filter.
	 *
	 * @param int $offset      Wochen-Offset.
	 * @param int $host_filter Host-Filter.
	 * @return string
	 */
	private function list_url( int $offset, int $host_filter ): string {
		$args = array( 'page' => self::SLUG, 'week_offset' => $offset );
		if ( $host_filter > 0 ) {
			$args['host'] = $host_filter;
		}
		return admin_url( add_query_arg( $args, 'admin.php' ) );
	}

	/**
	 * Capability-Wache.
	 *
	 * @return void
	 */
	private function guard(): void {
		if ( ! current_user_can( self::CAP ) ) {
			wp_die( esc_html__( 'Sie haben keine Berechtigung, die Termine einzusehen.', 'kurato' ) );
		}
	}

	/**
	 * Statusmeldung nach Redirect.
	 *
	 * @return void
	 */
	private function render_notice(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nur Anzeige nach Redirect.
		$msg = isset( $_GET['msg'] ) ? sanitize_key( wp_unslash( $_GET['msg'] ) ) : '';
		if ( 'cancelled' === $msg ) {
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Der Termin wurde storniert.', 'kurato' ) . '</p></div>';
		} elseif ( 'cancel_failed' === $msg ) {
			echo '<div class="notice notice-error is-dismissible"><p>' . esc_html__( 'Der Termin konnte nicht storniert werden. Vielleicht war er bereits storniert.', 'kurato' ) . '</p></div>';
		}
	}

	/**
	 * Redirect zurueck auf die Liste (Woche und Filter erhalten).
	 *
	 * @param string $msg         Statuscode.
	 * @param int    $offset      Wochen-Offset.
	 * @param int    $host_filter Host-Filter.
	 * @return void
	 */
	private function redirect( string $msg, int $offset, int $host_filter ): void {
		$args = array( 'page' => self::SLUG, 'week_offset' => $offset, 'msg' => $msg );
		if ( $host_filter > 0 ) {
			$args['host'] = $host_filter;
		}
		wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) );
		exit;
	}
}
