<?php

namespace objects;

use classes\db;
use classes\object_property;
use classes\stripe;
use Exception;
use traits\db_object_t;

class stripe_payment_intents_o extends db
{
    use db_object_t;

    public object_property $order_id;
    public object_property $payment_intent_id;
    public object_property $client_secret;
    public object_property $data;
    public object_property $reader_id;
    public object_property $tax_percentage;

    public function structure(): void
    {
        $this->setTable('stripe_payment_intents');
    }

    /**
     * Add a new payment intent
     * @throws Exception
     */
    public function add(
        int $order_id,
        string $payment_intent_id,
        string $client_secret,
        mixed $data = null,
        ?string $reader_id = null,
        int $tax_percentage = null
    ): void {
        if (is_object($data) || is_array($data)) {
            $data = json_encode($data);
        }

        $this->clearOrderPaymentIntents($order_id);
        $tmp_id = self::add_object([
            'order_id' => $order_id,
            'payment_intent_id' => $payment_intent_id,
            'client_secret' => $client_secret,
            'data' => $data,
            'reader_id' => $reader_id,
            'tax_percentage' => ($tax_percentage !== null) ? (int)$tax_percentage : 0,
        ]);
        $this->id = $tmp_id;
        self::getObjectProperties();
        self::objectChanged();
    }

    public function getObjectProperties(): void
    {
        $this->order_id = new object_property($this->table, $this->id, 'order_id', 'int', false);
        $this->payment_intent_id = new object_property($this->table, $this->id, 'payment_intent_id', 'string', false);
        $this->client_secret = new object_property($this->table, $this->id, 'client_secret', 'string', false);
        $this->data = new object_property($this->table, $this->id, 'data', 'string', false);
        $this->reader_id = new object_property($this->table, $this->id, 'reader_id', 'string', false);
        $this->tax_percentage = new object_property($this->table, $this->id, 'tax_percentage', 'int', false);
    }

    public function objectChanged(): void
    {
        // TODO: Add cache invalidation
    }

    public function doesOrderHavePaymentIntent(int $order_id): bool
    {
        return count($this->getOrderPaymentIntentRows($order_id)) > 0;
    }

    /**
     * @throws Exception
     */
    public function selectOrderPaymentIntent(int $order_id): self
    {
        $rows = $this->getOrderPaymentIntentRows($order_id);
        if (count($rows) === 0) {
            throw new Exception('No payment intent found for order id: ' . $order_id);
        }

        $this->id = (int)$rows[0]['id'];
        self::getObjectProperties();
        $this->deleteDuplicateOrderPaymentIntents($order_id, $this->id);
        return $this;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function getOrderPaymentIntentRows(int $order_id): array
    {
        $rows = self::getFieldsWhere(
            [
                'order_id' => $order_id,
            ],
            ['id', 'payment_intent_id', 'reader_id', 'tax_percentage']
        );

        usort($rows, static fn(array $a, array $b): int => ((int)($b['id'] ?? 0)) <=> ((int)($a['id'] ?? 0)));
        return $rows;
    }

    public function clearOrderPaymentIntents(int $order_id, ?int $keepId = null): void
    {
        foreach ($this->getOrderPaymentIntentRows($order_id) as $row) {
            $rowId = (int)($row['id'] ?? 0);
            if ($rowId <= 0 || ($keepId !== null && $rowId === $keepId)) {
                continue;
            }
            self::delete_object($this->getTable(), $rowId);
        }
    }

    public function updateStoredPaymentIntent(mixed $paymentIntent): void
    {
        self::requireSelected();

        if (is_object($paymentIntent) && method_exists($paymentIntent, 'toJSON')) {
            $encodedData = $paymentIntent->toJSON();
        } elseif (is_array($paymentIntent) || is_object($paymentIntent)) {
            $encodedData = json_encode($paymentIntent);
        } else {
            $encodedData = (string)$paymentIntent;
        }

        $this->data->set($encodedData);

        $normalizedClientSecret = null;
        $normalizedReaderId = null;
        $normalizedTaxPercentage = null;

        if (is_object($paymentIntent) || is_array($paymentIntent)) {
            $metadata = is_array($paymentIntent)
                ? ($paymentIntent['metadata'] ?? [])
                : ($paymentIntent->metadata ?? null);

            $normalizedClientSecret = is_array($paymentIntent)
                ? ($paymentIntent['client_secret'] ?? null)
                : ($paymentIntent->client_secret ?? null);

            if (is_array($metadata)) {
                $normalizedReaderId = $metadata['reader_id'] ?? $metadata['reader'] ?? null;
                $normalizedTaxPercentage = $metadata['tax_percentage'] ?? null;
            } elseif (is_object($metadata)) {
                $normalizedReaderId = $metadata->reader_id ?? $metadata->reader ?? null;
                $normalizedTaxPercentage = $metadata->tax_percentage ?? null;
            }
        }

        if (is_string($normalizedClientSecret) && $normalizedClientSecret !== '') {
            $this->client_secret->set($normalizedClientSecret);
        }
        if ($normalizedReaderId !== null) {
            $this->reader_id->set((string)$normalizedReaderId);
        }
        if ($normalizedTaxPercentage !== null && is_numeric($normalizedTaxPercentage)) {
            $this->tax_percentage->set((int)$normalizedTaxPercentage);
        }

        self::objectChanged();
    }

    public function setReaderId(?string $readerId): void
    {
        self::requireSelected();

        if ($readerId === null || trim($readerId) === '') {
            $this->reader_id->nullify();
            self::objectChanged();
            return;
        }

        $this->reader_id->set(trim($readerId));
        self::objectChanged();
    }

    /**
     * @throws Exception
     */
    public function delete(): void
    {
        self::requireSelected();
        self::cancelPaymentIntent();
        self::deletePermanently();
    }

    /**
     * @throws Exception
     */
    public function cancelPaymentIntent(): void
    {
        self::requireSelected();
        $stripe = new stripe();

        $readerId = trim((string)($this->reader_id->value() ?? ''));
        if ($readerId !== '') {
            try {
                $stripe->readers->sendCancelPaymentIntent($readerId);
            } catch (\Stripe\Exception\InvalidRequestException) {
                // The reader may already be idle or missing. Clearing local state is sufficient.
            }
            $this->reader_id->nullify();
        }

        $paymentIntentId = trim((string)($this->payment_intent_id->value() ?? ''));
        if ($paymentIntentId === '') {
            self::objectChanged();
            return;
        }

        try {
            $paymentIntent = $stripe->payment_intents->get($paymentIntentId);
        } catch (\Stripe\Exception\InvalidRequestException) {
            self::objectChanged();
            return;
        }

        $status = strtolower((string)($paymentIntent->status ?? ''));
        if (in_array($status, ['succeeded', 'canceled'], true)) {
            $this->updateStoredPaymentIntent($paymentIntent);
            return;
        }

        try {
            $cancelledPaymentIntent = $stripe->payment_intents->cancel($paymentIntentId);
            $this->updateStoredPaymentIntent($cancelledPaymentIntent);
        } catch (\Stripe\Exception\InvalidRequestException) {
            self::objectChanged();
        }
    }

    private function deleteDuplicateOrderPaymentIntents(int $order_id, int $keepId): void
    {
        $this->clearOrderPaymentIntents($order_id, $keepId);
    }
}
