<?php
declare(strict_types=1);

namespace Itdatex\Ausgangsrechnung\Admin\Pages;

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

use DateTimeImmutable;
use Itdatex\Ausgangsrechnung\Admin\Menu;
use Itdatex\Ausgangsrechnung\IssueService;
use Itdatex\Ausgangsrechnung\Numbering;
use Itdatex\Ausgangsrechnung\Plugin;
use Itdatex\Ausgangsrechnung\Settings as SettingsStore;
use Itdatex\EInvoiceCore\InvoiceDraft;
use Itdatex\EInvoiceCore\InvoiceLineDraft;
use Itdatex\EInvoiceCore\InvoiceParty;
use Itdatex\EInvoiceCore\InvoiceRepository;

/**
 * Formular fuer eine neue Ausgangsrechnung.
 *
 * MVP-Layout: 10 leere Positions-Zeilen, leere werden serverseitig gefiltert.
 */
final class CreatePage
{
    public const ACTION = 'ausrg_create_invoice';
    public const NONCE  = 'ausrg_create_nonce';
    public const ROWS   = 10;

    /**
     * Form-Processing MUSS in admin_init laufen, nicht im Render-Callback —
     * der Render-Callback wird erst nach dem WP-Admin-Header aufgerufen,
     * da hat output bereits begonnen und wp_safe_redirect() greift nicht
     * mehr. PRG-Pattern: Sticky/Errors via Transient an die Render-Phase
     * reichen.
     */
    public function register(): void
    {
        add_action( 'admin_init', [ self::class, 'maybe_handle_submit' ] );
    }

    public static function render(): void
    {
        if ( ! current_user_can( Plugin::CAPABILITY ) ) {
            wp_die( esc_html__( 'Keine Berechtigung.', 'itdatex-e-invoice-output' ), '', [ 'response' => 403 ] );
        }

        $stash = get_transient( self::transient_key() );
        if ( is_array( $stash ) ) {
            delete_transient( self::transient_key() );
        } else {
            $stash = [ 'sticky' => [], 'errors' => [] ];
        }

        $today      = ( new DateTimeImmutable( 'today' ) )->format( 'Y-m-d' );
        $defaultDue = ( new DateTimeImmutable( '+30 days' ) )->format( 'Y-m-d' );

        $sticky      = (array) ( $stash['sticky'] ?? [] );
        $errors      = (array) ( $stash['errors'] ?? [] );
        $rows        = self::ROWS;
        $action_url  = admin_url( 'admin.php?page=' . Menu::SLUG_CREATE );

        // Auto-Vorschlag fuer die Rechnungsnummer, wenn das Sticky-Feld leer
        // ist (Neuformular oder nach dem PRG-Reset). Nach einem
        // Validierungs-Fehler kehrt der Nutzer mit seiner eigenen Eingabe
        // zurueck — die soll nicht ueberschrieben werden.
        $invSticky = (array) ( $sticky['invoice'] ?? [] );
        if ( trim( (string) ( $invSticky['number'] ?? '' ) ) === '' ) {
            $invSticky['number'] = Numbering::nextSuggestion();
            $sticky['invoice']    = $invSticky;
        }

        // Buyer-Prefill aus einer Vorgaenger-Rechnung, wenn ?buyer_from=<id>
        // gesetzt ist und noch keine sticky-Buyer-Daten existieren.
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only prefill; form-submit unten hat separaten Nonce.
        $buyerFrom = isset( $_GET['buyer_from'] ) ? (int) $_GET['buyer_from'] : 0;
        if ( $buyerFrom > 0 && empty( $sticky['buyer'] ) ) {
            $prefill = self::loadBuyerFrom( $buyerFrom );
            if ( $prefill !== null ) {
                $sticky['buyer'] = $prefill;
            }
        }

        include AUSRG_DIR . 'admin/views/page-create.php';
    }

    public static function maybe_handle_submit(): void
    {
        $method = isset( $_SERVER['REQUEST_METHOD'] )
            ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) )
            : '';
        if ( $method !== 'POST' ) {
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce-Check unten via check_admin_referer.
        $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( (string) $_GET['page'] ) ) : '';
        if ( $page !== Menu::SLUG_CREATE ) {
            return;
        }
        if ( ! isset( $_POST[ self::NONCE ] ) ) {
            return;
        }
        if ( ! current_user_can( Plugin::CAPABILITY ) ) {
            wp_die( esc_html__( 'Keine Berechtigung.', 'itdatex-e-invoice-output' ), '', [ 'response' => 403 ] );
        }
        check_admin_referer( self::ACTION, self::NONCE );

        // $_POST wird unslashed und rekursiv sanitized (sanitize_textarea_field
        // erhaelt Zeilenumbrueche fuer mehrzeilige Felder wie Adresse/Notes,
        // strippt aber Tags). build_draft() macht darauf noch feld-spezifisches
        // sanitize (email/int/format/...). So landen keine ungefilterten Daten
        // im PRG-Transient.
        $sticky = self::sanitize_form_tree( wp_unslash( $_POST ) );
        $errors = [];

        try {
            $draft  = self::build_draft( $sticky );
            $format = self::sanitize_format(
                (string) ( ( $sticky['invoice']['format'] ?? '' ) )
            );
            $result = ( new IssueService() )->issue( $draft, '', $format );

            if ( $result['result'] === IssueService::RESULT_ISSUED ) {
                Numbering::consumeIfMatched( $draft->invoiceNumber );
                wp_safe_redirect( add_query_arg(
                    [
                        'page'         => Menu::SLUG_LIST,
                        'ausrg_status' => 'issued',
                        'invoice_id'   => (int) $result['invoice_id'],
                    ],
                    admin_url( 'admin.php' )
                ) );
                exit;
            }
            if ( $result['result'] === IssueService::RESULT_DUPLICATE ) {
                wp_safe_redirect( add_query_arg(
                    [
                        'page'         => Menu::SLUG_LIST,
                        'ausrg_status' => 'duplicate',
                        'invoice_id'   => (int) ( $result['invoice_id'] ?? 0 ),
                    ],
                    admin_url( 'admin.php' )
                ) );
                exit;
            }
            $errors[] = sprintf(
                /* translators: %s — Fehlertext aus IssueService */
                __( 'Fehler beim Erstellen: %s', 'itdatex-e-invoice-output' ),
                $result['message'] ?? __( 'unbekannt', 'itdatex-e-invoice-output' )
            );
        } catch ( \Throwable $e ) {
            $errors[] = $e->getMessage();
        }

        set_transient( self::transient_key(), [ 'sticky' => $sticky, 'errors' => $errors ], 60 );
        wp_safe_redirect( admin_url( 'admin.php?page=' . Menu::SLUG_CREATE ) );
        exit;
    }

    private static function transient_key(): string
    {
        return 'ausrg_create_stash_' . get_current_user_id();
    }

    /**
     * Rekursive Basis-Sanitisierung fuer die komplette Form-Struktur
     * (Ein-/Mehrzeilige Textfelder). Nicht-String-Werte werden verworfen —
     * unsere Views lesen jede Zelle als (string), also ist das kein Verlust.
     *
     * @param mixed $value
     * @return mixed
     */
    private static function sanitize_form_tree( $value )
    {
        if ( is_array( $value ) ) {
            $out = [];
            foreach ( $value as $k => $v ) {
                $key = is_string( $k ) ? sanitize_key( $k ) : (int) $k;
                $out[ $key ] = self::sanitize_form_tree( $v );
            }
            return $out;
        }
        if ( is_string( $value ) ) {
            return sanitize_textarea_field( $value );
        }
        return '';
    }

    /**
     * KoSIT-/XRechnung-Pflicht beim Kaeufer (BG-7 Buyer postal address +
     * Endpoint). Spiegel zu Settings::requiredSellerFields(); buyer_reference
     * (BT-10) ist invoice-level und wird in build_draft separat geprueft.
     *
     * @return array<string, string>  feld_slug => label
     */
    public static function requiredBuyerFields(): array
    {
        return [
            'name'                      => __( 'Kaeufer-Name (BT-44)',                 'itdatex-e-invoice-output' ),
            'address'                   => __( 'Kaeufer-Adresse (BT-50)',              'itdatex-e-invoice-output' ),
            'city'                      => __( 'Kaeufer-Stadt (BR-DE-8/BT-52)',        'itdatex-e-invoice-output' ),
            'postal_code'               => __( 'Kaeufer-PLZ (BR-DE-9/BT-53)',          'itdatex-e-invoice-output' ),
            'country_code'              => __( 'Kaeufer-Land (BT-55)',                 'itdatex-e-invoice-output' ),
            'electronic_address_scheme' => __( 'EAS-Schema (R010/BT-49-1)',            'itdatex-e-invoice-output' ),
            'electronic_address_value'  => __( 'Kaeufer-Elektronische-Adresse (R010/BT-49)', 'itdatex-e-invoice-output' ),
        ];
    }

    /**
     * @param array<string, mixed> $buyer  Roh-Form-Daten (sanitized erst danach)
     * @return array<string, string>       feld_slug => fehlertext
     */
    public static function missingBuyerFields( array $buyer ): array
    {
        $required = self::requiredBuyerFields();
        $missing  = [];
        foreach ( $required as $key => $label ) {
            $val = trim( (string) ( $buyer[ $key ] ?? '' ) );
            if ( $val === '' ) {
                $missing[ $key ] = $label;
            }
        }
        return $missing;
    }

    /**
     * @param array<string,mixed> $p
     */
    private static function build_draft( array $p ): InvoiceDraft
    {
        $invMeta = is_array( $p['invoice'] ?? null ) ? $p['invoice'] : [];
        $buyer   = is_array( $p['buyer']   ?? null ) ? $p['buyer']   : [];
        $linesIn = is_array( $p['lines']   ?? null ) ? $p['lines']   : [];

        $number = trim( (string) ( $invMeta['number'] ?? '' ) );
        if ( $number === '' ) {
            throw new \InvalidArgumentException( esc_html__( 'Rechnungsnummer ist Pflicht.', 'itdatex-e-invoice-output' ) );
        }
        $issueDate = trim( (string) ( $invMeta['issue_date'] ?? '' ) );
        if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $issueDate ) ) {
            throw new \InvalidArgumentException( esc_html__( 'Rechnungsdatum muss im Format YYYY-MM-DD sein.', 'itdatex-e-invoice-output' ) );
        }
        $dueDate = trim( (string) ( $invMeta['due_date'] ?? '' ) );
        $dueDate = ( $dueDate !== '' && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $dueDate ) ) ? $dueDate : null;

        $deliveryDate = trim( (string) ( $invMeta['delivery_date'] ?? '' ) );
        $deliveryDate = ( $deliveryDate !== '' && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $deliveryDate ) ) ? $deliveryDate : null;

        // Pflichtfeld-Check Kaeufer + Rechnungs-Meta. Wir sammeln alle Luecken,
        // damit der User nicht Feld fuer Feld zurueckkommen muss.
        $missing = self::missingBuyerFields( $buyer );
        if ( trim( (string) ( $invMeta['buyer_reference'] ?? '' ) ) === '' ) {
            $missing['buyer_reference'] = __( 'Leitweg-ID / Buyer Reference (BR-DE-15/BT-10)', 'itdatex-e-invoice-output' );
        }
        if ( $missing !== [] ) {
            throw new \InvalidArgumentException( esc_html(
                __( 'KoSIT-Pflichtfelder fehlen:', 'itdatex-e-invoice-output' )
                . "\n  - " . implode( "\n  - ", $missing )
            ) );
        }

        // Lines filtern (leere ueberspringen, mindestens eine Pflicht)
        $lines = [];
        foreach ( $linesIn as $row ) {
            if ( ! is_array( $row ) ) continue;
            $name = trim( (string) ( $row['name'] ?? '' ) );
            $qty  = (float) ( $row['quantity'] ?? 0 );
            if ( $name === '' || $qty <= 0 ) continue;
            $cat       = self::sanitize_vat_cat( (string) ( $row['vat_category'] ?? 'S' ) );
            $reasonRaw = trim( (string) ( $row['tax_exemption_reason'] ?? '' ) );
            $reason    = $reasonRaw !== ''
                ? sanitize_text_field( $reasonRaw )
                : self::defaultExemptionReason( $cat );
            $lines[] = new InvoiceLineDraft(
                name:               sanitize_text_field( $name ),
                quantity:           $qty,
                unitPrice:          (float) ( $row['unit_price'] ?? 0 ),
                vatRate:            (float) ( $row['vat_rate'] ?? 19.0 ),
                unitCode:           self::sanitize_unit( (string) ( $row['unit_code'] ?? 'C62' ) ),
                vatCategory:        $cat,
                description:        null,
                taxExemptionReason: $reason,
            );
        }
        if ( $lines === [] ) {
            throw new \InvalidArgumentException( esc_html__( 'Mindestens eine Position mit Name und Menge > 0.', 'itdatex-e-invoice-output' ) );
        }

        $addressLines = preg_split( '/\r\n|\r|\n/', trim( (string) ( $buyer['address'] ?? '' ) ) ) ?: [];
        $addressLines = array_values( array_filter( array_map( 'trim', $addressLines ), fn( $l ) => $l !== '' ) );

        $buyerEasScheme = self::nullIfEmpty( strtoupper(
            preg_replace( '/[^A-Za-z0-9]/', '', (string) ( $buyer['electronic_address_scheme'] ?? '' ) ) ?? ''
        ) );
        $buyerEasValue  = self::nullIfEmpty( (string) ( $buyer['electronic_address_value'] ?? '' ) );
        if ( $buyerEasValue !== null && $buyerEasScheme === null ) {
            throw new \InvalidArgumentException( esc_html__(
                'Buyer: bei elektronischer Adresse muss auch das EAS-Schema gewaehlt werden (PEPPOL-EN16931-R010).',
                'itdatex-e-invoice-output'
            ) );
        }

        $buyerParty = new InvoiceParty(
            name:                    sanitize_text_field( (string) $buyer['name'] ),
            vatNumber:               self::nullIfEmpty( (string) ( $buyer['vat_number'] ?? '' ) ),
            addressLines:            $addressLines,
            city:                    self::nullIfEmpty( (string) ( $buyer['city'] ?? '' ) ),
            postalCode:              self::nullIfEmpty( (string) ( $buyer['postal_code'] ?? '' ) ),
            countryCode:             self::nullIfEmpty( strtoupper( (string) ( $buyer['country_code'] ?? 'DE' ) ) ),
            contactName:             self::nullIfEmpty( (string) ( $buyer['contact_name'] ?? '' ) ),
            contactEmail:            self::nullIfEmpty( (string) ( $buyer['contact_email'] ?? '' ) ),
            contactPhone:            self::nullIfEmpty( (string) ( $buyer['contact_phone'] ?? '' ) ),
            electronicAddressScheme: $buyerEasScheme,
            electronicAddressValue:  $buyerEasValue,
        );

        $seller = SettingsStore::seller();
        if ( $seller->name === '' ) {
            throw new \InvalidArgumentException( esc_html__(
                'Verkaeufer-Stammdaten sind leer. Bitte unter Einstellungen ausfuellen.',
                'itdatex-e-invoice-output'
            ) );
        }

        $payment = SettingsStore::paymentRaw();

        return new InvoiceDraft(
            invoiceNumber:  $number,
            issueDate:      new DateTimeImmutable( $issueDate ),
            seller:         $seller,
            buyer:          $buyerParty,
            lines:          $lines,
            dueDate:        $dueDate ? new DateTimeImmutable( $dueDate ) : null,
            currency:       strtoupper( (string) ( $invMeta['currency'] ?? 'EUR' ) ),
            buyerReference: self::nullIfEmpty( (string) ( $invMeta['buyer_reference'] ?? '' ) ),
            note:           self::nullIfEmpty( (string) ( $invMeta['note'] ?? '' ) ),
            paymentIban:    self::nullIfEmpty( $payment['iban']  ?? '' ),
            paymentBic:     self::nullIfEmpty( $payment['bic']   ?? '' ),
            paymentTerms:   self::nullIfEmpty( $payment['terms'] ?? '' ),
            deliveryDate:   $deliveryDate ? new DateTimeImmutable( $deliveryDate ) : null,
        );
    }

    private static function sanitize_format( string $value ): string
    {
        $value = strtolower( trim( $value ) );
        return array_key_exists( $value, IssueService::supportedFormats() )
            ? $value
            : IssueService::FORMAT_UBL;
    }

    private static function sanitize_unit( string $value ): string
    {
        $value = strtoupper( preg_replace( '/[^A-Z0-9]/', '', $value ) ?? '' );
        return $value === '' ? 'C62' : $value;
    }

    private static function sanitize_vat_cat( string $value ): string
    {
        $value = strtoupper( substr( preg_replace( '/[^A-Z]/', '', $value ) ?? '', 0, 2 ) );
        return in_array( $value, [ 'S', 'AE', 'Z', 'E', 'G', 'K', 'O' ], true ) ? $value : 'S';
    }

    /**
     * Default-Begruendung pro steuerfreier VAT-Kategorie (BT-120 / BR-*-09).
     * Wer im Formular einen eigenen Text setzt, ueberschreibt das hier; ohne
     * Eingabe nehmen wir den Standard, damit die Rechnung KoSIT-akzeptiert
     * herauskommt, ohne dass der Nutzer juristische Formulierungen tippen muss.
     *
     * Add-ons koennen per Filter 'ausrg_default_exemption_reason' eigene Texte
     * einhaengen (z.B. eine prazisere Reverse-Charge-Formulierung pro
     * Branche, eine osterreichische Variante, oder eine englische
     * Uebersetzung fur internationale Empfaenger).
     *
     * @param string $cat  EN-16931 BT-151 Code (E/AE/Z/G/K/O/S)
     */
    private static function defaultExemptionReason( string $cat ): ?string
    {
        $reason = match ( $cat ) {
            'E'  => __( 'Steuerbefreit nach § 19 UStG (Kleinunternehmerregelung)',     'itdatex-e-invoice-output' ),
            'AE' => __( 'Steuerschuldnerschaft des Leistungsempfaengers (§ 13b UStG)', 'itdatex-e-invoice-output' ),
            'Z'  => __( 'Nullsatz-Lieferung',                                          'itdatex-e-invoice-output' ),
            'G'  => __( 'Ausfuhrlieferung (steuerfrei nach § 4 Nr. 1a UStG)',          'itdatex-e-invoice-output' ),
            'K'  => __( 'Innergemeinschaftliche Lieferung (steuerfrei nach § 4 Nr. 1b UStG)', 'itdatex-e-invoice-output' ),
            'O'  => __( 'Nicht im Geltungsbereich der Umsatzsteuer',                   'itdatex-e-invoice-output' ),
            default => null,  // 'S' = Standard, kein Reason
        };

        /**
         * Filter den Default-Exemption-Reason pro Cat-Code.
         *
         * @param string|null $reason  Default oder null bei Cat 'S'.
         * @param string      $cat     EN-16931 BT-151 Cat-Code.
         */
        $filtered = apply_filters( 'ausrg_default_exemption_reason', $reason, $cat );
        return is_string( $filtered ) ? $filtered : ( $filtered === null ? null : (string) $filtered );
    }

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

    /**
     * Laedt die persistierten Buyer-Daten einer Vorgaenger-Rechnung
     * (aus raw_meta) im Format, das die View erwartet. Nur outgoing.
     *
     * @return array<string,string>|null
     */
    private static function loadBuyerFrom( int $invoiceId ): ?array
    {
        $repo    = new InvoiceRepository( 'outgoing' );
        $invoice = $repo->findInvoice( $invoiceId );
        if ( ! $invoice || (string) $invoice->direction !== 'outgoing' ) {
            return null;
        }
        $meta = json_decode( (string) $invoice->raw_meta, true );
        if ( ! is_array( $meta ) || ! isset( $meta['buyer'] ) || ! is_array( $meta['buyer'] ) ) {
            return null;
        }
        return array_map( 'strval', $meta['buyer'] );
    }
}
