<?php

declare( strict_types=1 );

namespace Brightfern\Inbound;

use Brightfern\Archive\ArchiveInterface;
use Brightfern\Support\Logger;
use Brightfern\Transmission\AccessPointProvider;
use Brightfern\Transmission\InboundInvoice;

/**
 * The provider-agnostic inbound pipeline: normalize → parse → match → store →
 * archive. Shared by both the webhook controller and the polling cron so the
 * two entry points behave identically.
 */
final class InboundReceiver {

	public function __construct(
		private AccessPointProvider $provider,
		private UblInboundParser $parser,
		private OrderMatcher $matcher,
		private ArchiveInterface $archive,
	) {}

	/**
	 * Ingest a raw provider webhook payload.
	 *
	 * @param array<string,mixed> $payload
	 * @return int|null Stored row id; 0 if ignored/duplicate (benign — the
	 *   provider's own retry already succeeded once, nothing to do); null if
	 *   storage genuinely failed, so the caller can respond and log
	 *   accordingly instead of a lost document looking identical to a
	 *   harmless duplicate.
	 */
	public function ingestWebhook( array $payload ): ?int {
		$inbound = $this->provider->handleIncoming( $payload );
		if ( null === $inbound ) {
			return 0; // Not an inbound-document event.
		}
		return $this->receive( $inbound );
	}

	/** Run the polling fallback; returns the number of new documents stored. */
	public function runPoll(): int {
		$count = 0;
		foreach ( $this->provider->poll() as $inbound ) {
			$id = $this->receive( $inbound );
			if ( $id > 0 ) {
				++$count;
			} elseif ( null === $id ) {
				// Distinct from 0 (benign duplicate) specifically so this
				// doesn't vanish the same way it used to: the cron has no HTTP
				// response to signal failure through, so a log line is the
				// only place a genuinely lost document is visible at all.
				Logger::error( 'Polling fallback: failed to store an inbound document (' . $inbound->providerMessageId . ').' );
			}
		}
		return $count;
	}

	/** @return int|null Row id; 0 for a benign duplicate; null on genuine storage failure. */
	public function receive( InboundInvoice $inbound ): ?int {
		if ( InboundStore::exists( $inbound->providerMessageId ) ) {
			return 0;
		}

		$summary = $this->parser->parse( $inbound->xml );
		$orderId = $this->matcher->match( $summary );

		$id = InboundStore::save( $inbound, $summary, $orderId );

		if ( null === $id ) {
			Logger::error(
				sprintf(
					'Failed to store inbound e-invoice %s from %s (%s) — the document was received but NOT saved.',
					$summary->invoiceNumber,
					$summary->supplierName,
					$inbound->senderId
				)
			);
			return null;
		}

		if ( $id > 0 ) {
			$this->archive->store(
				'inbound',
				$inbound->xml,
				array(
					'providerMessageId' => $inbound->providerMessageId,
					'invoiceNumber'     => $summary->invoiceNumber,
					'supplierVat'       => $summary->supplierVat,
				)
			);

			Logger::info(
				sprintf(
					'Received e-invoice %s from %s (%s); %s.',
					$summary->invoiceNumber,
					$summary->supplierName,
					$inbound->senderId,
					$orderId > 0 ? 'matched order #' . $orderId : 'no order match'
				)
			);

			/**
			 * Fires after an inbound document is stored.
			 *
			 * @param int            $id      Inbound row id.
			 * @param InboundSummary $summary Parsed summary.
			 * @param int            $orderId Matched order id (0 if none).
			 */
			do_action( 'brightfern_inbound_received', $id, $summary, $orderId );
		}

		return $id;
	}
}
