<?php
declare(strict_types=1);

namespace Itdatex\EInvoiceCore;

use DateTime;
use Itdatex\EInvoiceCore\Vendor\Einvoicing\Identifier as UblIdentifier;
use Itdatex\EInvoiceCore\Vendor\Einvoicing\Delivery as UblDelivery;
use Itdatex\EInvoiceCore\Vendor\Einvoicing\Invoice as UblInvoice;
use Itdatex\EInvoiceCore\Vendor\Einvoicing\InvoiceLine as UblInvoiceLine;
use Itdatex\EInvoiceCore\Vendor\Einvoicing\InvoiceReference as UblInvoiceReference;
use Itdatex\EInvoiceCore\Vendor\Einvoicing\Party as UblParty;
use Itdatex\EInvoiceCore\Vendor\Einvoicing\Payments\Payment as UblPayment;
use Itdatex\EInvoiceCore\Vendor\Einvoicing\Payments\Transfer as UblTransfer;
use Itdatex\EInvoiceCore\Vendor\Einvoicing\Writers\UblWriter;

/**
 * Baut aus einem InvoiceDraft einen XRechnung-UBL-XML-String.
 *
 * Verwendet das XRechnung-3.0-UBL-Spezifikations-Profil. Der dahinter
 * liegende josemmo/einvoicing UblWriter erzeugt EN-16931-konforme UBL,
 * dazu setzen wir den XRechnung-Customisations-Identifier.
 */
final class UblBuilder
{
    /**
     * XRechnung-3.x-Customisation-Identifier (steward = XEinkauf).
     *
     * Achtung: der frueher uebliche xoev-de-URI (urn:xoev-de:kosit:standard:xrechnung_3.0)
     * gehoert zur XRechnung-2.x-Zeit und wird von aktuellen KoSIT-Szenarien
     * nicht mehr als XRechnung-3.0 erkannt — Validierung schlaegt dann ohne
     * konkrete Findings fehl.
     */
    public const XRECHNUNG_3_UBL_SPECIFICATION =
        'urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0';

    /** Business process / ProfileID — PEPPOL-EN16931-R001 verlangt einen Wert. */
    public const XRECHNUNG_DEFAULT_BUSINESS_PROCESS = 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0';

    public function build( InvoiceDraft $draft ): string
    {
        if ( $draft->lines === [] ) {
            throw new \InvalidArgumentException( 'InvoiceDraft muss mindestens eine Position enthalten.' );
        }

        $inv = new UblInvoice();
        $inv->setSpecification( self::XRECHNUNG_3_UBL_SPECIFICATION );
        $inv->setBusinessProcess( self::XRECHNUNG_DEFAULT_BUSINESS_PROCESS );
        $inv->setType( $draft->typeCode );
        $inv->setNumber( $draft->invoiceNumber );
        $inv->setIssueDate( self::toDateTime( $draft->issueDate->format( 'Y-m-d' ) ) );
        if ( $draft->precedingInvoiceNumber !== null && $draft->precedingInvoiceNumber !== '' ) {
            $inv->addPrecedingInvoiceReference( new UblInvoiceReference(
                $draft->precedingInvoiceNumber,
                $draft->precedingIssueDate ? self::toDateTime( $draft->precedingIssueDate->format( 'Y-m-d' ) ) : null
            ) );
        }
        if ( $draft->dueDate !== null ) {
            $inv->setDueDate( self::toDateTime( $draft->dueDate->format( 'Y-m-d' ) ) );
        }
        $inv->setCurrency( $draft->currency );
        if ( $draft->buyerReference !== null ) {
            $inv->setBuyerReference( $draft->buyerReference );
        }
        if ( $draft->note !== null ) {
            $inv->setNote( $draft->note );
        }

        $inv->setSeller( self::toParty( $draft->seller ) );
        $inv->setBuyer( self::toParty( $draft->buyer ) );

        if ( $draft->deliveryDate !== null ) {
            $delivery = new UblDelivery();
            $delivery->setDate( self::toDateTime( $draft->deliveryDate->format( 'Y-m-d' ) ) );
            $inv->setDelivery( $delivery );
        }

        foreach ( $draft->lines as $line ) {
            $inv->addLine( self::toLine( $line ) );
        }

        if ( $draft->paymentIban !== null && $draft->paymentIban !== '' ) {
            $payment  = new UblPayment();
            $transfer = new UblTransfer();
            $transfer->setAccountId( $draft->paymentIban );
            if ( $draft->paymentBic !== null && $draft->paymentBic !== '' ) {
                $transfer->setProvider( $draft->paymentBic );
            }
            $payment->addTransfer( $transfer );
            // 58 = SEPA Credit Transfer (UN/ECE 4461)
            $payment->setMeansCode( '58' );
            if ( $draft->paymentTerms !== null && $draft->paymentTerms !== '' ) {
                $payment->setTerms( $draft->paymentTerms );
            }
            $inv->setPayment( $payment );
        } elseif ( $draft->paymentTerms !== null && $draft->paymentTerms !== '' ) {
            $inv->setPaymentTerms( $draft->paymentTerms );
        }

        return ( new UblWriter() )->export( $inv );
    }

    private static function toParty( InvoiceParty $p ): UblParty
    {
        $party = new UblParty();
        $party->setName( $p->name );
        if ( $p->vatNumber !== null && $p->vatNumber !== '' ) {
            $party->setVatNumber( $p->vatNumber );
        }
        if ( $p->addressLines !== [] ) {
            $party->setAddress( $p->addressLines );
        }
        if ( $p->city !== null && $p->city !== '' ) {
            $party->setCity( $p->city );
        }
        if ( $p->postalCode !== null && $p->postalCode !== '' ) {
            $party->setPostalCode( $p->postalCode );
        }
        if ( $p->countryCode !== null && $p->countryCode !== '' ) {
            $party->setCountry( $p->countryCode );
        }
        if ( $p->contactName !== null && $p->contactName !== '' ) {
            $party->setContactName( $p->contactName );
        }
        if ( $p->contactEmail !== null && $p->contactEmail !== '' ) {
            $party->setContactEmail( $p->contactEmail );
        }
        if ( $p->contactPhone !== null && $p->contactPhone !== '' ) {
            $party->setContactPhone( $p->contactPhone );
        }
        if ( $p->electronicAddressValue !== null && $p->electronicAddressValue !== '' ) {
            // EAS-Codeliste (CEF). Default '0204' = "Leitweg-ID" — DE Behoerden.
            $scheme = ( $p->electronicAddressScheme !== null && $p->electronicAddressScheme !== '' )
                ? $p->electronicAddressScheme
                : '0204';
            $party->setElectronicAddress( new UblIdentifier( $p->electronicAddressValue, $scheme ) );
        }
        return $party;
    }

    private static function toLine( InvoiceLineDraft $l ): UblInvoiceLine
    {
        $line = new UblInvoiceLine();
        $line->setName( $l->name );
        if ( $l->description !== null && $l->description !== '' ) {
            $line->setDescription( $l->description );
        }
        $line->setQuantity( $l->quantity );
        $line->setUnit( $l->unitCode );
        $line->setPrice( $l->unitPrice );
        $line->setVatCategory( $l->vatCategory );
        $line->setVatRate( $l->vatRate );
        // BT-120: Pflicht-Reason bei steuerfreien/AE-Lines.
        if ( $l->taxExemptionReason !== null && $l->taxExemptionReason !== '' ) {
            $line->setVatExemptionReason( $l->taxExemptionReason );
        }
        return $line;
    }

    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;
    }
}
