<?php
declare(strict_types=1);

namespace Itdatex\EInvoiceCore;

use DateTime;
use Itdatex\EInvoiceCore\Vendor\horstoeko\zugferd\ZugferdDocumentBuilder;
use Itdatex\EInvoiceCore\Vendor\horstoeko\zugferd\ZugferdProfiles;

/**
 * Baut aus einem InvoiceDraft eine ZUGFeRD-CII-XML im EN-16931-Profil.
 *
 * Symmetric zum UblBuilder: gleicher InvoiceDraft, anderer Schema-Baum.
 * KoSIT-konform fuer XRechnung-CII / Factur-X / ZUGFeRD EN16931.
 *
 * Total-Berechnung erfolgt lokal aus den Positionen — qty * unitPrice je
 * Zeile, dann pro VAT-Rate aggregiert. Keine Allowances/Charges im MVP.
 */
final class CiiBuilder
{
    public const PROFILE_EN16931 = ZugferdProfiles::PROFILE_EN16931;

    /** UN/CEFACT 1001 Document Type Code: 380 = Commercial invoice */
    public const DOC_TYPE_COMMERCIAL_INVOICE = '380';

    /** UN/CEFACT 4461 Payment Means Code: 58 = SEPA Credit Transfer */
    public const PAYMENT_MEANS_SEPA = '58';

    public function build( InvoiceDraft $draft ): string
    {
        return $this->buildBuilder( $draft )->getContent();
    }

    /**
     * Liefert das ZugferdDocumentBuilder-Objekt direkt zurueck — wird vom
     * ZugferdPdfBuilder benoetigt, der den Builder zum Embedden in eine
     * PDF braucht (und nicht nur die XML-Ausgabe).
     */
    public function buildBuilder( InvoiceDraft $draft ): ZugferdDocumentBuilder
    {
        if ( $draft->lines === [] ) {
            throw new \InvalidArgumentException( 'InvoiceDraft muss mindestens eine Position enthalten.' );
        }

        $b = ZugferdDocumentBuilder::createNew( self::PROFILE_EN16931 );

        // Header
        $b->setDocumentInformation(
            $draft->invoiceNumber,
            (string) $draft->typeCode,
            self::toDateTime( $draft->issueDate->format( 'Y-m-d' ) ),
            $draft->currency
        );
        if ( $draft->note !== null && $draft->note !== '' ) {
            $b->addDocumentNote( $draft->note );
        }
        if ( $draft->buyerReference !== null && $draft->buyerReference !== '' ) {
            $b->setDocumentBuyerReference( $draft->buyerReference );
        }
        if ( $draft->precedingInvoiceNumber !== null && $draft->precedingInvoiceNumber !== '' ) {
            $b->setDocumentInvoiceReferencedDocument(
                $draft->precedingInvoiceNumber,
                null,
                $draft->precedingIssueDate ? self::toDateTime( $draft->precedingIssueDate->format( 'Y-m-d' ) ) : null
            );
        }

        // Parties
        self::applySeller( $b, $draft->seller );
        self::applyBuyer( $b, $draft->buyer );

        // Lieferdatum (BT-72)
        if ( $draft->deliveryDate !== null ) {
            $b->setDocumentSupplyChainEvent(
                self::toDateTime( $draft->deliveryDate->format( 'Y-m-d' ) )
            );
        }

        // Zahlung
        if ( $draft->paymentIban !== null && $draft->paymentIban !== '' ) {
            $b->addDocumentPaymentMeanToCreditTransfer(
                $draft->paymentIban,
                null,
                null,
                $draft->paymentBic !== null && $draft->paymentBic !== '' ? $draft->paymentBic : null
            );
        }
        if ( $draft->paymentTerms !== null && $draft->paymentTerms !== '' ) {
            $b->addDocumentPaymentTerm(
                $draft->paymentTerms,
                $draft->dueDate !== null ? self::toDateTime( $draft->dueDate->format( 'Y-m-d' ) ) : null
            );
        }

        // Positionen + Tax-Aggregation
        $lineTotal     = 0.0;
        $byCategoryRate = [];  // [ "S|19.0" => [ 'category' => 'S', 'rate' => 19.0, 'base' => 100.0, 'reason' => string|null ] ]
        foreach ( $draft->lines as $i => $line ) {
            $posId = (string) ( $i + 1 );
            $b->addNewPosition( $posId );
            $b->setDocumentPositionProductDetails( $line->name, $line->description );
            $b->setDocumentPositionNetPrice( $line->unitPrice );
            $b->setDocumentPositionQuantity( $line->quantity, $line->unitCode );
            $b->addDocumentPositionTax(
                $line->vatCategory,
                'VAT',
                $line->vatRate,
                null,
                $line->taxExemptionReason
            );
            $rowNet = self::round2( $line->quantity * $line->unitPrice );
            $b->setDocumentPositionLineSummation( $rowNet );

            $lineTotal += $rowNet;
            $key = $line->vatCategory . '|' . number_format( $line->vatRate, 2, '.', '' );
            if ( ! isset( $byCategoryRate[ $key ] ) ) {
                $byCategoryRate[ $key ] = [
                    'category' => $line->vatCategory,
                    'rate'     => $line->vatRate,
                    'base'     => 0.0,
                    'reason'   => $line->taxExemptionReason,
                ];
            } else {
                $existing = $byCategoryRate[ $key ]['reason'];
                $incoming = $line->taxExemptionReason;
                if ( $existing === null && $incoming !== null ) {
                    // Bisher kein Reason — neue Zeile setzt den Wert.
                    $byCategoryRate[ $key ]['reason'] = $incoming;
                } elseif ( $existing !== null && $incoming !== null && $existing !== $incoming ) {
                    // Zwei unterschiedliche Begruendungen fuer denselben
                    // (Cat, Rate)-Eintrag wuerden auf Dokument-Ebene zu einer
                    // verschluckt — das ist ein Daten-Fehler beim Aufrufer,
                    // den wir laut machen statt silent zu uebernehmen.
                    throw new \InvalidArgumentException( sprintf(
                        'Konfligierende Tax-Exemption-Reasons fuer (cat=%s, rate=%s): "%s" vs. "%s"',
                        $line->vatCategory,
                        number_format( $line->vatRate, 2, '.', '' ),
                        $existing,
                        $incoming
                    ) );
                }
            }
            $byCategoryRate[ $key ]['base'] += $rowNet;
        }

        $taxTotal = 0.0;
        foreach ( $byCategoryRate as $entry ) {
            $base = self::round2( $entry['base'] );
            $tax  = self::round2( $base * $entry['rate'] / 100.0 );
            $b->addDocumentTax(
                $entry['category'],
                'VAT',
                $base,
                $tax,
                $entry['rate'],
                $entry['reason']
            );
            $taxTotal += $tax;
        }

        $grand = self::round2( $lineTotal + $taxTotal );
        $b->setDocumentSummation(
            $grand,                            // grandTotalAmount
            $grand,                            // duePayableAmount
            self::round2( $lineTotal ),        // lineTotalAmount
            0.0,                               // chargeTotalAmount
            0.0,                               // allowanceTotalAmount
            self::round2( $lineTotal ),        // taxBasisTotalAmount
            self::round2( $taxTotal ),         // taxTotalAmount
            0.0,                               // roundingAmount
            0.0                                // totalPrepaidAmount
        );

        return $b;
    }

    private static function applySeller( ZugferdDocumentBuilder $b, InvoiceParty $p ): void
    {
        $b->setDocumentSeller( $p->name );
        $b->setDocumentSellerAddress(
            $p->addressLines[0] ?? null,
            $p->addressLines[1] ?? null,
            $p->addressLines[2] ?? null,
            $p->postalCode,
            $p->city,
            $p->countryCode
        );
        if ( $p->vatNumber !== null && $p->vatNumber !== '' ) {
            $b->addDocumentSellerVATRegistrationNumber( $p->vatNumber );
        }
        if ( $p->contactName !== null || $p->contactPhone !== null || $p->contactEmail !== null ) {
            $b->setDocumentSellerContact(
                $p->contactName,
                null,
                $p->contactPhone,
                null,
                $p->contactEmail
            );
        }
        if ( $p->electronicAddressValue !== null && $p->electronicAddressValue !== '' ) {
            $b->setDocumentSellerCommunication(
                $p->electronicAddressScheme,
                $p->electronicAddressValue
            );
        }
    }

    private static function applyBuyer( ZugferdDocumentBuilder $b, InvoiceParty $p ): void
    {
        $b->setDocumentBuyer( $p->name );
        $b->setDocumentBuyerAddress(
            $p->addressLines[0] ?? null,
            $p->addressLines[1] ?? null,
            $p->addressLines[2] ?? null,
            $p->postalCode,
            $p->city,
            $p->countryCode
        );
        if ( $p->vatNumber !== null && $p->vatNumber !== '' ) {
            $b->addDocumentBuyerTaxRegistration( 'VA', $p->vatNumber );
        }
        if ( $p->contactName !== null || $p->contactPhone !== null || $p->contactEmail !== null ) {
            $b->setDocumentBuyerContact(
                $p->contactName,
                null,
                $p->contactPhone,
                null,
                $p->contactEmail
            );
        }
        if ( $p->electronicAddressValue !== null && $p->electronicAddressValue !== '' ) {
            $b->setDocumentBuyerCommunication(
                $p->electronicAddressScheme,
                $p->electronicAddressValue
            );
        }
    }

    private static function toDateTime( string $ymd ): DateTime
    {
        $d = DateTime::createFromFormat( '!Y-m-d', $ymd );
        if ( $d === false ) {
            throw new \InvalidArgumentException( "Ungueltiges Datum: {$ymd}" );
        }
        return $d;
    }

    private static function round2( float $value ): float
    {
        return round( $value, 2 );
    }
}
