<?php
declare(strict_types=1);

namespace Itdatex\Rechnungseingang;

if ( ! defined( 'ABSPATH' ) ) { exit; }

/**
 * Pflegt die zentrale Ingest-Pipeline: Parse → Validate → Hooks → Storage →
 * InvoiceRepository → Audit.
 *
 * Aufrufer (UploadHandler aus dem Browser-Formular ODER Add-ons wie
 * itdatex E-Invoice Intake Pro mit IMAP-Import) bekommen einen einheitlichen
 * Einstiegspunkt — das stellt sicher, dass die Hooks `reing_after_validate`
 * und `reing_after_invoice_insert` immer feuern, egal aus welcher Quelle die
 * Datei kommt.
 *
 * Der Aufrufer ist verantwortlich fuer:
 *   - tmpPath muss eine LESBARE Datei sein (lebt waehrend des Ingests).
 *   - Datei wird durch Storage in das Persistenz-Verzeichnis kopiert; der
 *     temporaere Pfad darf danach geloescht werden.
 */
final class IngestService
{
    public const SOURCE_UPLOAD = 'upload';
    public const SOURCE_IMAP   = 'imap';

    /**
     * Ergebnis eines Ingest-Versuchs.
     */
    public const RESULT_INSERTED  = 'inserted';
    public const RESULT_DUPLICATE = 'duplicate';
    public const RESULT_FAILED    = 'failed';

    private InvoiceRepository $repo;
    private Parser $parser;
    private Validator $validator;
    private Storage $storage;

    public function __construct(
        ?InvoiceRepository $repo = null,
        ?Parser $parser = null,
        ?Validator $validator = null,
        ?Storage $storage = null
    ) {
        $this->repo      = $repo      ?? new InvoiceRepository();
        $this->parser    = $parser    ?? new Parser();
        $this->validator = $validator ?? new Validator();
        $this->storage   = $storage   ?? new Storage();
    }

    /**
     * Verarbeitet eine Rechnungsdatei und legt einen DB-Eintrag an.
     *
     * @param string $tmpPath      Absoluter Pfad zu einer lesbaren Datei.
     * @param string $originalName Dateiname fuer Anzeige + Persistenz.
     * @param string $source       'upload' | 'imap' | …
     * @param string $actor        Frei waehlbarer Actor-String fuer das Audit.
     *
     * @return array{result:string, invoice_id:int|null, message?:string}
     *   - result=duplicate: invoice_id zeigt auf die vorhandene Datei
     *   - result=inserted: invoice_id ist der neue Eintrag
     *   - result=failed:   message enthaelt einen menschen-lesbaren Grund
     */
    public function ingest(
        string $tmpPath,
        string $originalName,
        string $source = self::SOURCE_UPLOAD,
        string $actor = ''
    ): array {
        if ( $tmpPath === '' || ! file_exists( $tmpPath ) ) {
            return [ 'result' => self::RESULT_FAILED, 'invoice_id' => null, 'message' => 'file_missing' ];
        }
        $originalName = sanitize_file_name( $originalName );
        if ( $originalName === '' ) {
            $originalName = basename( $tmpPath );
        }
        if ( $actor === '' ) {
            $actor = function_exists( 'wp_get_current_user' ) && wp_get_current_user()->ID
                ? ( wp_get_current_user()->user_login ?: 'system' )
                : 'system';
        }

        try {
            // Vorab Hash → Duplikat-Check
            $sha256   = hash_file( 'sha256', $tmpPath );
            $existing = $this->repo->findFileBySha( (string) $sha256 );
            if ( $existing ) {
                // Wir liefern die invoice_id zurueck, wenn das File schon eine Rechnung hat
                global $wpdb;
                $invoice_id = (int) $wpdb->get_var( $wpdb->prepare(
                    "SELECT id FROM {$this->repo->tableInvoices()} WHERE file_id = %d LIMIT 1",
                    (int) $existing->id
                ) );
                return [
                    'result'     => self::RESULT_DUPLICATE,
                    'invoice_id' => $invoice_id ?: null,
                    'message'    => 'sha256_match',
                ];
            }

            $invoiceData = $this->parser->parse( $tmpPath );
            $report      = $this->validator->validate( $tmpPath );

            do_action( 'reing_after_validate', $tmpPath, $report, $invoiceData );

            $prevChain = $this->repo->lastChainHash();
            $fileData  = $this->storage->persist( $tmpPath, $originalName, $prevChain );
            $fileId    = $this->repo->insertFile( $fileData, $source );
            if ( $fileId === 0 ) {
                return [ 'result' => self::RESULT_FAILED, 'invoice_id' => null, 'message' => 'file_insert_failed' ];
            }

            $invoiceId = $this->repo->insertInvoice( $fileId, $invoiceData, $report );
            if ( $invoiceId === 0 ) {
                return [ 'result' => self::RESULT_FAILED, 'invoice_id' => null, 'message' => 'invoice_insert_failed' ];
            }

            $this->repo->appendAudit(
                $invoiceId,
                $actor,
                'ingested',
                sprintf( 'source=%s, format=%s, errors=%d, warnings=%d',
                    $source,
                    $invoiceData->format->value,
                    $report->errorCount(),
                    $report->warningCount()
                )
            );

            do_action( 'reing_after_invoice_insert', $invoiceId, $invoiceData, $report );

            return [ 'result' => self::RESULT_INSERTED, 'invoice_id' => $invoiceId ];

        } catch ( \Throwable $e ) {
            error_log( '[itdatex-e-invoice-intake] IngestService-Fehler: ' . $e->getMessage() );
            return [ 'result' => self::RESULT_FAILED, 'invoice_id' => null, 'message' => $e->getMessage() ];
        }
    }
}
