<?php

namespace App\EventPaymentModules;

use App\Enumerations\PaymentStatus;
use App\EventPaymentModules\DTO\CreateInvoiceRequest;
use App\EventPaymentModules\DTO\CreateInvoiceResponse;
use App\EventPaymentModules\DTO\DoPaymentRequest;
use App\EventPaymentModules\DTO\DoPaymentResponse;
use App\EventPaymentModules\DTO\RegistrationSummaryRequest;
use App\EventPaymentModules\DTO\RegistrationSummaryResponse;

/**
 * Gemeinsame Basis aller Zahlungsmodule. Hält den geteilten Code und die aus {@see getOptions()}
 * abgeleiteten Options-Helfer; konkrete Module definieren nur ihre Identität, ihr Options-Schema
 * und -- später -- die je Zahlungsart variierenden Teile (z.B. den Rechnungs-Schlusssatz).
 */
abstract class AbstractEventPaymentModule implements EventPaymentModule
{
    public function defaultDescription(): ?string
    {
        return null;
    }

    /**
     * Standard-Konfiguration beim Anlegen der Tenant-Instanz. Standard: leer.
     *
     * @return array<string, mixed>
     */
    public function defaultConfiguration(): array
    {
        return [];
    }

    /**
     * Payer-seitige Eingaben, die eine Zahlungsart vom Teilnehmer benötigt (z.B. später SEPA-IBAN,
     * PayPal-Mail). Standard: keine. Gleiche Form wie getOptions(): [{name,label,type,required}].
     *
     * @return array<int, array{name: string, label: string, type: string, required: bool}>
     */
    public function getParticipantOptions(): array
    {
        return [];
    }

    // -----------------------------------------------------------------------------------------
    // Aus dem jeweiligen Schema abgeleitete Helfer (Admin-Config = getOptions(),
    // Teilnehmer-Eingaben = getParticipantOptions()).
    // -----------------------------------------------------------------------------------------

    /** @return array<int, string> */
    public function requiredOptionKeys(): array
    {
        return $this->requiredKeys($this->getOptions());
    }

    /**
     * @param array<string, mixed> $config
     * @return array<string, mixed>
     */
    public function sanitizeConfiguration(array $config): array
    {
        return $this->sanitizeAgainst($this->getOptions(), $config);
    }

    /** @param array<string, mixed> $config */
    public function isConfigurationComplete(array $config): bool
    {
        return $this->allRequiredFilled($this->getOptions(), $config);
    }

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    public function sanitizeParticipantOptions(array $input): array
    {
        return $this->sanitizeAgainst($this->getParticipantOptions(), $input);
    }

    /** @param array<string, mixed> $input */
    public function participantOptionsComplete(array $input): bool
    {
        return $this->allRequiredFilled($this->getParticipantOptions(), $input);
    }

    /**
     * @param array<int, array{name: string, required?: bool}> $schema
     * @return array<int, string>
     */
    private function requiredKeys(array $schema): array
    {
        return array_values(array_map(
            static fn (array $option) => $option['name'],
            array_filter($schema, static fn(array $option) => $option['required'] ?? false)
        ));
    }

    /**
     * Reduziert Eingaben auf die im Schema definierten Keys (unbekannte werden verworfen).
     *
     * @param array<int, array{name: string}> $schema
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    private function sanitizeAgainst(array $schema, array $input): array
    {
        $result = [];
        foreach ($schema as $option) {
            if (array_key_exists($option['name'], $input)) {
                $result[$option['name']] = $input[$option['name']];
            }
        }

        return $result;
    }

    /**
     * Sind alle Pflichtfelder des Schemas befüllt (non-empty)?
     *
     * @param array<int, array{name: string, required?: bool}> $schema
     * @param array<string, mixed> $input
     */
    private function allRequiredFilled(array $schema, array $input): bool
    {
        foreach ($this->requiredKeys($schema) as $key) {
            $value = $input[$key] ?? null;
            if ($value === null || $value === '' || $value === []) {
                return false;
            }
        }

        return true;
    }

    // -----------------------------------------------------------------------------------------
    // Operative Methoden -- in dieser Iteration bewusst als Stubs.
    // -----------------------------------------------------------------------------------------

    public function registrationSummary(RegistrationSummaryRequest $request): RegistrationSummaryResponse
    {
        // TODO: In einer Folge-Iteration ausformulieren (zahlungsspezifischer Teil der Zusammenfassung).
        return new RegistrationSummaryResponse();
    }

    public function doPayment(DoPaymentRequest $request): DoPaymentResponse
    {
        // TODO: In einer Folge-Iteration ausformulieren. Default: nichts aktiv anzustoßen (Status offen).
        $response = new DoPaymentResponse();
        $response->success = true;
        $response->status = PaymentStatus::PAYMENT_STATUS_PENDING;

        return $response;
    }

    /**
     * Template-Method: Der gemeinsame Rechnungs-Rumpf wird hier gebaut, der variierende Schlusssatz
     * kommt aus {@see invoiceClosingStatement()} des jeweiligen Moduls.
     */
    public function createInvoice(CreateInvoiceRequest $request): CreateInvoiceResponse
    {
        $response = new CreateInvoiceResponse();
        // TODO: In einer Folge-Iteration den gemeinsamen Rumpf (Betrag, Leistung, Teilnehmerdaten) befüllen
        //       und an die Invoice-Domain anbinden.
        $response->success = true;
        $response->closingStatement = $this->invoiceClosingStatement($request);

        return $response;
    }

    /**
     * Der je Zahlungsart variierende Schlusssatz der Rechnung
     * ("Bitte überweise bis ..." / "wird abgebucht ..." / "wurde per PayPal beglichen").
     */
    abstract protected function invoiceClosingStatement(CreateInvoiceRequest $request): string;
}
