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

namespace Hafenstudios\Kurato\Mail;

use Hafenstudios\Core\Mail\MailDriver;
use Hafenstudios\Kurato\Db\MailEventRepository;
use Hafenstudios\Kurato\Db\QueueRepository;

defined( 'ABSPATH' ) || exit;

/**
 * Verarbeitet einen Queue-Durchlauf (K4-Konzept Abschnitt 6).
 *
 * Ablauf je Minuten-Lauf: haengengebliebene Zeilen zurueckholen, einen Batch
 * (Groesse = Stundenrate/60) claimen, je Zeile die Nachricht bauen und senden,
 * Ergebnis markieren. Permanente Fehler (ungueltige Adresse) werden nicht
 * wiederholt; voruebergehende (Timeout, 5xx) mit Backoff bis MAX_ATTEMPTS.
 *
 * Bei erfolgreichem Versand wird ein `sent`-Ereignis geschrieben, bei einem
 * PERMANENTEN Fehler ein `failed`-Ereignis (K4e-1). Voruebergehende Fehler
 * erzeugen bewusst KEIN Ereignis (das wuerde bei jedem Retry das Log fluten).
 */
final class QueueProcessor {

	private QueueRepository $queue;

	private MailBuilder $builder;

	private ?MailEventRepository $events;

	/**
	 * @param QueueRepository          $queue   Queue-Repository.
	 * @param MailBuilder              $builder Nachrichten-Erzeuger (K4c).
	 * @param MailEventRepository|null $events  Ereignis-Log (optional; ohne bleibt
	 *                                          das Verhalten wie vor K4e-1).
	 */
	public function __construct( QueueRepository $queue, MailBuilder $builder, ?MailEventRepository $events = null ) {
		$this->queue   = $queue;
		$this->builder = $builder;
		$this->events  = $events;
	}

	/**
	 * Verarbeitet einen Batch ueber den gegebenen Treiber.
	 *
	 * @param MailDriver $driver        Aktiver Versandtreiber.
	 * @param int        $rate_per_hour Durchsatzrate.
	 * @return array{sent:int,failed:int,skipped:int}
	 */
	public function run( MailDriver $driver, int $rate_per_hour ): array {
		$this->queue->reclaim_stale();

		$batch   = $this->queue->claim_batch( QueueRepository::batch_size( $rate_per_hour ) );
		$sent    = 0;
		$failed  = 0;
		$skipped = 0;

		foreach ( $batch as $row ) {
			$id       = (int) ( $row['id'] ?? 0 );
			$attempts = (int) ( $row['attempts'] ?? 0 );

			$message = $this->builder->build( $row );
			if ( null === $message ) {
				$this->queue->mark_skipped( $id, 'kein Inhalt oder uebersprungen' );
				$skipped++;
				continue;
			}

			$result = $driver instanceof EspDriver
				? $driver->send_detailed( $message )
				: ( $driver->send( $message ) ? SendResult::success() : SendResult::transient( 'Versand fehlgeschlagen' ) );

			if ( $result->ok ) {
				$this->queue->mark_sent( $id, $result->message_id );
				$this->record_event( $row, 'sent', $result->message_id );
				$sent++;
			} else {
				$this->queue->mark_failed( $id, $attempts, $result->permanent, $result->error );
				if ( $result->permanent ) {
					$this->record_event( $row, 'failed', $result->error );
				}
				$failed++;
			}
		}

		return array( 'sent' => $sent, 'failed' => $failed, 'skipped' => $skipped );
	}

	/**
	 * Schreibt ein Versand-Ereignis, sofern ein Ereignis-Log verdrahtet ist.
	 *
	 * @param array<string, mixed> $row    Queue-Zeile.
	 * @param string               $type   Ereignistyp (sent|failed).
	 * @param string               $detail Provider-Message-ID bzw. Fehlermeldung.
	 * @return void
	 */
	private function record_event( array $row, string $type, string $detail ): void {
		if ( null === $this->events ) {
			return;
		}
		$this->events->record(
			(int) ( $row['campaign_id'] ?? 0 ),
			(int) ( $row['contact_id'] ?? 0 ),
			$type,
			array(
				'detail'   => $detail,
				'queue_id' => (int) ( $row['id'] ?? 0 ),
			)
		);
	}
}
