<?php
declare(strict_types=1);

namespace Itdatex\EInvoiceCore;

/**
 * Struktur- und (eingeschraenkte) XSD-Validierung fuer E-Rechnungen.
 *
 * Was hier passiert:
 *   - well-formed XML-Check (libxml errors)
 *   - XSD-Schema-Validierung fuer CII (FACTUR-X EN-16931, aus horstoeko/zugferd)
 *   - Pflichtfeld-Check ueber den Parser (Rechnungsnummer, Datum, Betraege)
 *
 * Was hier NICHT passiert (kommt in M4):
 *   - Schematron-Regeln (BR-DE-*, BR-S-*, EN-16931-Geschaeftsregeln)
 *   - das uebernimmt der KoSIT-Validator als Java-Daemon
 *   - UBL-XSD-Validierung (Schemas muessen extern eingebunden werden)
 */
final class Validator
{
    private const CII_XSD_EN16931 = __DIR__ . '/../vendor-prefixed/horstoeko/zugferd/src/schema/FACTUR-X_EN16931.xsd';

    /**
     * @return ValidationReport
     */
    public function validate(string $filePath): ValidationReport
    {
        $issues = [];
        $format = FormatDetector::detect($filePath);

        if ($format === Format::UNKNOWN || $format === Format::PDF_LEGACY) {
            $issues[] = new ValidationIssue('format', 'error', "Format nicht erkannt oder kein XML/ZUGFeRD: {$format->value}");
            return new ValidationReport($format, $issues);
        }

        // 1. XML-Inhalt holen (ZUGFeRD: aus PDF extrahieren)
        if ($format === Format::ZUGFERD) {
            $xml = \Itdatex\EInvoiceCore\Vendor\horstoeko\zugferd\ZugferdDocumentPdfReader::getXmlFromFile($filePath);
        } else {
            $xml = (string) file_get_contents($filePath);
        }

        // 2. Well-formed XML?
        $previous = libxml_use_internal_errors(true);
        libxml_clear_errors();
        $doc = new \DOMDocument();
        $loaded = $doc->loadXML($xml, LIBXML_NONET);
        if (! $loaded) {
            foreach (libxml_get_errors() as $err) {
                $issues[] = new ValidationIssue(
                    'xml',
                    'error',
                    "Zeile {$err->line}: " . trim($err->message)
                );
            }
            libxml_clear_errors();
            libxml_use_internal_errors($previous);
            return new ValidationReport($format, $issues);
        }
        libxml_clear_errors();

        // 3. XSD-Validierung (nur CII / ZUGFeRD — UBL kommt spaeter)
        if ($format === Format::XRECHNUNG_CII || $format === Format::ZUGFERD) {
            if (is_file(self::CII_XSD_EN16931)) {
                $xsdValid = $doc->schemaValidate(self::CII_XSD_EN16931);
                if (! $xsdValid) {
                    foreach (libxml_get_errors() as $err) {
                        $issues[] = new ValidationIssue(
                            'xsd',
                            $err->level === LIBXML_ERR_WARNING ? 'warning' : 'error',
                            "Zeile {$err->line}: " . trim($err->message)
                        );
                    }
                    libxml_clear_errors();
                }
            } else {
                $issues[] = new ValidationIssue('xsd', 'info', 'CII-XSD nicht gefunden — Schema-Validierung uebersprungen.');
            }
        } elseif ($format === Format::XRECHNUNG_UBL) {
            $issues[] = new ValidationIssue('xsd', 'info', 'UBL-XSD-Validierung kommt in einer spaeteren Stufe. Aktuell nur Well-Formed-Check + Pflichtfeld-Plausibilitaet.');
        }

        libxml_use_internal_errors($previous);

        // 4. Pflichtfeld-Plausibilitaet (ueber Parser)
        try {
            $invoice = (new Parser())->parse($filePath);
            foreach (self::requiredFieldChecks($invoice) as $issue) {
                $issues[] = $issue;
            }
        } catch (\Throwable $e) {
            $issues[] = new ValidationIssue('parse', 'error', $e->getMessage());
        }

        return new ValidationReport($format, $issues);
    }

    /**
     * @return iterable<ValidationIssue>
     */
    private static function requiredFieldChecks(InvoiceData $i): iterable
    {
        if ($i->invoiceNumber === null || $i->invoiceNumber === '') {
            yield new ValidationIssue('field', 'error', 'BT-1: Rechnungsnummer fehlt.');
        }
        if ($i->issueDate === null) {
            yield new ValidationIssue('field', 'error', 'BT-2: Rechnungsdatum fehlt.');
        }
        if ($i->sellerName === null || $i->sellerName === '') {
            yield new ValidationIssue('field', 'error', 'BT-27: Verkaeufer-Name fehlt.');
        }
        if ($i->totalGross === null) {
            yield new ValidationIssue('field', 'error', 'BT-112: Brutto-Gesamtbetrag fehlt.');
        }
        if ($i->lineCount === 0 && $i->format !== Format::PDF_LEGACY) {
            yield new ValidationIssue('field', 'warning', 'Keine Rechnungs-Positionen erkannt.');
        }
    }
}
