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

namespace Hafenstudios\Kurato\Booking;

use Hafenstudios\Core\Mail\Message;
use Hafenstudios\Kurato\Db\BookingRepository;
use Hafenstudios\Kurato\Db\BookingTypeRepository;
use Hafenstudios\Kurato\Db\ContactRepository;
use Hafenstudios\Kurato\Db\HostRepository;
use Hafenstudios\Kurato\Mail\DriverFactory;
use Hafenstudios\Kurato\Site\BookingPage;

defined( 'ABSPATH' ) || exit;

/**
 * Transaktionsmails des Buchungsmoduls (K5c, K5-KONZEPT Abschnitt 5).
 *
 * Alle Mails gehen DIREKT ueber den aktuellen Treiber (DriverFactory->current(),
 * Muster WelcomeMailService), NIE durch die Marketing-Queue: kein Abmeldelink,
 * kein Tracking. Es gibt drei Anlaesse:
 *
 * - Bestaetigung an Besucher UND Host bei einer neuen Buchung (Storno-Link nur
 *   beim Besucher).
 * - Storno-Info: an die Action `kurato_booking_cancelled` gehaengt. source 'admin'
 *   (Betreiber-Storno) -> Mail an den Besucher; source 'visitor' (Storno ueber den
 *   Link) -> Mail an den Host.
 * - Erinnerung vor dem Termin (Erinnerungs-Cron, {@see BookingReminders}).
 *
 * ICS: das Core-`Message`-Objekt traegt keine Anhaenge (geprueft in K5c); die
 * Termindatei wird deshalb als signierter Download-Link (BookingToken ueber den
 * ICS-Endpoint der oeffentlichen Seite) in die Mails und auf die Bestaetigungs-/
 * Storno-Seite gesetzt. Der reine ICS-Inhalt kommt aus {@see ics_for_booking()}.
 *
 * Nicht final; `config()` und `send()` sind Test-Seams (Muster WelcomeMailService).
 */
class BookingMailer {

	private BookingRepository $bookings;

	private HostRepository $hosts;

	private BookingTypeRepository $types;

	private ContactRepository $contacts;

	private IcsBuilder $ics;

	private BookingToken $tokens;

	/**
	 * @param BookingRepository     $bookings Buchungs-Repository.
	 * @param HostRepository        $hosts    Host-Repository.
	 * @param BookingTypeRepository $types    Terminarten-Repository.
	 * @param ContactRepository     $contacts Kontakt-Repository.
	 * @param IcsBuilder            $ics      ICS-Builder (rein).
	 * @param BookingToken          $tokens   Signierer fuer Storno-/ICS-Links.
	 */
	public function __construct( BookingRepository $bookings, HostRepository $hosts, BookingTypeRepository $types, ContactRepository $contacts, IcsBuilder $ics, BookingToken $tokens ) {
		$this->bookings = $bookings;
		$this->hosts    = $hosts;
		$this->types    = $types;
		$this->contacts = $contacts;
		$this->ics      = $ics;
		$this->tokens   = $tokens;
	}

	/**
	 * Verdrahtet die Storno-Action.
	 *
	 * @return void
	 */
	public function register(): void {
		add_action( 'kurato_booking_cancelled', array( $this, 'on_cancelled' ), 10, 2 );
	}

	// --- Bestaetigung ------------------------------------------------------

	/**
	 * Sendet die Bestaetigungsmails an Besucher (mit Storno-Link) und Host.
	 *
	 * @param array<string, mixed> $booking Buchung (id, start_utc, end_utc, ...).
	 * @param array<string, mixed> $host    Host.
	 * @param array<string, mixed> $type    Terminart.
	 * @param array<string, mixed> $contact Kontakt (email, display_name).
	 * @return void
	 */
	public function send_confirmation( array $booking, array $host, array $type, array $contact ): void {
		$config     = $this->config();
		$to_visitor = sanitize_email( (string) ( $contact['email'] ?? '' ) );
		$when       = $this->format_datetime( (string) $booking['start_utc'] );
		$where      = $this->location_line( $type );
		$ics_url    = $this->ics_url( (int) $booking['id'] );

		if ( '' !== $to_visitor ) {
			$storno = $this->manage_url( (int) $booking['id'] );

			$lines   = array();
			$lines[] = sprintf(
				/* translators: %s: Name der Terminart */
				__( 'vielen Dank fuer Ihre Buchung. Ihr Termin "%s" ist bestaetigt.', 'kurato' ),
				(string) $type['label']
			);
			$body  = '<p>' . esc_html( sprintf( /* translators: %s: Anrede-Name */ __( 'Hallo %s,', 'kurato' ), (string) ( $contact['display_name'] ?? '' ) ) ) . '</p>';
			$body .= '<p>' . esc_html( $lines[0] ) . '</p>';
			$body .= $this->detail_block( $host, $type, $when, $where );
			$body .= '<p>' . sprintf(
				/* translators: %s: Download-Link */
				esc_html__( 'Termin zum Kalender hinzufuegen: %s', 'kurato' ),
				'<a href="' . esc_url( $ics_url ) . '">' . esc_html__( 'Kalenderdatei herunterladen', 'kurato' ) . '</a>'
			) . '</p>';
			$body .= '<p>' . sprintf(
				/* translators: %s: Storno-Link */
				esc_html__( 'Sie moechten den Termin absagen oder verschieben? %s', 'kurato' ),
				'<a href="' . esc_url( $storno ) . '">' . esc_html__( 'Termin verwalten', 'kurato' ) . '</a>'
			) . '</p>';

			$text  = sprintf( /* translators: %s: Anrede-Name */ __( 'Hallo %s,', 'kurato' ), (string) ( $contact['display_name'] ?? '' ) ) . "\n\n";
			$text .= $lines[0] . "\n\n";
			$text .= $this->detail_text( $host, $type, $when, $where ) . "\n\n";
			$text .= __( 'Termin zum Kalender hinzufuegen:', 'kurato' ) . ' ' . $ics_url . "\n";
			$text .= __( 'Termin verwalten (absagen oder verschieben):', 'kurato' ) . ' ' . $storno;

			$subject = sprintf( /* translators: %s: Terminart */ __( 'Terminbestaetigung: %s', 'kurato' ), (string) $type['label'] );
			$this->send( $this->message( $to_visitor, $subject, $body, $config, $text ) );
		}

		$to_host = $this->host_email( $host, $config );
		if ( '' !== $to_host ) {
			$body  = '<p>' . esc_html__( 'es wurde ein neuer Termin gebucht.', 'kurato' ) . '</p>';
			$body .= $this->detail_block( $host, $type, $when, $where );
			$body .= '<p>' . esc_html( sprintf(
				/* translators: 1: Name, 2: E-Mail */
				__( 'Kontakt: %1$s (%2$s)', 'kurato' ),
				(string) ( $contact['display_name'] ?? '' ),
				(string) ( $contact['email'] ?? '' )
			) ) . '</p>';
			if ( '' !== trim( (string) ( $booking['note'] ?? '' ) ) ) {
				$body .= '<p>' . esc_html__( 'Anliegen:', 'kurato' ) . '<br />' . nl2br( esc_html( (string) $booking['note'] ) ) . '</p>';
			}
			$body .= '<p>' . sprintf(
				/* translators: %s: Download-Link */
				esc_html__( 'Termin zum Kalender hinzufuegen: %s', 'kurato' ),
				'<a href="' . esc_url( $ics_url ) . '">' . esc_html__( 'Kalenderdatei herunterladen', 'kurato' ) . '</a>'
			) . '</p>';

			$text  = __( 'es wurde ein neuer Termin gebucht.', 'kurato' ) . "\n\n";
			$text .= $this->detail_text( $host, $type, $when, $where ) . "\n\n";
			$text .= sprintf(
				/* translators: 1: Name, 2: E-Mail */
				__( 'Kontakt: %1$s (%2$s)', 'kurato' ),
				(string) ( $contact['display_name'] ?? '' ),
				(string) ( $contact['email'] ?? '' )
			);
			if ( '' !== trim( (string) ( $booking['note'] ?? '' ) ) ) {
				$text .= "\n" . __( 'Anliegen:', 'kurato' ) . ' ' . trim( (string) $booking['note'] );
			}
			$text .= "\n\n" . __( 'Termin zum Kalender hinzufuegen:', 'kurato' ) . ' ' . $ics_url;

			$subject = sprintf( /* translators: %s: Terminart */ __( 'Neue Buchung: %s', 'kurato' ), (string) $type['label'] );
			$this->send( $this->message( $to_host, $subject, $body, $config, $text ) );
		}
	}

	// --- Storno ------------------------------------------------------------

	/**
	 * Reagiert auf `kurato_booking_cancelled`: source 'admin' -> Info an den
	 * Besucher, source 'visitor' -> Info an den Host.
	 *
	 * @param int    $booking_id Buchungs-ID.
	 * @param string $source     Ausloeser ('admin' | 'visitor').
	 * @return void
	 */
	public function on_cancelled( int $booking_id, string $source ): void {
		$booking = $this->bookings->get( $booking_id );
		if ( null === $booking ) {
			return;
		}
		$host = $this->hosts->get( (int) $booking['host_id'] );
		$type = $this->types->get( (int) $booking['type_id'] );
		if ( null === $host || null === $type ) {
			return;
		}

		$config = $this->config();
		$when   = $this->format_datetime( (string) $booking['start_utc'] );
		$where  = $this->location_line( $type );

		if ( 'admin' === $source ) {
			$contact = ! empty( $booking['contact_id'] ) ? $this->contacts->get( (int) $booking['contact_id'] ) : null;
			$to      = null !== $contact ? sanitize_email( (string) ( $contact['email'] ?? '' ) ) : '';
			if ( '' === $to ) {
				return;
			}
			$body  = '<p>' . esc_html( sprintf( /* translators: %s: Anrede-Name */ __( 'Hallo %s,', 'kurato' ), (string) ( $contact['display_name'] ?? '' ) ) ) . '</p>';
			$body .= '<p>' . esc_html__( 'Ihr Termin wurde abgesagt.', 'kurato' ) . '</p>';
			$body .= $this->detail_block( $host, $type, $when, $where );
			$body .= '<p>' . sprintf(
				/* translators: %s: Link zur Buchungsseite */
				esc_html__( 'Sie moechten einen neuen Termin buchen? %s', 'kurato' ),
				'<a href="' . esc_url( $this->booking_url( (string) $host['slug'] ) ) . '">' . esc_html__( 'Zur Terminbuchung', 'kurato' ) . '</a>'
			) . '</p>';

			$text  = sprintf( /* translators: %s: Anrede-Name */ __( 'Hallo %s,', 'kurato' ), (string) ( $contact['display_name'] ?? '' ) ) . "\n\n";
			$text .= __( 'Ihr Termin wurde abgesagt.', 'kurato' ) . "\n\n";
			$text .= $this->detail_text( $host, $type, $when, $where ) . "\n\n";
			$text .= __( 'Neuen Termin buchen:', 'kurato' ) . ' ' . $this->booking_url( (string) $host['slug'] );

			$subject = sprintf( /* translators: %s: Terminart */ __( 'Termin abgesagt: %s', 'kurato' ), (string) $type['label'] );
			$this->send( $this->message( $to, $subject, $body, $config, $text ) );
			return;
		}

		if ( 'visitor' === $source ) {
			$to = $this->host_email( $host, $config );
			if ( '' === $to ) {
				return;
			}
			$contact = ! empty( $booking['contact_id'] ) ? $this->contacts->get( (int) $booking['contact_id'] ) : null;

			$body  = '<p>' . esc_html__( 'ein Besucher hat seinen Termin abgesagt.', 'kurato' ) . '</p>';
			$body .= $this->detail_block( $host, $type, $when, $where );
			if ( null !== $contact ) {
				$body .= '<p>' . esc_html( sprintf(
					/* translators: 1: Name, 2: E-Mail */
					__( 'Kontakt: %1$s (%2$s)', 'kurato' ),
					(string) ( $contact['display_name'] ?? '' ),
					(string) ( $contact['email'] ?? '' )
				) ) . '</p>';
			}

			$text  = __( 'ein Besucher hat seinen Termin abgesagt.', 'kurato' ) . "\n\n";
			$text .= $this->detail_text( $host, $type, $when, $where );
			if ( null !== $contact ) {
				$text .= "\n\n" . sprintf(
					/* translators: 1: Name, 2: E-Mail */
					__( 'Kontakt: %1$s (%2$s)', 'kurato' ),
					(string) ( $contact['display_name'] ?? '' ),
					(string) ( $contact['email'] ?? '' )
				);
			}

			$subject = sprintf( /* translators: %s: Terminart */ __( 'Termin abgesagt: %s', 'kurato' ), (string) $type['label'] );
			$this->send( $this->message( $to, $subject, $body, $config, $text ) );
		}
	}

	// --- Erinnerung --------------------------------------------------------

	/**
	 * Sendet eine Terminerinnerung an einen Empfaenger.
	 *
	 * @param string               $to      Empfaenger.
	 * @param bool                 $is_host Empfaenger ist der Host (kein Storno-Link).
	 * @param array<string, mixed> $booking Buchung.
	 * @param array<string, mixed> $host    Host.
	 * @param array<string, mixed> $type    Terminart.
	 * @param array<string, mixed> $contact Kontakt.
	 * @return bool true, wenn versendet.
	 */
	public function send_reminder( string $to, bool $is_host, array $booking, array $host, array $type, array $contact ): bool {
		$to = sanitize_email( $to );
		if ( '' === $to ) {
			return false;
		}
		$config = $this->config();
		$when   = $this->format_datetime( (string) $booking['start_utc'] );
		$where  = $this->location_line( $type );

		$body  = '<p>' . esc_html__( 'eine kurze Erinnerung an Ihren bevorstehenden Termin.', 'kurato' ) . '</p>';
		$body .= $this->detail_block( $host, $type, $when, $where );
		if ( ! $is_host ) {
			$body .= '<p>' . sprintf(
				/* translators: %s: Storno-Link */
				esc_html__( 'Sie koennen nicht teilnehmen? %s', 'kurato' ),
				'<a href="' . esc_url( $this->manage_url( (int) $booking['id'] ) ) . '">' . esc_html__( 'Termin verwalten', 'kurato' ) . '</a>'
			) . '</p>';
			$body .= '<p>' . sprintf(
				/* translators: %s: Download-Link */
				esc_html__( 'Termin zum Kalender hinzufuegen: %s', 'kurato' ),
				'<a href="' . esc_url( $this->ics_url( (int) $booking['id'] ) ) . '">' . esc_html__( 'Kalenderdatei herunterladen', 'kurato' ) . '</a>'
			) . '</p>';
		}

		$text  = __( 'eine kurze Erinnerung an Ihren bevorstehenden Termin.', 'kurato' ) . "\n\n";
		$text .= $this->detail_text( $host, $type, $when, $where );
		if ( ! $is_host ) {
			$text .= "\n\n" . __( 'Termin verwalten (absagen oder verschieben):', 'kurato' ) . ' ' . $this->manage_url( (int) $booking['id'] );
			$text .= "\n" . __( 'Termin zum Kalender hinzufuegen:', 'kurato' ) . ' ' . $this->ics_url( (int) $booking['id'] );
		}

		$subject = sprintf( /* translators: %s: Terminart */ __( 'Erinnerung: %s', 'kurato' ), (string) $type['label'] );
		return $this->send( $this->message( $to, $subject, $body, $config, $text ) );
	}

	// --- ICS ---------------------------------------------------------------

	/**
	 * Baut den ICS-Inhalt fuer eine Buchung: METHOD:REQUEST fuer confirmed,
	 * METHOD:CANCEL fuer cancelled (Umbuchung v1 = Storno + Neubuchung).
	 *
	 * @param array<string, mixed> $booking Buchung.
	 * @param array<string, mixed> $host    Host.
	 * @param array<string, mixed> $type    Terminart.
	 * @return string ICS-Text.
	 */
	public function ics_for_booking( array $booking, array $host, array $type ): string {
		$event = array(
			'id'          => (int) $booking['id'],
			'site_host'   => $this->site_host(),
			'start_utc'   => (string) $booking['start_utc'],
			'end_utc'     => (string) $booking['end_utc'],
			'summary'     => (string) $type['label'] . ' - ' . (string) $host['label'],
			'location'    => $this->location_line( $type ),
			'description' => (string) ( $type['label'] ?? '' ),
			'dtstamp'     => '' !== (string) ( $booking['created_at'] ?? '' ) ? (string) $booking['created_at'] : 'now',
		);
		return 'cancelled' === (string) ( $booking['status'] ?? 'confirmed' )
			? $this->ics->build_cancel( $event )
			: $this->ics->build( $event );
	}

	// --- URL-Helfer --------------------------------------------------------

	/**
	 * Signierter Storno-/Verwaltungs-Link (BookingToken).
	 *
	 * @param int $booking_id Buchungs-ID.
	 * @return string
	 */
	public function manage_url( int $booking_id ): string {
		return BookingPage::manage_url( $this->tokens->create( $booking_id ) );
	}

	/**
	 * Signierter ICS-Download-Link (BookingToken).
	 *
	 * @param int $booking_id Buchungs-ID.
	 * @return string
	 */
	public function ics_url( int $booking_id ): string {
		return BookingPage::ics_url( $this->tokens->create( $booking_id ) );
	}

	/**
	 * Oeffentliche Buchungs-URL eines Hosts.
	 *
	 * @param string $slug Host-Slug.
	 * @return string
	 */
	private function booking_url( string $slug ): string {
		return BookingPage::booking_url( $slug );
	}

	// --- Test-Seams --------------------------------------------------------

	/**
	 * Absender-/Site-Einstellungen. Seam.
	 *
	 * @return array{from_name:string,from_email:string,site_name:string,admin_email:string}
	 */
	protected function config(): array {
		$settings = \Hafenstudios\Core\Core::instance()->settings( 'kurato' );
		return array(
			'from_name'   => (string) $settings->get( 'mail.sender.from_name', get_bloginfo( 'name' ) ),
			'from_email'  => (string) $settings->get( 'mail.sender.from_email', '' ),
			'site_name'   => (string) get_bloginfo( 'name' ),
			'admin_email' => (string) get_option( 'admin_email', '' ),
		);
	}

	/**
	 * Versendet eine Einzelmail direkt ueber den aktuellen Treiber. Seam.
	 *
	 * @param Message $message Nachricht.
	 * @return bool
	 */
	protected function send( Message $message ): bool {
		return ( new DriverFactory() )->current()->send( $message );
	}

	/**
	 * Host aus der Site (fuer die ICS-UID). Seam-nah (nur get_bloginfo).
	 *
	 * @return string
	 */
	protected function site_host(): string {
		$url = (string) get_bloginfo( 'url' );
		return '' !== $url ? $url : 'localhost';
	}

	// --- Bau-Helfer --------------------------------------------------------

	/**
	 * Baut eine HTML-Transaktionsmail (ohne Abmelde-Footer, ohne Tracking).
	 *
	 * @param string                $to      Empfaenger.
	 * @param string                $subject Betreff.
	 * @param string                $body    HTML-Inhalt (bereits escaped).
	 * @param array<string, string> $config  Absenderwerte.
	 * @return Message
	 */
	private function message( string $to, string $subject, string $body, array $config, string $text = '' ): Message {
		$html          = '<div style="font-family:Arial,Helvetica,sans-serif;font-size:15px;line-height:1.5;color:#222;">' . $body . '</div>';
		$message       = new Message( $to, $subject, $html );
		$message->is_html = true;
		// Multipart-Alternative (Zustellbarkeit): schlichter Klartext der Termindaten.
		if ( '' !== $text ) {
			$message->text_body = $text;
		}
		if ( '' !== (string) ( $config['from_email'] ?? '' ) ) {
			$message->from_email = (string) $config['from_email'];
			$message->from_name  = (string) ( $config['from_name'] ?? '' );
		}
		return $message;
	}

	/**
	 * Termin-Detailblock (Terminart, Ansprechpartner, Zeit, Ort).
	 *
	 * @param array<string, mixed> $host  Host.
	 * @param array<string, mixed> $type  Terminart.
	 * @param string               $when  Formatierte Zeit.
	 * @param string               $where Ort-Zeile.
	 * @return string
	 */
	private function detail_block( array $host, array $type, string $when, string $where ): string {
		$rows  = '<p style="background:#f4f4f4;border-radius:6px;padding:12px 16px;">';
		$rows .= '<strong>' . esc_html( (string) $type['label'] ) . '</strong><br />';
		$rows .= esc_html( sprintf( /* translators: %s: Name */ __( 'mit %s', 'kurato' ), (string) $host['label'] ) ) . '<br />';
		$rows .= esc_html( $when );
		if ( '' !== $where ) {
			$rows .= '<br />' . esc_html( $where );
		}
		$rows .= '</p>';
		return $rows;
	}

	/**
	 * Ort-Zeile aus Terminart (Typ + Detail).
	 *
	 * @param array<string, mixed> $type Terminart.
	 * @return string
	 */
	private function location_line( array $type ): string {
		$labels = array(
			'phone'     => __( 'Telefon', 'kurato' ),
			'video'     => __( 'Video', 'kurato' ),
			'in_person' => __( 'Vor Ort', 'kurato' ),
		);
		$label  = $labels[ (string) ( $type['location_type'] ?? '' ) ] ?? '';
		$detail = trim( (string) ( $type['location_detail'] ?? '' ) );
		if ( '' === $label ) {
			return $detail;
		}
		return '' !== $detail ? $label . ': ' . $detail : $label;
	}

	/**
	 * E-Mail des Hosts: bevorzugt der verknuepfte WP-Benutzer, sonst die
	 * Administrator-Adresse der Site.
	 *
	 * @param array<string, mixed>  $host   Host.
	 * @param array<string, string> $config Absenderwerte.
	 * @return string
	 */
	private function host_email( array $host, array $config ): string {
		if ( ! empty( $host['user_id'] ) && function_exists( 'get_userdata' ) ) {
			$user = get_userdata( (int) $host['user_id'] );
			if ( $user && '' !== (string) $user->user_email ) {
				return sanitize_email( (string) $user->user_email );
			}
		}
		return sanitize_email( (string) ( $config['admin_email'] ?? '' ) );
	}

	/**
	 * Formatiert eine UTC-'Y-m-d H:i:s'-Zeit in der Site-Zeitzone, sprachneutral.
	 *
	 * Bewusst OHNE Wochentagsnamen: der Name (z.B. "l") folgt der Site-Locale und
	 * erscheint sonst in einer deutschen Mail als englisches "Thursday", wenn die
	 * Site auf EN steht (Live-Fund 23.07.). Datum und Uhrzeit sind reine Zahlen und
	 * damit locale-unabhaengig; die Uhrzeit bleibt in der Site-Zeitzone. Das
	 * zusammensetzende Format ist uebersetzbar (i18n), Ausgabe z.B.
	 * "23.07.2026, 14:00 Uhr".
	 *
	 * @param string $utc UTC-Zeit.
	 * @return string
	 */
	private function format_datetime( string $utc ): string {
		$ts = strtotime( $utc . ' UTC' );
		if ( false === $ts ) {
			return $utc;
		}
		$date = function_exists( 'wp_date' ) ? (string) wp_date( 'd.m.Y', $ts ) : gmdate( 'd.m.Y', $ts );
		$time = function_exists( 'wp_date' ) ? (string) wp_date( 'H:i', $ts ) : gmdate( 'H:i', $ts );
		return sprintf(
			/* translators: 1: Datum, z.B. 23.07.2026. 2: Uhrzeit, z.B. 14:00. */
			__( '%1$s, %2$s Uhr', 'kurato' ),
			$date,
			$time
		);
	}

	/**
	 * Termin-Detailblock als Klartext (fuer die Multipart-Alternative).
	 *
	 * @param array<string, mixed> $host  Host.
	 * @param array<string, mixed> $type  Terminart.
	 * @param string               $when  Formatierte Zeit.
	 * @param string               $where Ort-Zeile.
	 * @return string
	 */
	private function detail_text( array $host, array $type, string $when, string $where ): string {
		$lines   = array();
		$lines[] = (string) $type['label'];
		$lines[] = sprintf( /* translators: %s: Name des Ansprechpartners */ __( 'mit %s', 'kurato' ), (string) $host['label'] );
		$lines[] = $when;
		if ( '' !== $where ) {
			$lines[] = $where;
		}
		return implode( "\n", $lines );
	}
}
