<?php
declare(strict_types=1);

namespace Itdatex\Ausgangsrechnung\Cli;

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

use DateTimeImmutable;
use Itdatex\Ausgangsrechnung\IssueService;
use Itdatex\Ausgangsrechnung\Settings;
use Itdatex\EInvoiceCore\InvoiceDraft;
use Itdatex\EInvoiceCore\InvoiceLineDraft;
use Itdatex\EInvoiceCore\InvoiceParty;
use WP_CLI;

/**
 * WP-CLI: itdatex Ausgangsrechnung erzeugen.
 */
final class IssueCommand
{
    /**
     * Erzeugt eine Ausgangsrechnung aus einer JSON-Entwurfsdatei.
     *
     * ## OPTIONS
     *
     * --from=<path>
     * : Pfad zu einer JSON-Datei mit dem Rechnungs-Entwurf. Verkaeufer-Daten
     *   werden aus den Plugin-Settings gezogen, ausser sie sind im JSON
     *   explizit angegeben.
     *
     * [--actor=<name>]
     * : Optionaler Akteurs-Name fuer das Audit (Default: WP-User oder 'cli').
     *
     * [--format=<format>]
     * : Ausgabeformat: 'ubl' (Default, XRechnung UBL) oder 'cii' (ZUGFeRD/Factur-X EN 16931 CII).
     *
     * ## EXAMPLES
     *
     *     wp ausrg issue --from=/tmp/draft.json --actor=cli
     *     wp ausrg issue --from=/tmp/draft.json --format=cii
     *
     * @when after_wp_load
     */
    public function issue( array $args, array $assoc_args ): void
    {
        $jsonPath = (string) ( $assoc_args['from'] ?? '' );
        if ( $jsonPath === '' || ! is_file( $jsonPath ) ) {
            WP_CLI::error( "JSON-Datei nicht gefunden: {$jsonPath}" );
        }
        $raw = file_get_contents( $jsonPath );
        if ( $raw === false ) {
            WP_CLI::error( "JSON-Datei nicht lesbar: {$jsonPath}" );
        }
        $data = json_decode( $raw, true );
        if ( ! is_array( $data ) ) {
            WP_CLI::error( 'JSON konnte nicht geparst werden.' );
        }

        $actor = (string) ( $assoc_args['actor'] ?? '' );

        try {
            $draft = self::toDraft( $data );
        } catch ( \Throwable $e ) {
            WP_CLI::error( 'Ungueltiger Rechnungs-Entwurf: ' . $e->getMessage() );
        }

        $rawFormat = (string) ( $assoc_args['format'] ?? ( $data['format'] ?? IssueService::FORMAT_UBL ) );
        $format = array_key_exists( strtolower( $rawFormat ), IssueService::supportedFormats() )
            ? strtolower( $rawFormat )
            : IssueService::FORMAT_UBL;

        $service = new IssueService();
        $result  = $service->issue( $draft, $actor, $format );

        switch ( $result['result'] ) {
            case IssueService::RESULT_ISSUED:
                WP_CLI::success( sprintf(
                    "Rechnung erzeugt — invoice_id=%d, file=%s",
                    $result['invoice_id'],
                    $result['file_path'] ?? '(unbekannt)'
                ) );
                break;
            case IssueService::RESULT_DUPLICATE:
                WP_CLI::warning( sprintf(
                    "Doppelte Rechnung (SHA-256-Match) — invoice_id=%s",
                    $result['invoice_id'] ?? 'null'
                ) );
                break;
            default:
                WP_CLI::error( 'Fehlgeschlagen: ' . ( $result['message'] ?? 'unbekannter Fehler' ) );
        }
    }

    /**
     * @param array<string, mixed> $data
     */
    private static function toDraft( array $data ): InvoiceDraft
    {
        $required = [ 'invoice_number', 'issue_date', 'buyer', 'lines' ];
        foreach ( $required as $key ) {
            if ( ! isset( $data[ $key ] ) ) {
                throw new \InvalidArgumentException( esc_html( "Pflichtfeld fehlt: {$key}" ) );
            }
        }

        $seller = isset( $data['seller'] ) && is_array( $data['seller'] )
            ? self::toParty( $data['seller'] )
            : Settings::seller();

        if ( $seller->name === '' ) {
            throw new \InvalidArgumentException(
                'Verkaeufer-Name fehlt. Bitte Settings-Seite ausfuellen oder "seller" im JSON setzen.'
            );
        }

        $buyer = self::toParty( $data['buyer'] );

        $lines = [];
        foreach ( (array) $data['lines'] as $i => $raw ) {
            if ( ! is_array( $raw ) ) {
                throw new \InvalidArgumentException( esc_html( "Zeile {$i}: muss Objekt sein." ) );
            }
            $lines[] = new InvoiceLineDraft(
                name:               (string) ( $raw['name'] ?? '' ),
                quantity:           (float) ( $raw['quantity'] ?? 0 ),
                unitPrice:          (float) ( $raw['unit_price'] ?? 0 ),
                vatRate:            (float) ( $raw['vat_rate'] ?? 19.0 ),
                unitCode:           (string) ( $raw['unit_code'] ?? 'C62' ),
                vatCategory:        (string) ( $raw['vat_category'] ?? 'S' ),
                description:        isset( $raw['description'] ) ? (string) $raw['description'] : null,
                taxExemptionReason: isset( $raw['tax_exemption_reason'] ) ? (string) $raw['tax_exemption_reason'] : null,
            );
        }

        $payment = Settings::paymentRaw();

        return new InvoiceDraft(
            invoiceNumber:  (string) $data['invoice_number'],
            issueDate:      new DateTimeImmutable( (string) $data['issue_date'] ),
            seller:         $seller,
            buyer:          $buyer,
            lines:          $lines,
            dueDate:        isset( $data['due_date'] ) ? new DateTimeImmutable( (string) $data['due_date'] ) : null,
            currency:       (string) ( $data['currency'] ?? 'EUR' ),
            buyerReference: isset( $data['buyer_reference'] ) ? (string) $data['buyer_reference'] : null,
            note:           isset( $data['note'] ) ? (string) $data['note'] : null,
            paymentIban:    self::nullIfEmpty( $data['payment_iban'] ?? ( $payment['iban'] ?? '' ) ),
            paymentBic:     self::nullIfEmpty( $data['payment_bic']  ?? ( $payment['bic']  ?? '' ) ),
            paymentTerms:   self::nullIfEmpty( $data['payment_terms'] ?? ( $payment['terms'] ?? '' ) ),
            deliveryDate:   isset( $data['delivery_date'] ) ? new DateTimeImmutable( (string) $data['delivery_date'] ) : null,
        );
    }

    /** @param array<string, mixed> $a */
    private static function toParty( array $a ): InvoiceParty
    {
        $lines = $a['address_lines'] ?? [];
        if ( ! is_array( $lines ) ) {
            $lines = [];
        }
        return new InvoiceParty(
            name:                    (string) ( $a['name'] ?? '' ),
            vatNumber:               self::nullIfEmpty( $a['vat_number']     ?? '' ),
            addressLines:            array_values( array_map( 'strval', $lines ) ),
            city:                    self::nullIfEmpty( $a['city']           ?? '' ),
            postalCode:              self::nullIfEmpty( $a['postal_code']    ?? '' ),
            countryCode:             self::nullIfEmpty( strtoupper( $a['country_code'] ?? '' ) ),
            contactName:             self::nullIfEmpty( $a['contact_name']   ?? '' ),
            contactEmail:            self::nullIfEmpty( $a['contact_email']  ?? '' ),
            electronicAddressScheme: self::nullIfEmpty( strtoupper( (string) ( $a['electronic_address_scheme'] ?? '' ) ) ),
            electronicAddressValue:  self::nullIfEmpty( (string) ( $a['electronic_address_value']  ?? '' ) ),
            contactPhone:   self::nullIfEmpty( $a['contact_phone']  ?? '' ),
        );
    }

    private static function nullIfEmpty( $value ): ?string
    {
        $value = is_string( $value ) ? trim( $value ) : '';
        return $value === '' ? null : $value;
    }
}
