<?php
declare(strict_types=1);

namespace Itdatex\Rechnungseingang;

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

use Itdatex\Rechnungseingang\Vendor\Einvoicing\Invoice as UblInvoice;
use Itdatex\Rechnungseingang\Vendor\Einvoicing\Readers\UblReader;
use Itdatex\Rechnungseingang\Vendor\horstoeko\zugferd\ZugferdDocumentPdfReader;
use Itdatex\Rechnungseingang\Vendor\horstoeko\zugferd\ZugferdDocumentReader;

/**
 * Orchestriert Format-Erkennung und Parsing.
 *
 * Delegiert an die jeweils passende Bibliothek:
 *   - UBL                 -> josemmo/einvoicing (UblReader)
 *   - CII (UN/CEFACT)     -> horstoeko/zugferd  (ZugferdDocumentReader)
 *   - ZUGFeRD-PDF         -> horstoeko/zugferd  (ZugferdDocumentPdfReader -> Reader)
 *   - PDF ohne XML        -> Format::PDF_LEGACY (Kandidat fuer KI-Extraktion)
 *
 * Liefert immer ein InvoiceData-DTO mit den Kernfeldern.
 */
final class Parser
{
    public function parse(string $filePath): InvoiceData
    {
        $format = FormatDetector::detect($filePath);

        return match ($format) {
            Format::XRECHNUNG_UBL => $this->parseUbl($filePath),
            Format::XRECHNUNG_CII => $this->parseCiiFile($filePath),
            Format::ZUGFERD       => $this->parseZugferd($filePath),
            Format::PDF_LEGACY    => new InvoiceData($format),
            Format::UNKNOWN       => throw new \RuntimeException("Unbekanntes Format: {$filePath}"),
        };
    }

    /* -------------------------------------------------------------- UBL */

    private function parseUbl(string $filePath): InvoiceData
    {
        $xml     = (string) file_get_contents($filePath);
        $reader  = new UblReader();
        $invoice = $reader->import($xml);
        return $this->ublToDto($invoice, Format::XRECHNUNG_UBL);
    }

    private function ublToDto(UblInvoice $invoice, Format $format): InvoiceData
    {
        $totals = $invoice->getTotals();
        $seller = $invoice->getSeller();
        $buyer  = $invoice->getBuyer();

        return new InvoiceData(
            format:         $format,
            invoiceNumber:  $invoice->getNumber(),
            issueDate:      $invoice->getIssueDate(),
            dueDate:        $invoice->getDueDate(),
            sellerName:     $seller?->getName(),
            sellerVatId:    self::asString($seller?->getVatNumber()),
            buyerName:      $buyer?->getName(),
            buyerReference: $invoice->getBuyerReference(),
            totalNet:       (float) $totals->taxExclusiveAmount,
            totalTax:       (float) $totals->vatAmount,
            totalGross:     (float) $totals->taxInclusiveAmount,
            currency:       $totals->currency ?: 'EUR',
            lineCount:      count($invoice->getLines()),
        );
    }

    /* -------------------------------------------------------------- CII */

    private function parseCiiFile(string $filePath): InvoiceData
    {
        $reader = ZugferdDocumentReader::readAndGuessFromFile($filePath);
        return $this->ciiToDto($reader, Format::XRECHNUNG_CII);
    }

    private function parseZugferd(string $pdfPath): InvoiceData
    {
        $reader = ZugferdDocumentPdfReader::readAndGuessFromFile($pdfPath);
        return $this->ciiToDto($reader, Format::ZUGFERD);
    }

    private function ciiToDto(ZugferdDocumentReader $r, Format $format): InvoiceData
    {
        // Header-Infos
        $docNo = $docTypeCode = $currency = $taxCurrency = $docName = $docLang = null;
        $docDate = $effectivePeriod = null;
        $r->getDocumentInformation($docNo, $docTypeCode, $docDate, $currency, $taxCurrency, $docName, $docLang, $effectivePeriod);

        // Buyer Reference (Leitweg-ID)
        $buyerRef = null;
        try { $r->getDocumentBuyerReference($buyerRef); } catch (\Throwable) {}

        // Verkaeufer
        $sellerName = $sellerDesc = null; $sellerIds = null;
        try { $r->getDocumentSeller($sellerName, $sellerIds, $sellerDesc); } catch (\Throwable) {}
        $sellerVat = null;
        try {
            $taxReg = null;
            $r->getDocumentSellerTaxRegistration($taxReg);
            $sellerVat = self::firstVatId($taxReg);
        } catch (\Throwable) {}

        // Kaeufer
        $buyerName = $buyerDesc = null; $buyerIds = null;
        try { $r->getDocumentBuyer($buyerName, $buyerIds, $buyerDesc); } catch (\Throwable) {}

        // Beträge
        $grandTotal = $duePayable = $lineTotal = $chargeTotal = null;
        $allowanceTotal = $taxBasis = $taxTotal = $rounding = $prepaid = null;
        try {
            $r->getDocumentSummation($grandTotal, $duePayable, $lineTotal, $chargeTotal, $allowanceTotal, $taxBasis, $taxTotal, $rounding, $prepaid);
        } catch (\Throwable) {}

        // Faelligkeit
        $dueDate = null;
        try { $r->getDocumentBillingPeriod($dueDate); } catch (\Throwable) {}
        if ($dueDate === null) {
            // ZUGFeRD-Faelligkeit liegt im PaymentTerms-Block
            try {
                $paymentDesc = null; $paymentDue = null; $directDebitMandate = null;
                $r->getDocumentPaymentTerm($paymentDesc, $paymentDue, $directDebitMandate);
                $dueDate = $paymentDue;
            } catch (\Throwable) {}
        }

        // Anzahl Positionen
        $lineCount = 0;
        try {
            $r->firstDocumentPosition();
            $lineCount = 1;
            while ($r->nextDocumentPosition()) { $lineCount++; }
        } catch (\Throwable) {}

        return new InvoiceData(
            format:         $format,
            invoiceNumber:  $docNo,
            issueDate:      $docDate,
            dueDate:        $dueDate,
            sellerName:     $sellerName,
            sellerVatId:    $sellerVat,
            buyerName:      $buyerName,
            buyerReference: $buyerRef,
            totalNet:       $taxBasis !== null ? (float) $taxBasis : null,
            totalTax:       $taxTotal !== null ? (float) $taxTotal : null,
            totalGross:     $grandTotal !== null ? (float) $grandTotal : null,
            currency:       $currency ?: 'EUR',
            lineCount:      $lineCount,
        );
    }

    /* --------------------------------------------------------- Helpers */

    private static function asString(mixed $value): ?string
    {
        if ($value === null) return null;
        if (is_string($value)) return $value;
        if (is_object($value) && method_exists($value, 'getValue')) {
            return (string) $value->getValue();
        }
        return (string) $value;
    }

    /**
     * Aus dem zugferd-Output ($taxReg) die USt-IdNr. herausziehen.
     * Format: [ 'VA' => 'DE123...', 'FC' => '123/456' ]
     * Scheme-Codes: VA = VAT, FC = local tax (Steuernummer).
     */
    private static function firstVatId(mixed $taxReg): ?string
    {
        if (! is_array($taxReg)) return null;
        if (isset($taxReg['VA']) && is_string($taxReg['VA'])) return $taxReg['VA'];
        // Fallback: irgendein Eintrag mit Wert
        foreach ($taxReg as $v) {
            if (is_string($v) && $v !== '') return $v;
        }
        return null;
    }
}
