<?php

namespace App\Data\Pagarme\Order;

use App\Data\Pagarme\Customer\CustomerResponseData;
use App\Data\Pagarme\Order\Parts\Response\ChargeData;
use App\Data\Pagarme\Order\Parts\Response\CheckoutData;
use App\Data\Pagarme\Order\Parts\Response\ItemData;
use App\Data\Pagarme\Order\Parts\Response\TransactionData;
use App\Data\Pagarme\PagarmeResponseData;
use App\Enums\PaymentStatusEnum;

/**
 * @param ItemData[]     $items
 * @param ChargeData[]   $charges
 * @param CheckoutData[] $checkouts
 */
final readonly class OrderResponseData extends PagarmeResponseData
{
    public function __construct(
        public ?string $id,
        public ?string $code,
        public ?int    $amount,
        public ?string $currency,
        public ?bool   $closed,
        public ?string $status,
        public array   $items,

        public ?CustomerResponseData $customer,

        public array   $charges,
        public array   $checkouts,
        public array   $metadata,
        public ?string $createdAt = null,
        public ?string $updatedAt = null,
        public ?string $closedAt  = null,
    ) {}

    public function authorizedAt(): ?string
    {
        $transaction = $this->lastTransaction();

        if (! $transaction) {
            return null;
        }

        if (in_array($transaction->status, ['authorized_pending_capture', 'captured', 'partial_capture'], true)) {
            return $transaction->createdAt;
        }

        return null;
    }

    public function failureCode(): ?string
    {
        $transaction = $this->lastTransaction();

        if (! $transaction) {
            return null;
        }

        $code = $this->filledValue(data_get($transaction->gatewayResponse, 'code'));

        if ($code === null || $this->isMisleadingFailureCode($code)) {
            return $this->filledValue($transaction->status);
        }

        return $code;
    }

    public function failureMessage(): ?string
    {
        $transaction = $this->lastTransaction();

        if (! $transaction) {
            return null;
        }

        $acquirerMessage = $this->filledValue($transaction->acquirerMessage);

        if ($acquirerMessage && ! $this->isMisleadingAcquirerMessage($acquirerMessage)) {
            return $acquirerMessage;
        }

        $gatewayErrors = data_get($transaction->gatewayResponse, 'errors', []);

        if (! is_array($gatewayErrors) || empty($gatewayErrors)) {
            return $this->failureMessageFromStatus($transaction->status);
        }

        $message = collect($gatewayErrors)
            ->pluck('message')
            ->filter()
            ->implode('; ') ?: null;

        if ($message && str_contains($message, 'Sem ambiente configurado')) {
            return 'Pix não esta habilitado ou configurado neste ambiente do Pagar.me.';
        }

        return $message;
    }

    public function firstCharge(): ?ChargeData
    {
        return data_get($this->charges, 0);
    }

    public function gatewayEntityLabel(): string
    {
        $charge = $this->firstCharge();

        return $charge?->id ? 'charge' : 'order';
    }

    public function gatewayEntityReference(): ?string
    {
        $charge = $this->firstCharge();

        return $charge?->id ?? $this->id;
    }

    public function gatewayOperationLabel(): string
    {
        $charge      = $this->firstCharge();
        $transaction = $this->lastTransaction();

        return $transaction?->id ? 'transaction' : ($charge?->id ? 'charge' : 'order');
    }

    public function gatewayOperationReference(): ?string
    {
        $charge      = $this->firstCharge();
        $transaction = $this->lastTransaction();

        return $transaction?->id ?? $charge?->id ?? $this->id;
    }

    public function lastTransaction(): ?TransactionData
    {
        return $this->firstCharge()?->transaction();
    }

    public function paymentStatus(): PaymentStatusEnum
    {
        $charge      = $this->firstCharge();
        $transaction = $this->lastTransaction();
        $status      = strtolower((string) ($transaction?->status ?: $charge?->status));

        return match ($status) {
            'captured', 'paid', 'overpaid'                  => PaymentStatusEnum::PAID,
            'authorized_pending_capture', 'waiting_capture' => PaymentStatusEnum::AUTHORIZED,
            'pending', 'waiting_payment'                    => PaymentStatusEnum::PENDING,
            'processing'                                    => PaymentStatusEnum::PROCESSING,

            'not_authorized', 'with_error', 'failed',
            'underpaid', 'chargedback' => PaymentStatusEnum::FAILED,

            'voided', 'partial_void', 'canceled',
            'cancelled', 'refunded', 'partial_refunded',
            'partial_canceled' => PaymentStatusEnum::CANCELLED,
            default            => PaymentStatusEnum::PENDING,
        };
    }

    public function paidAt(): ?string
    {
        return $this->firstCharge()?->paidAt;
    }

    public function requireId(): string
    {
        if (! $this->id) {
            throw new \RuntimeException('Pagar.me order creation returned an empty id.');
        }

        return $this->id;
    }

    //

    public static function fromArray(array $payload): static
    {
        $customer = static::arrArray($payload, 'customer');

        return new self(
            id:       static::arrString($payload, 'id'),
            code:     static::arrString($payload, 'code'),
            amount:   static::arrInt($payload, 'amount'),
            currency: static::arrString($payload, 'currency'),
            closed:   static::arrBool($payload, 'closed'),
            status:   static::arrString($payload, 'status'),

            items: static::arrMap($payload, 'items',
                static fn (array $item) => ItemData::fromArray($item),
            ),

            customer: ! empty($customer)
                ? CustomerResponseData::fromArray($customer)
                : null,

            charges: static::arrMap($payload, 'charges',
                static fn (array $charge) => ChargeData::fromArray($charge),
            ),

            checkouts: static::arrMap($payload, 'checkouts',
                static fn (array $checkout) => CheckoutData::fromArray($checkout),
            ),

            metadata:  static::arrArray($payload, 'metadata'),
            createdAt: static::arrString($payload, 'created_at'),
            updatedAt: static::arrString($payload, 'updated_at'),
            closedAt:  static::arrString($payload, 'closed_at'),
        );
    }

    public function toArray(): array
    {
        return [
            'id'       => $this->id,
            'code'     => $this->code,
            'amount'   => $this->amount,
            'currency' => $this->currency,
            'closed'   => $this->closed,

            'items' => array_map(
                static fn (ItemData $item) => $item->toArray(),
                $this->items,
            ),

            'customer'   => $this->customer?->toArray(),
            'status'     => $this->status,
            'created_at' => $this->createdAt,
            'updated_at' => $this->updatedAt,
            'closed_at'  => $this->closedAt,

            'charges' => array_map(
                static fn (ChargeData $charge) => $charge->toArray(),
                $this->charges,
            ),

            'checkouts' => array_map(
                static fn (CheckoutData $checkout) => $checkout->toArray(),
                $this->checkouts,
            ),

            'metadata' => $this->metadata,
        ];
    }

    //

    private function isMisleadingFailureCode(string $code): bool
    {
        // filtra para nao incluir codigos http que nao sao de erro

        if (preg_match('/^[1-5]\d{2}$/', $code)) {
            return true;
        }

        $lower = mb_strtolower($code);

        $successCodes = ['00', '0', 'approved', 'success'];

        return in_array($lower, $successCodes, true);
    }

    private function isMisleadingAcquirerMessage(string $message): bool
    {
        $lower = mb_strtolower($message);

        $successPatterns = [
            'aprovada',
            'aprovado',
            'autorizada',
            'autorizado',
            'authorized',
            'sucesso',
        ];

        foreach ($successPatterns as $pattern) {
            if (str_contains($lower, $pattern)) {
                return true;
            }
        }

        return false;
    }

    //

    private function failureMessageFromStatus(?string $status): ?string
    {
        return match (strtolower((string) $status)) {
            'not_authorized' => 'Transação não autorizada pela operadora do cartão.',
            'with_error'     => 'Erro ao processar a transação.',
            'failed'         => 'Transação falhou.',
            'underpaid'      => 'Valor pago inferior ao esperado.',
            'chargedback'    => 'Transação sofreu chargeback.',
            default          => null,
        };
    }

    private function filledValue(mixed $value): ?string
    {
        if ($value === null || $value === '' || $value === []) {
            return null;
        }

        return (string) $value;
    }
}
