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

namespace Hafenstudios\Kurato\Admin;

use Hafenstudios\Core\Core;
use Hafenstudios\Kurato\Db\ContactRepository;
use Hafenstudios\Kurato\Db\ListRepository;
use Hafenstudios\Kurato\Db\StageRepository;
use Hafenstudios\Kurato\Db\SubscriptionRepository;

defined( 'ABSPATH' ) || exit;

/**
 * CSV-Import (Etappe K4a).
 *
 * Legt Kontakte aus einer CSV an (Dedup ueber E-Mail) und traegt sie optional in
 * eine Liste ein. Import-Policy (Inhaber-Entscheidung): Der Betreiber erklaert per
 * Checkbox, dass gueltige Einwilligungen vorliegen; das wird im Consent-Log
 * protokolliert und das Abo als "confirmed" gesetzt. Ohne Bestaetigung landen die
 * Abos auf "pending" (kein Nachweis). Synchron mit Zeilenlimit; groessere Importe
 * folgen mit der Queue in K4b.
 */
final class ImportPage {

	public const SLUG          = 'kurato-import';
	public const ACTION_IMPORT = 'kurato_import_csv';

	private const MAX_ROWS = 2000;

	private ContactRepository $contacts;

	private ListRepository $lists;

	private SubscriptionRepository $subscriptions;

	private StageRepository $stages;

	/**
	 * @param ContactRepository      $contacts      Kontakt-Repository.
	 * @param ListRepository         $lists         Listen-Repository.
	 * @param SubscriptionRepository $subscriptions Abo-Repository.
	 * @param StageRepository        $stages        Stufen-Repository.
	 */
	public function __construct( ContactRepository $contacts, ListRepository $lists, SubscriptionRepository $subscriptions, StageRepository $stages ) {
		$this->contacts      = $contacts;
		$this->lists         = $lists;
		$this->subscriptions = $subscriptions;
		$this->stages        = $stages;
	}

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

	/**
	 * Rendert die Import-Seite.
	 *
	 * @return void
	 */
	public function render(): void {
		if ( ! current_user_can( 'hs_manage_contacts' ) ) {
			wp_die( esc_html__( 'Sie haben keine Berechtigung, Kontakte zu importieren.', 'kurato' ) );
		}

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

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nur Anzeige der Ergebniszahlen nach Redirect.
		if ( isset( $_GET['imported'] ) ) {
			$imported = absint( wp_unslash( $_GET['imported'] ) );
			$skipped  = isset( $_GET['skipped'] ) ? absint( wp_unslash( $_GET['skipped'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			echo '<div class="notice notice-success is-dismissible"><p>';
			printf(
				/* translators: 1: importierte Zeilen, 2: uebersprungene Zeilen */
				esc_html__( '%1$d Kontakte importiert, %2$d Zeilen uebersprungen.', 'kurato' ),
				(int) $imported,
				(int) $skipped
			);
			echo '</p></div>';
		}

		echo '<p>' . esc_html__( 'Laden Sie eine CSV-Datei hoch. Die erste Zeile muss die Spaltennamen enthalten. Erkannt werden: email, name (oder first_name und last_name), phone.', 'kurato' ) . '</p>';

		echo '<form method="post" enctype="multipart/form-data" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" class="hs-kurato-import-form">';
		echo '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_IMPORT ) . '" />';
		wp_nonce_field( self::ACTION_IMPORT );

		echo '<p><label>' . esc_html__( 'CSV-Datei', 'kurato' ) . '<br /><input type="file" name="csv" accept=".csv,text/csv" required /></label></p>';

		echo '<p><label>' . esc_html__( 'In Liste eintragen', 'kurato' ) . '<br /><select name="list_id">';
		echo '<option value="0">' . esc_html__( 'Keine Liste (nur Kontakte anlegen)', 'kurato' ) . '</option>';
		foreach ( $this->lists->all() as $list ) {
			printf( '<option value="%1$d">%2$s</option>', (int) $list['id'], esc_html( $list['label'] ) );
		}
		echo '</select></label></p>';

		echo '<p><label><input type="checkbox" name="consent_declared" value="1" /> ';
		echo esc_html__( 'Ich bestaetige, dass fuer diese Kontakte gueltige Einwilligungen zum Erhalt von E-Mails vorliegen.', 'kurato' );
		echo '</label><br /><span class="hs-muted">' . esc_html__( 'Ohne Bestaetigung werden die Abos als "wartet auf Bestaetigung" angelegt und erhalten keine Mailings.', 'kurato' ) . '</span></p>';

		echo '<p><button type="submit" class="button button-primary">' . esc_html__( 'Import starten', 'kurato' ) . '</button></p>';
		echo '</form>';
		echo '</div>';
	}

	/**
	 * Verarbeitet den Upload (admin-post).
	 *
	 * @return void
	 */
	public function handle_import(): void {
		if ( ! current_user_can( 'hs_manage_contacts' ) ) {
			wp_die( esc_html__( 'Keine Berechtigung.', 'kurato' ) );
		}
		check_admin_referer( self::ACTION_IMPORT );

		$list_id  = isset( $_POST['list_id'] ) ? absint( wp_unslash( $_POST['list_id'] ) ) : 0;
		$declared = ! empty( $_POST['consent_declared'] );

		$path = $this->validated_upload_path();
		if ( '' === $path ) {
			$this->redirect( 0, 0 );
		}

		list( $imported, $skipped ) = $this->process_file( $path, $list_id, $declared );
		$this->redirect( $imported, $skipped );
	}

	/**
	 * Prueft den Upload und liefert den temporaeren Pfad (oder Leerstring).
	 *
	 * @return string
	 */
	private function validated_upload_path(): string {
		if ( ! isset( $_FILES['csv'] ) || ! is_array( $_FILES['csv'] ) ) {
			return '';
		}
		$error = isset( $_FILES['csv']['error'] ) ? (int) $_FILES['csv']['error'] : UPLOAD_ERR_NO_FILE;
		if ( UPLOAD_ERR_OK !== $error ) {
			return '';
		}
		$tmp  = isset( $_FILES['csv']['tmp_name'] ) ? sanitize_text_field( $_FILES['csv']['tmp_name'] ) : '';
		$name = isset( $_FILES['csv']['name'] ) ? sanitize_file_name( wp_unslash( $_FILES['csv']['name'] ) ) : '';
		$size = isset( $_FILES['csv']['size'] ) ? (int) $_FILES['csv']['size'] : 0;

		if ( '' === $tmp || ! is_uploaded_file( $tmp ) || $size <= 0 || $size > 5 * MB_IN_BYTES ) {
			return '';
		}
		$ext = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );
		if ( 'csv' !== $ext && 'txt' !== $ext ) {
			return '';
		}
		return $tmp;
	}

	/**
	 * Liest die CSV und legt Kontakte (und optional Abos) an.
	 *
	 * @param string $path     Dateipfad.
	 * @param int    $list_id  Ziel-Liste (0 = keine).
	 * @param bool   $declared Einwilligung selbst erklaert?
	 * @return array{0:int,1:int} [importiert, uebersprungen]
	 */
	private function process_file( string $path, int $list_id, bool $declared ): array {
		$handle = fopen( $path, 'r' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
		if ( false === $handle ) {
			return array( 0, 0 );
		}

		$header = fgetcsv( $handle );
		$map    = $this->column_map( is_array( $header ) ? $header : array() );
		if ( ! isset( $map['email'] ) ) {
			fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
			return array( 0, 0 );
		}

		$stage_id = $this->stages->first_stage_id();
		$imported = 0;
		$skipped  = 0;
		$rows     = 0;

		while ( ( $row = fgetcsv( $handle ) ) !== false ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
			if ( ++$rows > self::MAX_ROWS ) {
				break;
			}
			$email = isset( $map['email'], $row[ $map['email'] ] ) ? sanitize_email( trim( (string) $row[ $map['email'] ] ) ) : '';
			if ( '' === $email ) {
				$skipped++;
				continue;
			}

			$contact_id = $this->upsert_contact( $email, $row, $map, $stage_id );
			if ( $contact_id <= 0 ) {
				$skipped++;
				continue;
			}

			if ( $list_id > 0 ) {
				$this->subscribe( $contact_id, $email, $list_id, $declared );
			}
			$imported++;
		}

		fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
		return array( $imported, $skipped );
	}

	/**
	 * Legt einen Kontakt an oder fuellt leere Felder eines bestehenden auf.
	 *
	 * @param string               $email    E-Mail.
	 * @param array<int, string>   $row      CSV-Zeile.
	 * @param array<string, int>   $map      Spaltenzuordnung.
	 * @param int                  $stage_id Erste Pipeline-Stufe.
	 * @return int Kontakt-ID.
	 */
	private function upsert_contact( string $email, array $row, array $map, int $stage_id ): int {
		$fields = array(
			'display_name' => $this->cell( $row, $map, 'name' ),
			'first_name'   => $this->cell( $row, $map, 'first_name' ),
			'last_name'    => $this->cell( $row, $map, 'last_name' ),
			'phone'        => $this->cell( $row, $map, 'phone' ),
		);

		$existing = $this->contacts->find_by_email( $email );
		if ( null !== $existing ) {
			$patch = array();
			foreach ( $fields as $key => $value ) {
				if ( '' !== $value && '' === (string) ( $existing[ $key ] ?? '' ) ) {
					$patch[ $key ] = $value;
				}
			}
			if ( array() !== $patch ) {
				$this->contacts->update( (int) $existing['id'], $patch );
			}
			return (int) $existing['id'];
		}

		return $this->contacts->insert(
			array_merge(
				$fields,
				array(
					'email'    => $email,
					'stage_id' => $stage_id > 0 ? $stage_id : null,
					'source'   => 'import',
				)
			)
		);
	}

	/**
	 * Legt ein Abo an; bei Selbsterklaerung confirmed mit Consent-Nachweis.
	 *
	 * @param int    $contact_id Kontakt-ID.
	 * @param string $email      E-Mail (fuer den Consent-Nachweis).
	 * @param int    $list_id    Liste.
	 * @param bool   $declared   Einwilligung erklaert?
	 * @return void
	 */
	private function subscribe( int $contact_id, string $email, int $list_id, bool $declared ): void {
		$consent_id = 0;
		if ( $declared ) {
			$consent_id = (int) Core::instance()->consent()->record(
				array(
					'source'        => 'kurato:import',
					'subject_email' => $email,
					'purpose'       => 'newsletter',
					'consent_text'  => __( 'Beim CSV-Import hat der Betreiber erklaert, dass eine gueltige Einwilligung vorliegt.', 'kurato' ),
					'text_version'  => '1',
					'reference'     => 'import',
				)
			);
		}
		$this->subscriptions->subscribe( $list_id, $contact_id, $declared ? 'confirmed' : 'pending', 'import', $consent_id );
	}

	/**
	 * Erkennt die Spaltenpositionen aus der Kopfzeile.
	 *
	 * @param array<int, string> $header Kopfzeile.
	 * @return array<string, int>
	 */
	private function column_map( array $header ): array {
		$aliases = array(
			'email'      => array( 'email', 'e-mail', 'mail', 'e-mail-adresse', 'email address' ),
			'name'       => array( 'name', 'display_name', 'anzeigename', 'full name' ),
			'first_name' => array( 'first_name', 'firstname', 'vorname', 'first name' ),
			'last_name'  => array( 'last_name', 'lastname', 'nachname', 'last name' ),
			'phone'      => array( 'phone', 'telefon', 'tel', 'phone number', 'telefonnummer' ),
		);
		$map = array();
		foreach ( $header as $index => $label ) {
			$key = strtolower( trim( (string) $label ) );
			foreach ( $aliases as $field => $options ) {
				if ( ! isset( $map[ $field ] ) && in_array( $key, $options, true ) ) {
					$map[ $field ] = (int) $index;
				}
			}
		}
		return $map;
	}

	/**
	 * Liest eine Zelle sicher.
	 *
	 * @param array<int, string> $row   Zeile.
	 * @param array<string, int> $map   Spaltenzuordnung.
	 * @param string             $field Feldname.
	 * @return string
	 */
	private function cell( array $row, array $map, string $field ): string {
		if ( ! isset( $map[ $field ], $row[ $map[ $field ] ] ) ) {
			return '';
		}
		return sanitize_text_field( trim( (string) $row[ $map[ $field ] ] ) );
	}

	/**
	 * Leitet zurueck mit Ergebniszahlen.
	 *
	 * @param int $imported Importiert.
	 * @param int $skipped  Uebersprungen.
	 * @return void
	 */
	private function redirect( int $imported, int $skipped ): void {
		wp_safe_redirect(
			add_query_arg(
				array( 'page' => self::SLUG, 'imported' => $imported, 'skipped' => $skipped ),
				admin_url( 'admin.php' )
			)
		);
		exit;
	}
}
