<?php
/**
 * 2026 Paysera
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Academic Free License (AFL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/afl-3.0.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@prestashop.com so we can send you a copy immediately.
 *
 * @author Paysera <plugins@paysera.com>
 * @copyright 2026 Paysera
 * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
 * International Registered Trademark & Property of Paysera
 */

declare(strict_types=1);

namespace Paysera\PrestashopPayments\Repository;

use Paysera\PrestashopPayments\Dto\PayseraPayment;
use Paysera\PrestashopPayments\Installer\DatabaseInstaller;
use Psr\Clock\ClockInterface;
use Psr\Log\LoggerInterface;

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

class PayseraPaymentRepository
{
    public function __construct(
        private LoggerInterface $logger,
        private ClockInterface $clock
    ) {
    }

    public function findByOrderId(int $orderId): ?PayseraPayment
    {
        if ($orderId <= 0) {
            return null;
        }

        $table = $this->tableName();
        $sql = 'SELECT * FROM `' . $table . '` WHERE `id_order` = ' . $orderId;

        $row = \Db::getInstance()->getRow($sql);

        if (!is_array($row) || $row === []) {
            return null;
        }

        return new PayseraPayment(
            (int) $row['id_order'],
            $this->nullableString($row['paysera_payment_id'] ?? null),
            $this->nullableString($row['paysera_order_id'] ?? null),
            $this->nullableString($row['payment_link'] ?? null),
            $this->nullableInt($row['original_amount'] ?? null),
            $this->nullableString($row['original_currency'] ?? null),
            $this->nullableInt($row['paid_amount'] ?? null),
            $this->nullableString($row['paid_currency'] ?? null),
            $this->nullableString($row['payer_name'] ?? null),
            $this->nullableString($row['payer_email'] ?? null),
            $this->nullableString($row['payment_method'] ?? null),
            $this->nullableString($row['status'] ?? null)
        );
    }

    public function save(PayseraPayment $row): void
    {
        $orderId = $row->getOrderId();

        if ($orderId <= 0) {
            return;
        }

        $fields = $this->extractNonNullFields($row);

        if ($fields === []) {
            return;
        }

        $now = $this->clock->now()->format('Y-m-d H:i:s');
        $insertColumns = array_merge(
            ['id_order' => $orderId],
            $fields,
            ['date_add' => $now, 'date_upd' => $now]
        );

        $columnNames = array_keys($insertColumns);
        $valueLiterals = array_map(
            fn ($value): string => $this->toSqlLiteral($value),
            $insertColumns
        );

        $updateAssignments = array_map(
            static fn (string $column): string => '`' . $column . '` = VALUES(`' . $column . '`)',
            array_merge(array_keys($fields), ['date_upd'])
        );

        $sql = sprintf(
            'INSERT INTO `%s` (%s) VALUES (%s) ON DUPLICATE KEY UPDATE %s',
            $this->tableName(),
            implode(', ', array_map(static fn (string $c): string => '`' . $c . '`', $columnNames)),
            implode(', ', $valueLiterals),
            implode(', ', $updateAssignments)
        );

        \Db::getInstance()->execute($sql);

        $this->logger->debug('Paysera payment row saved', [
            'order_id' => $orderId,
            'fields' => array_keys($fields),
        ]);
    }

    private function tableName(): string
    {
        return _DB_PREFIX_ . DatabaseInstaller::TABLE_PAYMENT;
    }

    /**
     * @return array<string, scalar>
     */
    private function extractNonNullFields(PayseraPayment $row): array
    {
        $fields = [];

        if ($row->getPayseraPaymentId() !== null) {
            $fields['paysera_payment_id'] = $row->getPayseraPaymentId();
        }
        if ($row->getPayseraOrderId() !== null) {
            $fields['paysera_order_id'] = $row->getPayseraOrderId();
        }
        if ($row->getPaymentLink() !== null) {
            $fields['payment_link'] = $row->getPaymentLink();
        }
        if ($row->getOriginalAmount() !== null) {
            $fields['original_amount'] = $row->getOriginalAmount();
        }
        if ($row->getOriginalCurrency() !== null) {
            $fields['original_currency'] = $row->getOriginalCurrency();
        }
        if ($row->getPaidAmount() !== null) {
            $fields['paid_amount'] = $row->getPaidAmount();
        }
        if ($row->getPaidCurrency() !== null) {
            $fields['paid_currency'] = $row->getPaidCurrency();
        }
        if ($row->getPayerName() !== null) {
            $fields['payer_name'] = $row->getPayerName();
        }
        if ($row->getPayerEmail() !== null) {
            $fields['payer_email'] = $row->getPayerEmail();
        }
        if ($row->getPaymentMethod() !== null) {
            $fields['payment_method'] = $row->getPaymentMethod();
        }
        if ($row->getStatus() !== null) {
            $fields['status'] = $row->getStatus();
        }

        return $fields;
    }

    private function toSqlLiteral(mixed $value): string
    {
        if (is_int($value) || is_float($value)) {
            return (string) $value;
        }

        return "'" . pSQL((string) $value) . "'";
    }

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

        return (string) $value;
    }

    private function nullableInt(mixed $value): ?int
    {
        if ($value === null || $value === '') {
            return null;
        }

        return (int) $value;
    }
}
