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

namespace Hafenstudios\Kurato\Admin;

use Hafenstudios\Core\Core;
use Hafenstudios\Kurato\Db\ListRepository;
use Hafenstudios\Kurato\Db\RecommendationRepository;
use Hafenstudios\Kurato\Insight\FunnelAnalytics;
use Hafenstudios\Kurato\Insight\RecommendationEngine;

defined( 'ABSPATH' ) || exit;

/**
 * Kompass light (HANS-KI-KONZEPT Schicht 1+2). Serverseitige Auswertungsseite im
 * Kurato-Menue: Funnel-Tabelle (Kontakte je Stufe, Uebergangsraten mit
 * Bezugsgroesse, Median-Verweildauer), Zeitraum-Umschalter (30/90/365 Tage),
 * Wirkungszahlen-Block und darunter die aktiven Hans-Empfehlungskarten (max 3
 * gleichzeitig, Hans-Frequenz-Regel) mit je EINER Aktion + "Nicht mehr
 * vorschlagen".
 *
 * Leitplanke 1: die Seite versendet NICHTS und baut KEINE neuen Aktions-Flows. Die
 * Empfehlungs-Aktionen verlinken auf bestehende Seiten (Kampagne anlegen, Kontakte,
 * Listen). "Annehmen" markiert die Empfehlung (decide accepted) und leitet zur
 * Zielseite; "Nicht mehr vorschlagen" markiert sie (decide dismissed).
 *
 * Cap hs_view_contacts; jede Entscheidung laeuft nonce-geschuetzt ueber admin-post.
 *
 * Seit K-P1 gehoert diese Seite zu Kurato Pro: ohne Lizenz bleibt sie im Menue
 * sichtbar, zeigt aber gekennzeichnete Beispieldaten hinter Milchglas plus EINE
 * Kauf-Karte ({@see ProArea}). Ohne Lizenz wird kein Bericht gerechnet und keine
 * Empfehlung erzeugt; vorhandene Daten bleiben unberuehrt.
 */
final class InsightPage {

	public const SLUG = 'kurato-insight';

	public const ACTION_ACCEPT  = 'kurato_reco_accept';
	public const ACTION_DISMISS = 'kurato_reco_dismiss';

	/**
	 * Hoechstzahl gleichzeitig gezeigter Empfehlungskarten (Hans-Frequenz-Regel).
	 */
	private const MAX_CARDS = 3;

	private FunnelAnalytics $funnel;

	private RecommendationEngine $engine;

	private RecommendationRepository $recommendations;

	/**
	 * @param FunnelAnalytics          $funnel          Funnel-Analytik.
	 * @param RecommendationEngine     $engine          Regel-Auswertung.
	 * @param RecommendationRepository $recommendations Empfehlungs-Log.
	 */
	public function __construct( FunnelAnalytics $funnel, RecommendationEngine $engine, RecommendationRepository $recommendations ) {
		$this->funnel          = $funnel;
		$this->engine          = $engine;
		$this->recommendations = $recommendations;
	}

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

	/**
	 * Rendert die Kompass-Seite.
	 *
	 * @return void
	 */
	public function render(): void {
		if ( ! current_user_can( 'hs_view_contacts' ) ) {
			wp_die( esc_html__( 'Sie haben keine Berechtigung, den Kompass einzusehen.', 'kurato' ) );
		}

		echo '<div class="wrap hs-kurato-insight">';
		echo '<h1 class="wp-heading-inline">' . esc_html__( 'Kompass', 'kurato' ) . '</h1>';
		echo '<hr class="wp-header-end" />';
		echo '<p class="description">' . esc_html__( 'Der Kompass zeigt, wo Ihre Pipeline steht. Jede Zahl nennt ihre Bezugsgroesse; Vorschlaege sind Entwuerfe, die Sie mit einem Klick starten oder ablehnen.', 'kurato' ) . '</p>';

		$this->render_notice();

		// Kompass und Hans-Vorschlaege gehoeren zu Kurato Pro (K-P1). Ohne Lizenz
		// bleibt die Flaeche sichtbar, zeigt aber Beispieldaten - es wird keine
		// echte Zahl berechnet und keine verwischt.
		if ( ! ProArea::enabled() ) {
			echo ProArea::frosted( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Flaeche intern escaped.
				ProArea::CONTEXT_INSIGHT,
				__( 'Sehen, wo Ihre Pipeline haengt', 'kurato' ),
				__( 'Kurato Pro rechnet Ihre echten Uebergangsraten und Verweildauern aus und laesst Hans den naechsten Schritt vorschlagen.', 'kurato' ),
				$this->sample_html()
			);
			echo '</div>';
			return;
		}

		$days   = FunnelAnalytics::normalize_days( $this->requested_days() );
		$report = $this->funnel->report( $days );

		$this->render_range_switch( $days );
		$this->render_recommendations();
		$this->render_funnel_table( $report );
		$this->render_transitions( $report );
		$this->render_impact( $report );

		echo '</div>';
	}

	// --- Verschlossene Flaeche (ohne Kurato Pro) ---------------------------

	/**
	 * Beispiel-Ansicht fuer die verschlossene Flaeche. Sie laeuft absichtlich
	 * durch dieselben Render-Methoden wie die echten Zahlen, damit die Vorschau
	 * genau das zeigt, was Pro liefert - nur mit erfundenen Werten.
	 *
	 * @return string
	 */
	private function sample_html(): string {
		$report = self::sample_report();

		ob_start();
		$this->render_funnel_table( $report );
		$this->render_transitions( $report );
		$this->render_impact( $report );

		return (string) ob_get_clean();
	}

	/**
	 * Erfundener Bericht fuer die Beispiel-Ansicht (nie aus der Datenbank).
	 *
	 * @return array<string, mixed>
	 */
	private static function sample_report(): array {
		return array(
			'days'         => 30,
			'since'        => '',
			'hanging_days' => FunnelAnalytics::HANGING_DAYS,
			'stages'       => array(
				array( 'stage_id' => 1, 'slug' => 'neu', 'label' => __( 'Neu', 'kurato' ), 'is_won' => false, 'is_lost' => false, 'current' => 34, 'median_days' => 2.5, 'hanging' => 3 ),
				array( 'stage_id' => 2, 'slug' => 'kontaktiert', 'label' => __( 'Kontaktiert', 'kurato' ), 'is_won' => false, 'is_lost' => false, 'current' => 21, 'median_days' => 6.0, 'hanging' => 5 ),
				array( 'stage_id' => 3, 'slug' => 'angebot', 'label' => __( 'Angebot', 'kurato' ), 'is_won' => false, 'is_lost' => false, 'current' => 9, 'median_days' => 11.5, 'hanging' => 2 ),
				array( 'stage_id' => 4, 'slug' => 'gewonnen', 'label' => __( 'Gewonnen', 'kurato' ), 'is_won' => true, 'is_lost' => false, 'current' => 12, 'median_days' => null, 'hanging' => 0 ),
			),
			'transitions'  => array(
				array( 'from_id' => 1, 'from_label' => __( 'Neu', 'kurato' ), 'to_id' => 2, 'to_label' => __( 'Kontaktiert', 'kurato' ), 'moved' => 28, 'base' => 40, 'rate' => 0.7 ),
				array( 'from_id' => 2, 'from_label' => __( 'Kontaktiert', 'kurato' ), 'to_id' => 3, 'to_label' => __( 'Angebot', 'kurato' ), 'moved' => 11, 'base' => 28, 'rate' => 0.393 ),
				array( 'from_id' => 3, 'from_label' => __( 'Angebot', 'kurato' ), 'to_id' => 4, 'to_label' => __( 'Gewonnen', 'kurato' ), 'moved' => 4, 'base' => 11, 'rate' => 0.364 ),
			),
			'impact'       => array(
				'contacts_total'          => 76,
				'bookings'                => 8,
				'subscriptions_confirmed' => 31,
				'campaigns_sent'          => 2,
			),
		);
	}

	// --- Zeitraum ----------------------------------------------------------

	/**
	 * Zeitraum-Umschalter (30/90/365 Tage).
	 *
	 * @param int $active Aktiver Zeitraum.
	 * @return void
	 */
	private function render_range_switch( int $active ): void {
		$labels = array(
			30  => __( '30 Tage', 'kurato' ),
			90  => __( '90 Tage', 'kurato' ),
			365 => __( '365 Tage', 'kurato' ),
		);
		echo '<ul class="subsubsub" style="margin-bottom:12px;">';
		$i = 0;
		foreach ( FunnelAnalytics::RANGES as $days ) {
			$url     = add_query_arg( array( 'page' => self::SLUG, 'range' => $days ), admin_url( 'admin.php' ) );
			$current = $days === $active ? ' class="current"' : '';
			$sep     = $i > 0 ? ' | ' : '';
			echo '<li>' . esc_html( $sep ) . '<a href="' . esc_url( $url ) . '"' . $current . '>' . esc_html( $labels[ $days ] ) . '</a></li>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- current ist statisch.
			$i++;
		}
		echo '</ul>';
		echo '<br class="clear" />';
	}

	// --- Funnel ------------------------------------------------------------

	/**
	 * Funnel-Tabelle: Stufe, aktuelle Kontakte, Median-Verweildauer, Haenger.
	 *
	 * @param array<string, mixed> $report Bericht.
	 * @return void
	 */
	private function render_funnel_table( array $report ): void {
		echo '<h2>' . esc_html__( 'Pipeline', 'kurato' ) . '</h2>';
		echo '<table class="wp-list-table widefat fixed striped">';
		echo '<thead><tr>';
		echo '<th scope="col">' . esc_html__( 'Stufe', 'kurato' ) . '</th>';
		echo '<th scope="col">' . esc_html__( 'Kontakte aktuell', 'kurato' ) . '</th>';
		echo '<th scope="col">' . esc_html__( 'Median-Verweildauer', 'kurato' ) . '</th>';
		echo '<th scope="col">' . esc_html(
			sprintf(
				/* translators: %d: Schwelle in Tagen */
				__( 'Haenger (> %d Tage ohne Aktivitaet)', 'kurato' ),
				(int) $report['hanging_days']
			)
		) . '</th>';
		echo '</tr></thead><tbody>';

		if ( array() === $report['stages'] ) {
			echo '<tr><td colspan="4">' . esc_html__( 'Es gibt noch keine Pipeline-Stufen.', 'kurato' ) . '</td></tr>';
		}
		foreach ( $report['stages'] as $stage ) {
			echo '<tr>';
			echo '<td><strong>' . esc_html( (string) $stage['label'] ) . '</strong>' . $this->stage_flag( $stage ) . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Flag intern escaped.
			echo '<td>' . esc_html( number_format_i18n( (int) $stage['current'] ) ) . '</td>';
			echo '<td>' . esc_html( $this->format_median( $stage['median_days'] ) ) . '</td>';
			echo '<td>' . esc_html( number_format_i18n( (int) $stage['hanging'] ) ) . '</td>';
			echo '</tr>';
		}
		echo '</tbody></table>';
	}

	/**
	 * Uebergangsraten je aufeinanderfolgendem Stufenpaar, mit ehrlicher Basis.
	 *
	 * @param array<string, mixed> $report Bericht.
	 * @return void
	 */
	private function render_transitions( array $report ): void {
		if ( array() === $report['transitions'] ) {
			return;
		}
		echo '<h2>' . esc_html__( 'Uebergangsraten im Zeitraum', 'kurato' ) . '</h2>';
		echo '<table class="wp-list-table widefat fixed striped" style="max-width:820px;">';
		echo '<thead><tr>';
		echo '<th scope="col">' . esc_html__( 'Uebergang', 'kurato' ) . '</th>';
		echo '<th scope="col">' . esc_html__( 'Weiterbewegt', 'kurato' ) . '</th>';
		echo '<th scope="col">' . esc_html__( 'Rate', 'kurato' ) . '</th>';
		echo '</tr></thead><tbody>';
		foreach ( $report['transitions'] as $t ) {
			$label = sprintf( '%s %s %s', (string) $t['from_label'], "\xe2\x86\x92", (string) $t['to_label'] );
			echo '<tr>';
			echo '<td>' . esc_html( $label ) . '</td>';
			echo '<td>' . esc_html(
				sprintf(
					/* translators: 1: weiterbewegt, 2: Basis */
					__( '%1$d von %2$d', 'kurato' ),
					(int) $t['moved'],
					(int) $t['base']
				)
			) . '</td>';
			echo '<td>' . esc_html( $this->format_rate( $t['rate'] ) ) . '</td>';
			echo '</tr>';
		}
		echo '</tbody></table>';
		echo '<p class="description">' . esc_html__( 'Bezugsgroesse jeder Rate ist die Zahl der Kontakte, die die Ausgangsstufe im Zeitraum erreicht haben. Ohne Basis wird keine Rate ausgewiesen.', 'kurato' ) . '</p>';
	}

	/**
	 * Wirkungszahlen im Zeitraum.
	 *
	 * @param array<string, mixed> $report Bericht.
	 * @return void
	 */
	private function render_impact( array $report ): void {
		$impact = $report['impact'];
		echo '<h2>' . esc_html__( 'Wirkung im Zeitraum', 'kurato' ) . '</h2>';
		echo '<ul class="hs-muted" style="margin-left:16px;list-style:disc;">';
		echo '<li>' . esc_html( sprintf( /* translators: %s: Anzahl */ __( 'Buchungen: %s', 'kurato' ), number_format_i18n( (int) $impact['bookings'] ) ) ) . '</li>';
		echo '<li>' . esc_html( sprintf( /* translators: %s: Anzahl */ __( 'Bestaetigte Abos: %s', 'kurato' ), number_format_i18n( (int) $impact['subscriptions_confirmed'] ) ) ) . '</li>';
		echo '<li>' . esc_html( sprintf( /* translators: %s: Anzahl */ __( 'Versendete Kampagnen: %s', 'kurato' ), number_format_i18n( (int) $impact['campaigns_sent'] ) ) ) . '</li>';
		echo '</ul>';
		echo '<p class="description">' . esc_html(
			sprintf(
				/* translators: %s: Gesamtzahl Kontakte */
				__( 'Bezugsgroesse: %s Kontakte insgesamt.', 'kurato' ),
				number_format_i18n( (int) $impact['contacts_total'] )
			)
		) . '</p>';
	}

	// --- Empfehlungen ------------------------------------------------------

	/**
	 * Rendert bis zu MAX_CARDS aktive Empfehlungskarten. Jede gezeigte Empfehlung
	 * wird einmal je Kontext protokolliert (record_once); bereits entschiedene
	 * (angenommene/verworfene) Empfehlungen werden nicht erneut gezeigt.
	 *
	 * @return void
	 */
	private function render_recommendations(): void {
		$hans = Core::instance()->hans();
		if ( ! $hans->is_enabled() ) {
			return;
		}

		$shown = 0;
		$html  = '';
		foreach ( $this->engine->evaluate() as $candidate ) {
			if ( $shown >= self::MAX_CARDS ) {
				break;
			}
			$id = $this->recommendations->record_once( (string) $candidate['rule_key'], (string) $candidate['context_hash'] );
			if ( $id <= 0 ) {
				continue; // Bereits entschieden oder Fehler -> nicht zeigen.
			}
			$html .= $this->render_card( $id, $candidate, $hans );
			$shown++;
		}

		if ( '' === $html ) {
			return;
		}

		echo '<h2>' . esc_html__( 'Vorschlaege von Hans', 'kurato' ) . '</h2>';
		echo '<div class="hs-hans-cards">';
		echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Karten intern escaped.
		echo '</div>';
	}

	/**
	 * Rendert eine Empfehlungskarte im Hans-Stil mit Aktion + "Nicht mehr
	 * vorschlagen". Beide Wege laufen ueber admin-post mit Nonce.
	 *
	 * @param int                     $id        Datensatz-ID der Empfehlung.
	 * @param array<string, mixed>    $candidate Kandidat.
	 * @param \Hafenstudios\Core\Hans\Hans $hans Hans-Framework (fuer das Motiv).
	 * @return string
	 */
	private function render_card( int $id, array $candidate, $hans ): string {
		$image  = $hans->image_url( 30 );
		$action = is_array( $candidate['action'] ) ? $candidate['action'] : array();
		$intent = (string) ( $action['intent'] ?? '' );
		$label  = (string) ( $action['label'] ?? __( 'Ansehen', 'kurato' ) );

		$post_url = esc_url( admin_url( 'admin-post.php' ) );

		$out  = '<div class="hs-hans-card hs-hans-card--info" role="note" style="margin-bottom:12px;">';
		if ( '' !== $image ) {
			$out .= '<img class="hs-hans-card__img" src="' . esc_url( $image ) . '" alt="" width="72" height="72" loading="lazy" />';
		}
		$out .= '<div class="hs-hans-card__body">';
		$out .= '<p class="hs-hans-card__title">' . esc_html( (string) $candidate['title'] ) . '</p>';
		$out .= '<p class="hs-hans-card__text">' . esc_html( (string) $candidate['body'] ) . '</p>';

		$out .= '<p style="margin:8px 0 0;">';
		// Aktion: markiert accepted und leitet zur Zielseite.
		$out .= '<form method="post" action="' . $post_url . '" style="display:inline-block;margin-right:8px;">';
		$out .= '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_ACCEPT ) . '" />';
		$out .= '<input type="hidden" name="reco" value="' . esc_attr( (string) $id ) . '" />';
		$out .= '<input type="hidden" name="intent" value="' . esc_attr( $intent ) . '" />';
		$out .= wp_nonce_field( self::ACTION_ACCEPT . '_' . $id, '_wpnonce', true, false );
		$out .= '<button type="submit" class="button button-primary">' . esc_html( $label ) . '</button>';
		$out .= '</form>';

		// Nicht mehr vorschlagen: markiert dismissed.
		$out .= '<form method="post" action="' . $post_url . '" style="display:inline-block;">';
		$out .= '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_DISMISS ) . '" />';
		$out .= '<input type="hidden" name="reco" value="' . esc_attr( (string) $id ) . '" />';
		$out .= wp_nonce_field( self::ACTION_DISMISS . '_' . $id, '_wpnonce', true, false );
		$out .= '<button type="submit" class="button-link hs-muted">' . esc_html__( 'Nicht mehr vorschlagen', 'kurato' ) . '</button>';
		$out .= '</form>';
		$out .= '</p>';

		$out .= '</div></div>';
		return $out;
	}

	/**
	 * Handler "Annehmen": markiert die Empfehlung accepted und leitet zur Zielseite.
	 *
	 * @return void
	 */
	public function handle_accept(): void {
		$this->guard_view();
		$id = $this->posted_reco_id();
		check_admin_referer( self::ACTION_ACCEPT . '_' . $id );

		if ( $id > 0 ) {
			$this->recommendations->decide( $id, 'accepted' );
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce oben geprueft.
		$intent = isset( $_POST['intent'] ) ? sanitize_key( wp_unslash( $_POST['intent'] ) ) : '';
		wp_safe_redirect( $this->intent_url( $intent ) );
		exit;
	}

	/**
	 * Handler "Nicht mehr vorschlagen": markiert die Empfehlung dismissed.
	 *
	 * @return void
	 */
	public function handle_dismiss(): void {
		$this->guard_view();
		$id = $this->posted_reco_id();
		check_admin_referer( self::ACTION_DISMISS . '_' . $id );

		if ( $id > 0 ) {
			$this->recommendations->decide( $id, 'dismissed' );
		}

		wp_safe_redirect( add_query_arg( array( 'page' => self::SLUG, 'msg' => 'dismissed' ), admin_url( 'admin.php' ) ) );
		exit;
	}

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

	/**
	 * Loest eine Aktions-Absicht in eine bestehende Admin-URL auf (H2 baut keine
	 * neuen Flows). Unbekannte Absicht faellt auf die Kontaktliste zurueck.
	 *
	 * @param string $intent Absicht.
	 * @return string
	 */
	private function intent_url( string $intent ): string {
		$map = array(
			'create_campaign' => CampaignsPage::SLUG,
			'view_contacts'   => Menu::MENU_SLUG,
			'view_lists'      => ListsPage::SLUG,
		);
		$slug = $map[ $intent ] ?? Menu::MENU_SLUG;
		return add_query_arg( array( 'page' => $slug ), admin_url( 'admin.php' ) );
	}

	/**
	 * Angeforderter Zeitraum aus der URL (nur Auswahl, kein Schreibzugriff).
	 *
	 * @return int
	 */
	private function requested_days(): int {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nur Auswahl des Zeitraums.
		return isset( $_GET['range'] ) ? absint( wp_unslash( $_GET['range'] ) ) : FunnelAnalytics::RANGES[0];
	}

	/**
	 * Kennzeichnet Abschluss-Stufen (gewonnen/verloren) als kleines Schild.
	 *
	 * @param array<string, mixed> $stage Stufe.
	 * @return string
	 */
	private function stage_flag( array $stage ): string {
		if ( ! empty( $stage['is_won'] ) ) {
			return ' <span class="hs-shield hs-shield--green">' . esc_html__( 'Gewonnen', 'kurato' ) . '</span>';
		}
		if ( ! empty( $stage['is_lost'] ) ) {
			return ' <span class="hs-shield hs-shield--grey">' . esc_html__( 'Verloren', 'kurato' ) . '</span>';
		}
		return '';
	}

	/**
	 * Formatiert die Median-Verweildauer (null = keine Aussage).
	 *
	 * @param float|null $median Tage.
	 * @return string
	 */
	private function format_median( ?float $median ): string {
		if ( null === $median ) {
			return __( 'keine Daten', 'kurato' );
		}
		return sprintf(
			/* translators: %s: Tage (Median) */
			_n( '%s Tag', '%s Tage', (int) round( $median ), 'kurato' ),
			number_format_i18n( $median, 1 )
		);
	}

	/**
	 * Formatiert eine Uebergangsrate (null = keine Basis).
	 *
	 * @param float|null $rate Anteil 0..1.
	 * @return string
	 */
	private function format_rate( ?float $rate ): string {
		if ( null === $rate ) {
			return __( 'keine Basis', 'kurato' );
		}
		return number_format_i18n( $rate * 100, 1 ) . ' %';
	}

	/**
	 * Statusmeldung nach einem 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 ( 'dismissed' === $msg ) {
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Der Vorschlag wird nicht mehr angezeigt.', 'kurato' ) . '</p></div>';
		}
	}

	/**
	 * Cap-Wache.
	 *
	 * @return void
	 */
	private function guard_view(): void {
		if ( ! current_user_can( 'hs_view_contacts' ) ) {
			wp_die( esc_html__( 'Sie haben keine Berechtigung fuer diese Aktion.', 'kurato' ) );
		}
	}

	/**
	 * Empfehlungs-ID aus dem POST.
	 *
	 * @return int
	 */
	private function posted_reco_id(): int {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- ID nur zur Nonce-Bildung; Nonce wird direkt danach geprueft.
		return isset( $_POST['reco'] ) ? absint( wp_unslash( $_POST['reco'] ) ) : 0;
	}
}
