<?php

namespace App\Controllers;

use App\Libraries\NowPaymentsService;
use App\Models\PaymentTransactionModel;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\RESTful\ResourceController;

/**
 * Proxy for NOWPayments API (api.nowpayments.io/v1).
 *
 * @see https://documenter.getpostman.com/view/7907941/2s93JusNJt
 */
class NowPayments extends ResourceController
{
    protected NowPaymentsService $nowPayments;

    public function __construct()
    {
        $this->nowPayments = new NowPaymentsService();
    }

    private function jsonBody(): ?array
    {
        $json = $this->request->getJSON(true);
        if ($json !== null) {
            return $json;
        }
        $post = $this->request->getPost();
        if (!empty($post)) {
            return $post;
        }

        return null;
    }

    private function respondResult(array $result, int $successCode = 200): ResponseInterface
    {
        $code = $result['success'] ? $successCode : ($result['status_code'] ?? 400);
        if ($code < 100 || $code > 599) {
            $code = 400;
        }
        $response = [
            'success' => $result['success'],
            'message' => $result['message'],
        ];
        if (array_key_exists('data', $result)) {
            $response['data'] = $result['data'];
        }

        return $this->respond($response, $code);
    }

    public function createPayment()
    {
        $json = $this->jsonBody();
        if ($json === null || $json === []) {
            return $this->respond(['success' => false, 'message' => 'Invalid or empty JSON body'], 400);
        }
        foreach (['price_amount', 'price_currency', 'pay_currency'] as $field) {
            if (!array_key_exists($field, $json) || $json[$field] === '' || $json[$field] === null) {
                return $this->respond(['success' => false, 'message' => "Missing required field: {$field}"], 400);
            }
        }
        if (!is_numeric($json['price_amount'])) {
            return $this->respond(['success' => false, 'message' => 'price_amount must be numeric'], 400);
        }

        return $this->respondResult($this->nowPayments->createPayment($json));
    }

    public function paymentStatus($paymentId = null)
    {
        if (empty($paymentId)) {
            return $this->respond(['success' => false, 'message' => 'payment_id is required'], 400);
        }

        return $this->respondResult($this->nowPayments->getPaymentStatus($paymentId));
    }

    public function balance()
    {
        return $this->respondResult($this->nowPayments->getBalance());
    }

    public function payoutValidateAddress()
    {
        $json = $this->jsonBody();
        if ($json === null || $json === []) {
            return $this->respond(['success' => false, 'message' => 'Invalid or empty JSON body'], 400);
        }
        foreach (['address', 'currency'] as $field) {
            if (!array_key_exists($field, $json) || $json[$field] === '' || $json[$field] === null) {
                return $this->respond(['success' => false, 'message' => "Missing required field: {$field}"], 400);
            }
        }
        if (!array_key_exists('extra_id', $json)) {
            $json['extra_id'] = null;
        }

        return $this->respondResult($this->nowPayments->validatePayoutAddress($json));
    }

    public function payoutCreate()
    {
        $json = $this->jsonBody();
        if ($json === null || $json === []) {
            return $this->respond(['success' => false, 'message' => 'Invalid or empty JSON body'], 400);
        }
        if (empty($json['withdrawals']) || !is_array($json['withdrawals'])) {
            return $this->respond(['success' => false, 'message' => 'withdrawals must be a non-empty array'], 400);
        }
        foreach ($json['withdrawals'] as $i => $row) {
            if (!is_array($row)) {
                return $this->respond(['success' => false, 'message' => "withdrawals[{$i}] must be an object"], 400);
            }
            foreach (['address', 'currency', 'amount'] as $field) {
                if (!array_key_exists($field, $row) || $row[$field] === '' || $row[$field] === null) {
                    return $this->respond(['success' => false, 'message' => "withdrawals[{$i}].{$field} is required"], 400);
                }
            }
        }

        return $this->respondResult($this->nowPayments->createPayout($json));
    }

    public function payoutVerify($batchWithdrawalId = null)
    {
        if (empty($batchWithdrawalId)) {
            return $this->respond(['success' => false, 'message' => 'batch_withdrawal_id is required'], 400);
        }
        $json = $this->jsonBody();
        if ($json === null) {
            $json = [];
        }
        if (!isset($json['verification_code']) || $json['verification_code'] === '' || $json['verification_code'] === null) {
            return $this->respond(['success' => false, 'message' => 'verification_code is required'], 400);
        }

        return $this->respondResult($this->nowPayments->verifyPayout($batchWithdrawalId, $json));
    }

    public function payoutStatus($payoutId = null)
    {
        if (empty($payoutId)) {
            return $this->respond(['success' => false, 'message' => 'payout_id is required'], 400);
        }

        return $this->respondResult($this->nowPayments->getPayoutStatus($payoutId));
    }

    public function fiatProviders()
    {
        return $this->respondResult($this->nowPayments->fiatPayoutProviders());
    }

    public function fiatCurrencies()
    {
        return $this->respondResult($this->nowPayments->fiatPayoutFiatCurrencies());
    }

    public function fiatCryptoCurrencies()
    {
        $q = $this->request->getGet();
        $query = array_filter([
            'provider' => $q['provider'] ?? null,
            'currency' => $q['currency'] ?? null,
        ], static fn ($v) => $v !== null && $v !== '');

        return $this->respondResult($this->nowPayments->fiatPayoutCryptoCurrencies($query));
    }

    public function fiatPaymentMethods()
    {
        $q = $this->request->getGet();
        $query = [
            'provider' => array_key_exists('provider', $q) ? $q['provider'] : 'null',
            'currency' => array_key_exists('currency', $q) ? $q['currency'] : 'null',
        ];

        return $this->respondResult($this->nowPayments->fiatPayoutPaymentMethods($query));
    }

    public function fiatCreateAccount()
    {
        $json = $this->jsonBody();
        if ($json === null || $json === []) {
            return $this->respond(['success' => false, 'message' => 'Invalid or empty JSON body'], 400);
        }
        foreach (['currency', 'paymentCode', 'provider', 'countryCode'] as $field) {
            if (!array_key_exists($field, $json) || $json[$field] === '' || $json[$field] === null) {
                return $this->respond(['success' => false, 'message' => "Missing required field: {$field}"], 400);
            }
        }
        if (!isset($json['fields']) || !is_array($json['fields'])) {
            return $this->respond(['success' => false, 'message' => 'fields must be an object'], 400);
        }

        return $this->respondResult($this->nowPayments->fiatPayoutCreateAccount($json));
    }

    public function fiatRequestPayout()
    {
        $json = $this->jsonBody();
        if ($json === null || $json === []) {
            return $this->respond(['success' => false, 'message' => 'Invalid or empty JSON body'], 400);
        }
        foreach (['fiatCurrency', 'cryptoCurrency', 'amount', 'provider'] as $field) {
            if (!array_key_exists($field, $json) || $json[$field] === '' || $json[$field] === null) {
                return $this->respond(['success' => false, 'message' => "Missing required field: {$field}"], 400);
            }
        }
        if (!is_numeric($json['amount'])) {
            return $this->respond(['success' => false, 'message' => 'amount must be numeric'], 400);
        }

        return $this->respondResult($this->nowPayments->fiatPayoutRequest($json));
    }

    public function fiatList()
    {
        return $this->respondResult($this->nowPayments->fiatPayoutList($this->request->getGet()));
    }

    public function health()
    {
        return $this->respond([
            'success' => true,
            'message' => 'OK',
            'data' => [
                'configured' => $this->nowPayments->isConfigured(),
                'bearer_configured' => $this->nowPayments->isBearerConfigured(),
                'bearer_static' => $this->nowPayments->isBearerStaticConfigured(),
                'bearer_via_dashboard_auth' => $this->nowPayments->isAuthPasswordConfigured(),
                'ipn_secret_configured' => $this->nowPayments->isIpnSecretConfigured(),
                'service' => 'nowpayments',
            ],
        ], 200);
    }

    /**
     * NOWPayments IPN (Instant Payment Notification).
     * POST /api/nowpayments/ipn
     * Configure URL + IPN secret in NOWPayments dashboard. Verifies x-nowpayments-sig (HMAC-SHA512 of sorted JSON body).
     *
     * @see https://nowpayments.io/help/what-is/what-is-ipn
     */
    public function ipn()
    {
        $raw = $this->request->getBody();
        if ($raw === null || trim($raw) === '') {
            return $this->respond(['success' => false, 'message' => 'Empty body'], 400);
        }

        $sigHeader = $this->request->getHeaderLine('x-nowpayments-sig');
        if (!$this->nowPayments->verifyIpnSignature($raw, $sigHeader !== '' ? $sigHeader : null)) {
            log_message('warning', 'NOWPayments IPN rejected: invalid or missing x-nowpayments-sig');

            return $this->respond(['success' => false, 'message' => 'Invalid signature'], 403);
        }

        $data = json_decode($raw, true);
        if (!is_array($data)) {
            return $this->respond(['success' => false, 'message' => 'Invalid JSON'], 400);
        }

        $pid = $data['payment_id'] ?? $data['payout_id'] ?? '';
        $pst = $data['payment_status'] ?? $data['status'] ?? '';
        log_message('info', 'NOWPayments IPN ok payment_id=' . $pid . ' status=' . $pst);

        $this->applyNowpaymentsIpnToTransaction($data);

        return $this->respond(['success' => true, 'message' => 'OK'], 200);
    }

    /**
     * @param array<string, mixed> $data
     */
    private function applyNowpaymentsIpnToTransaction(array $data): void
    {
        $orderId = isset($data['order_id']) ? trim((string) $data['order_id']) : '';
        if ($orderId === '') {
            return;
        }

        $model = new PaymentTransactionModel();
        $row = $model->where('payment_method', 'nowpayments')
            ->groupStart()
            ->where('transaction_id', $orderId)
            ->orWhere('reference', $orderId)
            ->groupEnd()
            ->first();

        if ($row === null) {
            $fallback = new PaymentTransactionModel();
            $row = $fallback->groupStart()
                ->where('transaction_id', $orderId)
                ->orWhere('reference', $orderId)
                ->groupEnd()
                ->first();
        }

        if ($row === null) {
            log_message('info', 'NOWPayments IPN: no local row for order_id=' . $orderId);

            return;
        }

        $paymentStatus = strtolower((string) ($data['payment_status'] ?? ''));
        $ourStatus = 'PENDING';
        if (in_array($paymentStatus, ['finished', 'confirmed'], true)) {
            $ourStatus = 'SUCCESS';
        } elseif (in_array($paymentStatus, ['failed', 'expired', 'refunded'], true)) {
            $ourStatus = 'FAILED';
        }

        $meta = [];
        if (!empty($row['metadata'])) {
            $decoded = is_string($row['metadata']) ? json_decode($row['metadata'], true) : $row['metadata'];
            $meta = is_array($decoded) ? $decoded : [];
        }
        $meta['nowpayments_ipn'] = [
            'received_at' => date('c'),
            'payment_id' => $data['payment_id'] ?? null,
            'payment_status' => $data['payment_status'] ?? null,
            'pay_amount' => $data['pay_amount'] ?? null,
            'actually_paid' => $data['actually_paid'] ?? null,
        ];

        $update = [
            'status' => $ourStatus,
            'updated_at' => date('Y-m-d H:i:s'),
            'metadata' => json_encode($meta),
        ];
        if (!empty($data['payment_id'])) {
            $update['gateway_reference'] = (string) $data['payment_id'];
        }

        (new PaymentTransactionModel())->update((int) $row['id'], $update);
    }
}