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

namespace Hafenstudios\Kurato\Campaign;

use Hafenstudios\Core\Mail\Message;
use Hafenstudios\Kurato\Db\CampaignRepository;
use Hafenstudios\Kurato\Db\ContactRepository;
use Hafenstudios\Kurato\Mail\EmailRenderer;
use Hafenstudios\Kurato\Mail\LinkToken;
use Hafenstudios\Kurato\Mail\MailBuilder;
use Hafenstudios\Kurato\Mail\TrackingToken;
use Hafenstudios\Kurato\Preferences\PreferenceCenter;

defined( 'ABSPATH' ) || exit;

/**
 * Baut aus einer Queue-Zeile die fertige, personalisierte Marketing-Mail
 * (K4-Konzept Abschnitte 1, 6, 7, 8).
 *
 * Guard-Reihenfolge (jede liefert null = Zeile wird vom QueueProcessor
 * uebersprungen):
 * 1. Kampagne fehlt oder ist nicht mehr `sending` (pausiert/abgebrochen/fertig).
 * 2. Kontakt fehlt oder ist supprimiert (email_status != 'active') - finale
 *    Suppression-Pruefung beim tatsaechlichen Send.
 *
 * Sonst: Blocks rendern (do_blocks), Betreff aus dem Post-Title, Abmeldelink
 * signieren, Kontext zusammenstellen, ueber den EmailRenderer in das
 * Basistemplate wrappen. Die Nachricht traegt die Pflicht-Header
 * `List-Unsubscribe` und `List-Unsubscribe-Post` (RFC 8058).
 *
 * **Doppeltes Consent-Gate fuer Tracking** (K4-Konzept Abschnitt 7): Open-Pixel
 * und Link-Umschreibung werden NUR eingefuegt, wenn der Site-Schalter
 * (`$tracking_enabled`) an ist UND der Kontakt `tracking_consent = 1` hat. Ohne
 * beides bleiben die Original-Links stehen und es gibt kein Pixel. Abmelde- und
 * Preference-Link werden NIE umgeschrieben (sie muessen direkt funktionieren);
 * das ergibt sich daraus, dass nur der bereits gerenderte Inhalt (do_blocks)
 * umgeschrieben wird, waehrend Footer/Abmeldelink erst danach vom EmailRenderer
 * ueber Platzhalter angehaengt werden.
 */
final class CampaignMailBuilder implements MailBuilder {

	private CampaignRepository $campaigns;

	private ContactRepository $contacts;

	private EmailRenderer $renderer;

	private LinkToken $token;

	private string $from_name;

	private string $from_email;

	private string $sender_address;

	private bool $tracking_enabled;

	private ?TrackingToken $tracking_token;

	private ?LinkRegistry $registry;

	/**
	 * @param CampaignRepository $campaigns        Kampagnen-Repository.
	 * @param ContactRepository  $contacts         Kontakt-Repository.
	 * @param EmailRenderer      $renderer         HTML-Renderer.
	 * @param LinkToken          $token            Signierer fuer Abmeldelinks.
	 * @param string             $from_name        Absendername (Setting mail.sender.from_name).
	 * @param string             $from_email       Absender-Adresse (Setting mail.sender.from_email).
	 * @param string             $sender_address   Postalische Absenderzeile fuer den Footer (optional).
	 * @param bool               $tracking_enabled Site-Schalter Tracking (Default aus).
	 * @param TrackingToken|null $tracking_token   Tracking-Signierer (null = kein Tracking).
	 * @param LinkRegistry|null  $registry         Link-Registry (null = kein Tracking).
	 */
	public function __construct(
		CampaignRepository $campaigns,
		ContactRepository $contacts,
		EmailRenderer $renderer,
		LinkToken $token,
		string $from_name = '',
		string $from_email = '',
		string $sender_address = '',
		bool $tracking_enabled = false,
		?TrackingToken $tracking_token = null,
		?LinkRegistry $registry = null
	) {
		$this->campaigns        = $campaigns;
		$this->contacts         = $contacts;
		$this->renderer         = $renderer;
		$this->token            = $token;
		$this->from_name        = $from_name;
		$this->from_email       = $from_email;
		$this->sender_address   = $sender_address;
		$this->tracking_enabled = $tracking_enabled;
		$this->tracking_token   = $tracking_token;
		$this->registry         = $registry;
	}

	/**
	 * @param array<string, mixed> $queue_row Zeile aus hs_mail_queue.
	 * @return Message|null
	 */
	public function build( array $queue_row ): ?Message {
		$campaign_id = (int) ( $queue_row['campaign_id'] ?? 0 );
		$campaign    = $this->campaigns->get( $campaign_id );
		if ( null === $campaign || 'sending' !== (string) ( $campaign['status'] ?? '' ) ) {
			return null;
		}

		$contact_id = (int) ( $queue_row['contact_id'] ?? 0 );
		$contact    = $this->contacts->get( $contact_id );
		if ( null === $contact || 'active' !== (string) ( $contact['email_status'] ?? 'active' ) ) {
			return null;
		}

		$to_email = sanitize_email( (string) ( $queue_row['to_email'] ?? ( $contact['email'] ?? '' ) ) );
		if ( '' === $to_email ) {
			return null;
		}

		$post_id    = (int) ( $campaign['post_id'] ?? 0 );
		$inner_html = do_blocks( (string) get_post_field( 'post_content', $post_id ) );
		$subject    = (string) get_the_title( $post_id );

		// Quelle fuer die Klartext-Alternative: der Inhalt VOR der Tracking-
		// Umschreibung, damit im Text die echten Ziel-URLs stehen (kein
		// Redirect-Link) und kein Zaehlpixel auftaucht.
		$text_source = $inner_html;

		// Doppeltes Consent-Gate: nur bei Site-Schalter AN und Kontakt-Consent
		// werden Links umgeschrieben und ein Open-Pixel eingefuegt. Beides
		// geschieht auf dem Inhalt (do_blocks), BEVOR der EmailRenderer den
		// Footer mit Abmelde-/Preference-Link ueber Platzhalter anhaengt - so
		// bleiben diese Links unangetastet und funktionieren direkt.
		if ( $this->tracking_allowed( $contact ) ) {
			$inner_html  = $this->rewrite_links( $inner_html, $campaign_id, $contact_id, $post_id );
			$inner_html .= $this->open_pixel( $campaign_id, $contact_id );
		}

		$unsubscribe_url = add_query_arg(
			'token',
			$this->token->create( $contact_id, 0, LinkToken::PURPOSE_UNSUBSCRIBE ),
			rest_url( 'kurato/v1/unsubscribe' )
		);

		// Preference Center: zweckgebundener Token (kein One-Click-Abmelde-Token).
		$preferences_url = PreferenceCenter::url( PreferenceCenter::token_for( $this->token, $contact_id ) );

		$context = array(
			'first_name'      => (string) ( $contact['first_name'] ?? '' ),
			'display_name'    => (string) ( $contact['display_name'] ?? '' ),
			'email'           => $to_email,
			'unsubscribe_url' => $unsubscribe_url,
			'preferences_url' => $preferences_url,
			'sender_name'     => $this->from_name,
			'sender_address'  => $this->sender_address,
			'subject'         => $subject,
		);

		$message            = new Message( $to_email, $subject, $this->renderer->render_html( $inner_html, $context ) );
		$message->is_html   = true;
		// Multipart-Alternative (Zustellbarkeit, K4c-2): Klartext aus demselben
		// Inhalt inkl. Pflicht-Footer (EmailRenderer::render_text).
		$message->text_body = $this->renderer->render_text( $text_source, $context );

		if ( '' !== $this->from_email ) {
			$message->from_email = $this->from_email;
			$message->from_name  = $this->from_name;
		}

		// RFC 8058: List-Unsubscribe plus One-Click-POST (Google/Yahoo-Pflicht 2025).
		$message->headers['List-Unsubscribe']      = '<' . $unsubscribe_url . '>';
		$message->headers['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click';

		return $message;
	}

	/**
	 * Doppeltes Consent-Gate: Tracking nur bei Site-Schalter AN, verdrahteten
	 * Tracking-Diensten UND Kontakt-Einwilligung.
	 *
	 * @param array<string, mixed> $contact Kontaktzeile.
	 * @return bool
	 */
	private function tracking_allowed( array $contact ): bool {
		if ( ! $this->tracking_enabled || null === $this->tracking_token || null === $this->registry ) {
			return false;
		}
		return 1 === (int) ( $contact['tracking_consent'] ?? 0 );
	}

	/**
	 * Schreibt absolute http(s)-Links auf den Click-Redirect um und hinterlegt die
	 * Ziel-URLs in der kampagnenbezogenen Link-Registry (kein Open-Redirect). Nicht
	 * absolute Links, `mailto:`-Links und die Platzhalter `{{unsubscribe_url}}` /
	 * `{{preferences_url}}` (noch nicht aufgeloest) bleiben unberuehrt.
	 *
	 * @param string $html        Gerenderter Inhalt (do_blocks).
	 * @param int    $campaign_id Kampagne.
	 * @param int    $contact_id  Kontakt.
	 * @param int    $post_id     Inhalts-Post (Registry-Ablage).
	 * @return string
	 */
	private function rewrite_links( string $html, int $campaign_id, int $contact_id, int $post_id ): string {
		return (string) preg_replace_callback(
			'/href=(["\'])(https?:\/\/[^"\']+)\1/i',
			function ( array $m ) use ( $campaign_id, $contact_id, $post_id ): string {
				// In HTML-Attributen kodierte Ampersands zurueckwandeln, damit das
				// spaetere 302 auf die echte Ziel-URL geht.
				$url   = str_replace( '&amp;', '&', $m[2] );
				$index = $this->registry->index_for( $post_id, $url );
				if ( $index < 0 ) {
					return $m[0];
				}
				$redirect = add_query_arg(
					'token',
					$this->tracking_token->create_click( $campaign_id, $contact_id, $index ),
					rest_url( 'kurato/v1/t/click' )
				);
				return 'href=' . $m[1] . esc_url( $redirect ) . $m[1];
			},
			$html
		);
	}

	/**
	 * Baut das 1x1-Open-Pixel als HTML-img mit signiertem Token.
	 *
	 * @param int $campaign_id Kampagne.
	 * @param int $contact_id  Kontakt.
	 * @return string
	 */
	private function open_pixel( int $campaign_id, int $contact_id ): string {
		$src = add_query_arg(
			'token',
			$this->tracking_token->create_open( $campaign_id, $contact_id ),
			rest_url( 'kurato/v1/t/open' )
		);
		return '<img src="' . esc_url( $src ) . '" alt="" width="1" height="1" style="display:block;width:1px;height:1px;border:0;overflow:hidden;" />';
	}
}
