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

namespace Hafenstudios\Kurato\Db;

defined( 'ABSPATH' ) || exit;

/**
 * Terminarten je Host (hs_booking_types, K5a).
 *
 * Eine Terminart traegt Dauer, Ort (phone/video/in_person + freies Detailfeld),
 * Puffer vor/nach, Vorlauf (lead_time_hours) und Horizont (horizon_days). Free-
 * Umfang sind zwei Terminarten je Host; das Limit sitzt in der Admin-UI (K5b).
 *
 * Nicht final, damit Tests die DB-Methoden per Test-Double ueberschreiben koennen.
 */
class BookingTypeRepository {

	/**
	 * Erlaubte Ortstypen.
	 *
	 * @var array<int, string>
	 */
	public const LOCATION_TYPES = array( 'phone', 'video', 'in_person' );

	/**
	 * Terminarten eines Hosts (aktive zuerst, dann alphabetisch).
	 *
	 * @param int  $host_id     Host-ID.
	 * @param bool $active_only Nur aktive Terminarten.
	 * @return array<int, array<string, mixed>>
	 */
	public function for_host( int $host_id, bool $active_only = false ): array {
		global $wpdb;
		$table = Schema::table( 'booking_types' );
		$sql   = "SELECT * FROM {$table} WHERE host_id = %d";
		if ( $active_only ) {
			$sql .= ' AND active = 1';
		}
		$sql .= ' ORDER BY label ASC';
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$rows = $wpdb->get_results( $wpdb->prepare( $sql, $host_id ), ARRAY_A );
		return array_map( array( $this, 'hydrate' ), $rows ?: array() );
	}

	/**
	 * Eine Terminart.
	 *
	 * @param int $id Terminart-ID.
	 * @return array<string, mixed>|null
	 */
	public function get( int $id ): ?array {
		global $wpdb;
		$table = Schema::table( 'booking_types' );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $id ), ARRAY_A );
		return $row ? $this->hydrate( $row ) : null;
	}

	/**
	 * Terminart eines Hosts per Slug (fuer `/termin/{host}?art={slug}`).
	 *
	 * @param int    $host_id Host-ID.
	 * @param string $slug    Terminart-Slug.
	 * @return array<string, mixed>|null
	 */
	public function get_by_slug( int $host_id, string $slug ): ?array {
		$slug = sanitize_title( $slug );
		if ( '' === $slug ) {
			return null;
		}
		global $wpdb;
		$table = Schema::table( 'booking_types' );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE host_id = %d AND slug = %s", $host_id, $slug ), ARRAY_A );
		return $row ? $this->hydrate( $row ) : null;
	}

	/**
	 * Legt eine Terminart an.
	 *
	 * @param array<string, mixed> $data host_id, label, slug, duration_min,
	 *                                    location_type, location_detail,
	 *                                    buffer_before_min, buffer_after_min,
	 *                                    lead_time_hours, horizon_days, active.
	 * @return int Terminart-ID (0 bei Fehler).
	 */
	public function create( array $data ): int {
		$host_id = absint( $data['host_id'] ?? 0 );
		$label   = trim( sanitize_text_field( (string) ( $data['label'] ?? '' ) ) );
		if ( $host_id <= 0 || '' === $label ) {
			return 0;
		}
		$slug = $this->unique_slug( $host_id, sanitize_title( '' !== (string) ( $data['slug'] ?? '' ) ? (string) $data['slug'] : $label ) );

		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$ok = $wpdb->insert( Schema::table( 'booking_types' ), $this->sanitize_row( $data, $host_id, $label, $slug ) );
		return $ok ? (int) $wpdb->insert_id : 0;
	}

	/**
	 * Aktualisiert eine Terminart (nur uebergebene Felder).
	 *
	 * @param int                  $id   Terminart-ID.
	 * @param array<string, mixed> $data Zu aendernde Felder.
	 * @return bool
	 */
	public function update( int $id, array $data ): bool {
		$map = array(
			'label'             => 'text',
			'location_detail'   => 'text',
			'location_type'     => 'location',
			'duration_min'      => 'uint',
			'buffer_before_min' => 'uint',
			'buffer_after_min'  => 'uint',
			'lead_time_hours'   => 'uint',
			'horizon_days'      => 'uint',
			'active'            => 'bool',
		);
		$row = array();
		foreach ( $map as $key => $cast ) {
			if ( ! array_key_exists( $key, $data ) ) {
				continue;
			}
			$row[ $key ] = $this->cast_value( $cast, $data[ $key ] );
		}
		if ( array() === $row ) {
			return false;
		}
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return false !== $wpdb->update( Schema::table( 'booking_types' ), $row, array( 'id' => $id ) );
	}

	/**
	 * Loescht eine Terminart samt zugehoeriger Verfuegbarkeits-Fenster.
	 *
	 * @param int $id Terminart-ID.
	 * @return bool
	 */
	public function delete( int $id ): bool {
		global $wpdb;
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->delete( Schema::table( 'availability' ), array( 'booking_type_id' => $id ) );
		$deleted = $wpdb->delete( Schema::table( 'booking_types' ), array( 'id' => $id ) );
		// phpcs:enable
		return (bool) $deleted;
	}

	/**
	 * Anzahl der Terminarten eines Hosts (Free-Limit in der Admin-UI, K5b).
	 *
	 * @param int $host_id Host-ID.
	 * @return int
	 */
	public function count_for_host( int $host_id ): int {
		global $wpdb;
		$table = Schema::table( 'booking_types' );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE host_id = %d", $host_id ) );
	}

	/**
	 * Baut die vollstaendige Insert-Zeile.
	 *
	 * @param array<string, mixed> $data    Rohdaten.
	 * @param int                  $host_id Host-ID.
	 * @param string               $label   Bereinigtes Label.
	 * @param string               $slug    Eindeutiger Slug.
	 * @return array<string, mixed>
	 */
	private function sanitize_row( array $data, int $host_id, string $label, string $slug ): array {
		return array(
			'host_id'           => $host_id,
			'label'             => substr( $label, 0, 190 ),
			'slug'              => $slug,
			'duration_min'      => max( 1, absint( $data['duration_min'] ?? 30 ) ),
			'location_type'     => $this->cast_value( 'location', $data['location_type'] ?? 'phone' ),
			'location_detail'   => substr( sanitize_text_field( (string) ( $data['location_detail'] ?? '' ) ), 0, 255 ),
			'buffer_before_min' => absint( $data['buffer_before_min'] ?? 0 ),
			'buffer_after_min'  => absint( $data['buffer_after_min'] ?? 0 ),
			'lead_time_hours'   => absint( $data['lead_time_hours'] ?? 0 ),
			'horizon_days'      => max( 1, absint( $data['horizon_days'] ?? 60 ) ),
			'active'            => isset( $data['active'] ) && ! $data['active'] ? 0 : 1,
		);
	}

	/**
	 * Wandelt einen Wert je nach Zieltyp.
	 *
	 * @param string $cast  Zieltyp (text|location|uint|bool).
	 * @param mixed  $value Rohwert.
	 * @return mixed
	 */
	private function cast_value( string $cast, $value ) {
		switch ( $cast ) {
			case 'uint':
				return absint( $value );
			case 'bool':
				return ! empty( $value ) ? 1 : 0;
			case 'location':
				$loc = sanitize_key( (string) $value );
				return in_array( $loc, self::LOCATION_TYPES, true ) ? $loc : 'phone';
			default:
				return substr( sanitize_text_field( (string) $value ), 0, 255 );
		}
	}

	/**
	 * Eindeutiger Slug je Host.
	 *
	 * @param int    $host_id Host-ID.
	 * @param string $slug    Basis-Slug.
	 * @return string
	 */
	private function unique_slug( int $host_id, string $slug ): string {
		global $wpdb;
		$table = Schema::table( 'booking_types' );
		$base  = '' !== $slug ? $slug : 'termin';
		$try   = $base;
		$n     = 1;
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		while ( (int) $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$table} WHERE host_id = %d AND slug = %s", $host_id, $try ) ) > 0 ) {
			$n++;
			$try = $base . '-' . $n;
		}
		// phpcs:enable
		return $try;
	}

	/**
	 * Normalisiert eine Zeile.
	 *
	 * @param array<string, mixed> $row Rohzeile.
	 * @return array<string, mixed>
	 */
	private function hydrate( array $row ): array {
		return array(
			'id'                => (int) $row['id'],
			'host_id'           => (int) $row['host_id'],
			'label'             => (string) $row['label'],
			'slug'              => (string) $row['slug'],
			'duration_min'      => (int) $row['duration_min'],
			'location_type'     => (string) $row['location_type'],
			'location_detail'   => (string) ( $row['location_detail'] ?? '' ),
			'buffer_before_min' => (int) $row['buffer_before_min'],
			'buffer_after_min'  => (int) $row['buffer_after_min'],
			'lead_time_hours'   => (int) $row['lead_time_hours'],
			'horizon_days'      => (int) $row['horizon_days'],
			'active'            => (bool) $row['active'],
		);
	}
}
