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

namespace Hafenstudios\Wellenbrecher\Admin;

use Hafenstudios\Core\Settings\Settings;
use Hafenstudios\Core\Support\Secrets;
use Hafenstudios\Wellenbrecher\Pro\Disposable\DisposableUpdater;
use Hafenstudios\Wellenbrecher\Pro\GeoIp\GeoDatabase;
use Hafenstudios\Wellenbrecher\Pro\GeoIp\GeoDownloader;
use Hafenstudios\Wellenbrecher\Support\Uploads;

defined( 'ABSPATH' ) || exit;

/**
 * Seite "Externe Dienste" (Pro, W5b). Sammelt die vier Opt-in-Externa an EINER
 * gut sichtbaren Stelle mit Datenfluss-Hinweisen (welche Daten wohin, AVV-Hinweis).
 * JEDES Feature ist einzeln Opt-in, Default AUS - ohne Opt-in verlaesst kein
 * Byte die Site.
 *
 * - DB-IP GeoIP/ASN: Opt-in-Download (kein Besucher-Byte geht raus), Pflicht-
 *   Attribution "IP Geolocation by DB-IP" (CC BY 4.0).
 * - AbuseIPDB: BYO-Key (verschluesselt via Core-Secrets); die gepruefte IP geht
 *   an AbuseIPDB (USA).
 * - KI-Zweitmeinung: Grenzfall-Inhalt geht an den in der Core-KI eingerichteten
 *   Anbieter.
 * - Disposable-Auto-Update: laedt nur eine oeffentliche Domain-Liste.
 *
 * Serverseitig ueber admin-post (Muster SettingsPage), Cap hs_manage_spam,
 * Sie-Form, keine Em-Dashes.
 */
class ExternaPage {

	public const SLUG = 'wellenbrecher-externa';

	public const ACTION_SAVE       = 'wellenbrecher_externa_save';
	public const ACTION_GEOIP      = 'wellenbrecher_externa_geoip';
	public const ACTION_DISPOSABLE = 'wellenbrecher_externa_disposable';

	/**
	 * Pflicht-Attribution DB-IP Lite (CC BY 4.0).
	 */
	public const DBIP_ATTRIBUTION_TEXT = 'IP Geolocation by DB-IP';
	public const DBIP_ATTRIBUTION_URL  = 'https://db-ip.com';

	/**
	 * Wenn ein Key gespeichert ist, zeigen wir NUR diese Maske, nie den Klartext.
	 */
	public const KEY_MASK = '********';

	protected Settings $settings;

	/**
	 * @param Settings $settings Wellenbrecher-Settings.
	 */
	public function __construct( Settings $settings ) {
		$this->settings = $settings;
	}

	/**
	 * Registriert die admin-post-Handler.
	 *
	 * @return void
	 */
	public function register(): void {
		add_action( 'admin_post_' . self::ACTION_SAVE, array( $this, 'handle_save' ) );
		add_action( 'admin_post_' . self::ACTION_GEOIP, array( $this, 'handle_geoip_download' ) );
		add_action( 'admin_post_' . self::ACTION_DISPOSABLE, array( $this, 'handle_disposable_update' ) );
	}

	/**
	 * Rendert die Seite.
	 *
	 * @return void
	 */
	public function render(): void {
		if ( ! current_user_can( 'hs_manage_spam' ) ) {
			wp_die( esc_html__( 'Sie haben keine Berechtigung fuer die Wellenbrecher-Einstellungen.', 'wellenbrecher' ) );
		}

		$notice = isset( $_GET['msg'] ) ? sanitize_key( wp_unslash( $_GET['msg'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nur Anzeige nach Redirect.

		echo '<div class="wrap">';
		echo '<h1 class="wp-heading-inline">' . esc_html__( 'Wellenbrecher: Externe Dienste', 'wellenbrecher' ) . '</h1>';
		echo '<hr class="wp-header-end" />';

		$this->render_notice( $notice );

		echo '<p>' . esc_html__( 'Diese vier Zusatzpruefungen holen Daten von aussen oder senden Daten nach aussen. Sie sind alle einzeln abschaltbar und im Auslieferungszustand AUS. Solange Sie nichts einschalten, verlaesst kein Byte Ihre Website. Zu jeder Pruefung steht unten, welche Daten wohin gehen.', 'wellenbrecher' ) . '</p>';

		echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">';
		echo '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_SAVE ) . '" />';
		wp_nonce_field( self::ACTION_SAVE );

		$this->render_geoip_section();
		$this->render_abuseipdb_section();
		$this->render_ai_section();
		$this->render_disposable_section();

		echo '<p><button type="submit" class="button button-primary">' . esc_html__( 'Einstellungen speichern', 'wellenbrecher' ) . '</button></p>';
		echo '</form>';

		echo '</div>';
	}

	/**
	 * Status-/Fehlermeldungen nach Redirect.
	 *
	 * @param string $notice Statuscode.
	 * @return void
	 */
	protected function render_notice( string $notice ): void {
		$messages = array(
			'saved'            => array( 'success', __( 'Einstellungen gespeichert.', 'wellenbrecher' ) ),
			'geoip_ok'         => array( 'success', __( 'DB-IP-Datenbanken wurden heruntergeladen.', 'wellenbrecher' ) ),
			'geoip_fail'       => array( 'error', __( 'Der Download der DB-IP-Datenbanken ist fehlgeschlagen. Eine vorhandene Datenbank bleibt unveraendert. Bitte versuchen Sie es spaeter erneut.', 'wellenbrecher' ) ),
			'disposable_ok'    => array( 'success', __( 'Die Disposable-Liste wurde aktualisiert.', 'wellenbrecher' ) ),
			'disposable_fail'  => array( 'error', __( 'Die Aktualisierung der Disposable-Liste ist fehlgeschlagen. Der bisherige Bestand bleibt erhalten.', 'wellenbrecher' ) ),
		);
		if ( isset( $messages[ $notice ] ) ) {
			list( $type, $text ) = $messages[ $notice ];
			echo '<div class="notice notice-' . esc_attr( $type ) . ' is-dismissible"><p>' . esc_html( $text ) . '</p></div>';
		}
	}

	/**
	 * DB-IP GeoIP/ASN inkl. Pflicht-Attribution und Opt-in-Download.
	 *
	 * @return void
	 */
	protected function render_geoip_section(): void {
		$enabled   = ! empty( $this->settings->get( 'pro.geoip.enabled', false ) );
		$countries = $this->settings->get( 'pro.geoip.countries', array() );
		$countries = is_array( $countries ) ? implode( ', ', array_map( 'strval', $countries ) ) : '';
		$last      = (int) $this->settings->get( 'pro.geoip.last_download', 0 );

		$geo         = new GeoDatabase( Uploads::path( GeoDownloader::COUNTRY_FILE ), Uploads::path( GeoDownloader::ASN_FILE ) );
		$has_country = $geo->has_country();
		$has_asn     = $geo->has_asn();

		echo '<h2>' . esc_html__( 'Herkunft und Rechenzentrum (DB-IP)', 'wellenbrecher' ) . '</h2>';
		echo '<p class="description">' . esc_html__( 'Datenfluss: Fuer diese Pruefung wird einmalig eine Datenbank von DB-IP heruntergeladen und lokal gespeichert. Es wird KEINE Besucher-IP nach aussen gesendet; die Zuordnung Land/Rechenzentrum passiert danach vollstaendig auf Ihrem Server.', 'wellenbrecher' ) . '</p>';

		echo '<table class="form-table" role="presentation"><tbody>';

		echo '<tr><th scope="row">' . esc_html__( 'Aktiv', 'wellenbrecher' ) . '</th><td>';
		echo '<label><input type="checkbox" name="geoip[enabled]" value="1"' . checked( $enabled, true, false ) . ' /> ' . esc_html__( 'Land- und Rechenzentrums-Regeln verwenden', 'wellenbrecher' ) . '</label>';
		echo '<p class="description">' . esc_html__( 'Die Rechenzentrums-Regel (Registrierung/Formular aus einem VPS oder einer Cloud) gibt Zusatzpunkte. Die Land-Regel ist nur aktiv, wenn Sie unten Laender eintragen. Beide blockieren nie allein.', 'wellenbrecher' ) . '</p>';
		echo '</td></tr>';

		echo '<tr><th scope="row"><label for="wb-geoip-countries">' . esc_html__( 'Laender mit Zusatzpunkten', 'wellenbrecher' ) . '</label></th><td>';
		echo '<input type="text" id="wb-geoip-countries" name="geoip[countries]" value="' . esc_attr( $countries ) . '" class="regular-text" placeholder="RU, CN, IN" />';
		echo '<p class="description">' . esc_html__( 'Laendercodes (ISO 3166, zwei Buchstaben), durch Komma getrennt. Leer lassen = Land-Regel aus. Land wird nie hart blockiert, sondern nur mit +10 gewichtet.', 'wellenbrecher' ) . '</p>';
		echo '</td></tr>';

		echo '<tr><th scope="row">' . esc_html__( 'Datenbanken', 'wellenbrecher' ) . '</th><td>';
		echo '<p>';
		echo esc_html__( 'Country:', 'wellenbrecher' ) . ' ' . ( $has_country ? esc_html__( 'vorhanden', 'wellenbrecher' ) : esc_html__( 'fehlt', 'wellenbrecher' ) ) . ' &nbsp; ';
		echo esc_html__( 'ASN:', 'wellenbrecher' ) . ' ' . ( $has_asn ? esc_html__( 'vorhanden', 'wellenbrecher' ) : esc_html__( 'fehlt', 'wellenbrecher' ) );
		if ( $last > 0 ) {
			echo '<br />' . esc_html( sprintf( /* translators: %s: Datum */ __( 'Zuletzt geladen: %s', 'wellenbrecher' ), gmdate( 'Y-m-d H:i', $last ) . ' UTC' ) );
		}
		echo '</p>';
		echo '</td></tr>';

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

		// Attribution (Pflicht, CC BY 4.0) und der Download-Button in einem eigenen Formular.
		echo '<p class="description">';
		echo '<a href="' . esc_url( self::DBIP_ATTRIBUTION_URL ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( self::DBIP_ATTRIBUTION_TEXT ) . '</a> ';
		echo esc_html__( '(DB-IP Lite, Lizenz CC BY 4.0). Diese Namensnennung ist Pflicht und darf nicht entfernt werden.', 'wellenbrecher' );
		echo '</p>';

		echo '<p>';
		echo '<button type="submit" form="wb-geoip-download" class="button">' . esc_html__( 'Datenbanken jetzt herunterladen', 'wellenbrecher' ) . '</button> ';
		echo '<span class="description">' . esc_html__( 'Danach automatisch monatlich, solange diese Pruefung aktiv ist.', 'wellenbrecher' ) . '</span>';
		echo '</p>';

		// Separates Formular fuer den Download (eigene Aktion + Nonce).
		echo '<form id="wb-geoip-download" method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">';
		echo '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_GEOIP ) . '" />';
		wp_nonce_field( self::ACTION_GEOIP );
		echo '</form>';
	}

	/**
	 * AbuseIPDB (BYO-Key, IP geht in die USA).
	 *
	 * @return void
	 */
	protected function render_abuseipdb_section(): void {
		$enabled = ! empty( $this->settings->get( 'pro.abuseipdb.enabled', false ) );
		$has_key = '' !== (string) $this->settings->get( 'pro.abuseipdb.key_encrypted', '' );

		echo '<h2>' . esc_html__( 'AbuseIPDB-Reputationspruefung', 'wellenbrecher' ) . '</h2>';
		echo '<p class="description">' . esc_html__( 'Datenfluss: Nur in Grenzfaellen wird die IP-Adresse des Absenders an AbuseIPDB (USA) uebertragen, um deren Reputationswert abzufragen. Das ist eine Auftragsverarbeitung in einem Drittland; pruefen Sie Ihre Datenschutzerklaerung und schliessen Sie bei Bedarf einen AV-Vertrag mit AbuseIPDB. Ohne eigenen Schluessel und ohne Opt-in wird nichts uebertragen.', 'wellenbrecher' ) . '</p>';
		echo '<p class="description"><a href="https://www.abuseipdb.com/terms" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Nutzungsbedingungen von AbuseIPDB', 'wellenbrecher' ) . '</a></p>';

		echo '<table class="form-table" role="presentation"><tbody>';
		echo '<tr><th scope="row">' . esc_html__( 'Aktiv', 'wellenbrecher' ) . '</th><td>';
		echo '<label><input type="checkbox" name="abuseipdb[enabled]" value="1"' . checked( $enabled, true, false ) . ' /> ' . esc_html__( 'IP-Reputation bei Grenzfaellen abfragen', 'wellenbrecher' ) . '</label>';
		echo '</td></tr>';
		echo '<tr><th scope="row"><label for="wb-abuseipdb-key">' . esc_html__( 'API-Schluessel', 'wellenbrecher' ) . '</label></th><td>';
		echo '<input type="password" id="wb-abuseipdb-key" name="abuseipdb[key]" value="" class="regular-text" autocomplete="new-password" placeholder="' . esc_attr( $has_key ? self::KEY_MASK : '' ) . '" />';
		echo '<p class="description">';
		echo $has_key
			? esc_html__( 'Ein Schluessel ist gespeichert (verschluesselt). Feld leer lassen = behalten; neuen Wert eingeben = ersetzen.', 'wellenbrecher' )
			: esc_html__( 'Ihr persoenlicher AbuseIPDB-Schluessel. Wird verschluesselt gespeichert und nie im Klartext angezeigt.', 'wellenbrecher' );
		echo '</p>';
		echo '</td></tr>';
		echo '</tbody></table>';
	}

	/**
	 * KI-Zweitmeinung (Inhalt geht an den Core-KI-Anbieter).
	 *
	 * @return void
	 */
	protected function render_ai_section(): void {
		$enabled  = ! empty( $this->settings->get( 'pro.ai.enabled', false ) );
		$core_on  = false;
		if ( class_exists( \Hafenstudios\Core\Core::class ) ) {
			$core_on = \Hafenstudios\Core\Core::instance()->ai()->is_enabled();
		}

		echo '<h2>' . esc_html__( 'KI-Zweitmeinung', 'wellenbrecher' ) . '</h2>';
		echo '<p class="description">' . esc_html__( 'Datenfluss: Nur in Grenzfaellen wird der Inhalt des Kommentars oder Formulars (Text, Autor-Name, Autor-Link - NICHT die E-Mail-Adresse) an den KI-Anbieter gesendet, den Sie in den zentralen KI-Einstellungen eingerichtet haben. Wohin genau, haengt von diesem Anbieter ab. Ohne den zentralen KI-Hauptschalter UND dieses Opt-in wird nichts gesendet.', 'wellenbrecher' ) . '</p>';

		echo '<table class="form-table" role="presentation"><tbody>';
		echo '<tr><th scope="row">' . esc_html__( 'Aktiv', 'wellenbrecher' ) . '</th><td>';
		echo '<label><input type="checkbox" name="ai[enabled]" value="1"' . checked( $enabled, true, false ) . ' /> ' . esc_html__( 'Grenzfaelle zusaetzlich von der KI bewerten lassen', 'wellenbrecher' ) . '</label>';
		echo '<p class="description">';
		echo $core_on
			? esc_html__( 'Der zentrale KI-Hauptschalter ist an.', 'wellenbrecher' )
			: esc_html__( 'Hinweis: Der zentrale KI-Hauptschalter ist derzeit AUS. Solange er aus ist, bleibt diese Zweitmeinung wirkungslos. Sie richten den Anbieter in den KI-Einstellungen der Flotte ein.', 'wellenbrecher' );
		echo '</p>';
		echo '</td></tr>';
		echo '</tbody></table>';
	}

	/**
	 * Disposable-Auto-Update (laedt nur eine oeffentliche Domain-Liste).
	 *
	 * @return void
	 */
	protected function render_disposable_section(): void {
		$enabled = ! empty( $this->settings->get( 'pro.disposable.enabled', false ) );
		$last    = (int) $this->settings->get( 'pro.disposable.last_update', 0 );
		$has_file = '' !== Uploads::path( DisposableUpdater::TARGET ) && is_readable( Uploads::path( DisposableUpdater::TARGET ) );

		echo '<h2>' . esc_html__( 'Wegwerf-Adressen automatisch aktualisieren', 'wellenbrecher' ) . '</h2>';
		echo '<p class="description">' . esc_html__( 'Datenfluss: Es wird nur eine oeffentliche Liste bekannter Wegwerf-E-Mail-Domains von GitHub geladen und mit der gebundelten Liste gemischt. Es werden KEINE Besucher- oder Kundendaten uebertragen.', 'wellenbrecher' ) . '</p>';

		echo '<table class="form-table" role="presentation"><tbody>';
		echo '<tr><th scope="row">' . esc_html__( 'Aktiv', 'wellenbrecher' ) . '</th><td>';
		echo '<label><input type="checkbox" name="disposable[enabled]" value="1"' . checked( $enabled, true, false ) . ' /> ' . esc_html__( 'Woechentlich die grosse strict-Liste laden', 'wellenbrecher' ) . '</label>';
		echo '<p class="description">';
		if ( $last > 0 ) {
			echo esc_html( sprintf( /* translators: %s: Datum */ __( 'Zuletzt aktualisiert: %s', 'wellenbrecher' ), gmdate( 'Y-m-d H:i', $last ) . ' UTC' ) );
		} else {
			echo esc_html__( 'Noch nie aktualisiert. Es gilt die gebundelte Liste.', 'wellenbrecher' );
		}
		echo $has_file ? ' ' . esc_html__( '(Zusatzliste vorhanden)', 'wellenbrecher' ) : '';
		echo '</p>';
		echo '</td></tr>';
		echo '</tbody></table>';

		echo '<p>';
		echo '<button type="submit" form="wb-disposable-update" class="button">' . esc_html__( 'Liste jetzt aktualisieren', 'wellenbrecher' ) . '</button>';
		echo '</p>';

		echo '<form id="wb-disposable-update" method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">';
		echo '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_DISPOSABLE ) . '" />';
		wp_nonce_field( self::ACTION_DISPOSABLE );
		echo '</form>';
	}

	/**
	 * Speichert alle vier Abschnitte (admin-post). Plant/entplant dabei die
	 * beiden Crons nach dem Opt-in-Zustand.
	 *
	 * @return void
	 */
	public function handle_save(): void {
		if ( ! current_user_can( 'hs_manage_spam' ) ) {
			wp_die( esc_html__( 'Keine Berechtigung.', 'wellenbrecher' ) );
		}
		check_admin_referer( self::ACTION_SAVE );

		// GeoIP.
		$geoip_on = ! empty( $_POST['geoip']['enabled'] );
		$this->settings->set( 'pro.geoip.enabled', $geoip_on );
		$raw_countries = isset( $_POST['geoip']['countries'] ) ? sanitize_text_field( wp_unslash( $_POST['geoip']['countries'] ) ) : '';
		$this->settings->set( 'pro.geoip.countries', $this->parse_countries( $raw_countries ) );

		// AbuseIPDB.
		$this->settings->set( 'pro.abuseipdb.enabled', ! empty( $_POST['abuseipdb']['enabled'] ) );
		$posted_key = isset( $_POST['abuseipdb']['key'] ) ? trim( (string) wp_unslash( $_POST['abuseipdb']['key'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- API-Key, unten verschluesselt; keine Textbereinigung erwuenscht.
		if ( '' !== $posted_key && self::KEY_MASK !== $posted_key ) {
			$this->settings->set( 'pro.abuseipdb.key_encrypted', Secrets::encrypt( $posted_key ) );
		}

		// KI-Zweitmeinung.
		$this->settings->set( 'pro.ai.enabled', ! empty( $_POST['ai']['enabled'] ) );

		// Disposable-Auto-Update.
		$disposable_on = ! empty( $_POST['disposable']['enabled'] );
		$this->settings->set( 'pro.disposable.enabled', $disposable_on );

		// Crons nach Opt-in-Zustand planen/entplanen.
		$this->sync_cron( GeoDownloader::CRON_HOOK, GeoDownloader::CRON_SCHEDULE, $geoip_on );
		$this->sync_cron( DisposableUpdater::CRON_HOOK, 'weekly', $disposable_on );

		$this->redirect( 'saved' );
	}

	/**
	 * Manueller GeoIP-Download (admin-post).
	 *
	 * @return void
	 */
	public function handle_geoip_download(): void {
		if ( ! current_user_can( 'hs_manage_spam' ) ) {
			wp_die( esc_html__( 'Keine Berechtigung.', 'wellenbrecher' ) );
		}
		check_admin_referer( self::ACTION_GEOIP );

		$result = ( new GeoDownloader( $this->settings ) )->refresh();
		$ok     = ! empty( $result['country']['ok'] ) || ! empty( $result['asn']['ok'] );
		$this->redirect( $ok ? 'geoip_ok' : 'geoip_fail' );
	}

	/**
	 * Manuelles Disposable-Update (admin-post).
	 *
	 * @return void
	 */
	public function handle_disposable_update(): void {
		if ( ! current_user_can( 'hs_manage_spam' ) ) {
			wp_die( esc_html__( 'Keine Berechtigung.', 'wellenbrecher' ) );
		}
		check_admin_referer( self::ACTION_DISPOSABLE );

		$result = ( new DisposableUpdater( $this->settings ) )->refresh();
		$this->redirect( ! empty( $result['ok'] ) ? 'disposable_ok' : 'disposable_fail' );
	}

	/**
	 * Normalisiert eine eingegebene Laenderliste zu gueltigen ISO-Codes
	 * (zwei Grossbuchstaben), eindeutig. Reine, testbare Logik.
	 *
	 * @param string $raw Eingabe (Komma/Leerzeichen/Zeilen getrennt).
	 * @return array<int, string>
	 */
	public function parse_countries( string $raw ): array {
		$parts  = preg_split( '/[\s,;]+/', strtoupper( $raw ) );
		$codes  = array();
		foreach ( (array) $parts as $part ) {
			$part = trim( $part );
			if ( preg_match( '/^[A-Z]{2}$/', $part ) ) {
				$codes[ $part ] = true;
			}
		}
		return array_keys( $codes );
	}

	/**
	 * Plant oder entplant einen Cron-Hook nach dem Opt-in-Zustand.
	 *
	 * @param string $hook     Cron-Hook.
	 * @param string $schedule Wiederholung (weekly | wellenbrecher_monthly).
	 * @param bool   $enabled  Opt-in an?
	 * @return void
	 */
	protected function sync_cron( string $hook, string $schedule, bool $enabled ): void {
		$scheduled = (bool) wp_next_scheduled( $hook );
		if ( $enabled && ! $scheduled ) {
			wp_schedule_event( time() + HOUR_IN_SECONDS, $schedule, $hook );
		} elseif ( ! $enabled && $scheduled ) {
			wp_clear_scheduled_hook( $hook );
		}
	}

	/**
	 * Zurueck zur Seite mit Statusmeldung.
	 *
	 * @param string $msg Statuscode.
	 * @return void
	 */
	protected function redirect( string $msg ): void {
		wp_safe_redirect( add_query_arg( array( 'page' => self::SLUG, 'msg' => $msg ), admin_url( 'admin.php' ) ) );
		exit;
	}
}
