<?php

namespace Controllers;

use Libs\ResponseLib;
use Models\CprModel;
use Models\CommodityModel;
use Models\StatusModel;
use Models\PaymentModel;
use Psr\Http\Message\ServerRequestInterface;
use Services\TokenCreateService;

class CprFastTrackController
{
    private \PDO $pdo;
    private CprModel $cprModel;
    private CommodityModel $commodityModel;
    private StatusModel $statusModel;
    private PaymentModel $paymentModel;
    private TokenCreateService $tokenCreateService;

    public function __construct()
    {
        if (!isset($GLOBALS['pdo']) || !$GLOBALS['pdo'] instanceof \PDO) {
            throw new \RuntimeException('Global PDO connection not initialized');
        }

        $this->pdo = $GLOBALS['pdo'];
        $this->cprModel = new CprModel();
        $this->commodityModel = new CommodityModel();
        $this->statusModel = new StatusModel();
        $this->paymentModel = new PaymentModel();
        $this->tokenCreateService = new TokenCreateService();
    }

    public function __invoke(ServerRequestInterface $request)
    {
        $body = json_decode((string)$request->getBody(), true) ?? [];
        $cprPayload = isset($body['cpr']) && is_array($body['cpr']) ? $body['cpr'] : $body;

        $userId = isset($body['user_id']) ? (int)$body['user_id'] : 0;
        $companyId = isset($body['company_id']) ? (int)$body['company_id'] : 0;
        if ($userId <= 0 || $companyId <= 0) {
            $pair = $this->findAnyActiveUser();
            $userId = $pair['user_id'];
            $companyId = $pair['company_id'];
        }

        try {
            $statusId = $this->statusModel->getIdByStatus('pending');
            if ($statusId === null) {
                throw new \RuntimeException('Pending status not found');
            }

            $paymentExternal = 'FAST_' . bin2hex(random_bytes(6));
            $paymentId = $this->paymentModel->create(
                $paymentExternal,
                PaymentModel::STATUS_COMPLETED,
                $userId,
                time(),
                '',
                'fast-track'
            );

            $cpr = $this->cprModel->create($cprPayload, $statusId, $paymentId, $userId, $companyId);

            $inputs = $this->prepareTokenInputs($cpr, $companyId, $userId);
            $token = $this->tokenCreateService->createToken(
                $inputs['token_commodities_amount'],
                $inputs['token_commodities_value'],
                $inputs['token_uf'],
                $inputs['token_city'],
                (string)$cpr['cpr_id'],
                '',
                $inputs['wallet_id'],
                $inputs['chain_id'],
                $inputs['commodities_id'],
                (int)$cpr['cpr_id'],
                $userId
            );

            $this->cprModel->updateTokenId((int)$cpr['cpr_id'], (int)$token['token_id']);

            return ResponseLib::sendOk([
                'message' => 'CPR registrada e token mintado (fast-track)',
                'cpr_id' => (int)$cpr['cpr_id'],
                'payment_id' => $paymentId,
                'token_id' => (int)$token['token_id'],
                'token_external_id' => $token['token_external_id'],
                'tx_hash' => $token['tx_hash'],
            ], 'S_CPR_FAST');

        } catch (\Throwable $e) {
            return ResponseLib::sendFail('Falha no fast-track: ' . $e->getMessage(), [], 'E_FAST_TRACK')->withStatus(500);
        }
    }

    private function findAnyActiveUser(): array
    {
        $stmt = $this->pdo->query("SELECT user_id, company_id FROM \"user\" WHERE user_flag = 'a' ORDER BY user_id LIMIT 1");
        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
        if (!$row) {
            throw new \RuntimeException('Nenhum usuário ativo encontrado');
        }
        return ['user_id' => (int)$row['user_id'], 'company_id' => (int)$row['company_id']];
    }

    private function prepareTokenInputs(array $cpr, int $companyId, int $userId): array
    {
        $wallet = $this->findWalletByCompanyId($companyId);

        $commoditiesName = $this->requireStringField($cpr, ['cpr_product_name'], 'cpr_product_name');
        $commoditiesId = $this->resolveCommodityId($commoditiesName);

        $amount = $this->requireNumericField($cpr, ['cpr_product_quantity', 'cpr_issue_quantity'], 'quantidade do produto');
        $value = $this->requireNumericField($cpr, ['cpr_issue_value', 'cpr_issue_financial_value'], 'valor do produto');
        $uf = $this->requireStringField($cpr, ['cpr_deliveryPlace_state_acronym', 'cpr_issuers_state_acronym'], 'UF');
        $city = $this->requireStringField($cpr, ['cpr_deliveryPlace_city_name', 'cpr_issuers_city_name'], 'cidade');

        return [
            'token_commodities_amount' => $amount,
            'token_commodities_value' => $value,
            'token_uf' => $uf,
            'token_city' => $city,
            'wallet_id' => $wallet['wallet_id'],
            'chain_id' => $wallet['chain_id'],
            'commodities_id' => $commoditiesId,
            'user_id' => $userId,
        ];
    }

    private function findWalletByCompanyId(int $companyId): array
    {
        $stmt = $this->pdo->prepare('SELECT wallet_id, chain_id FROM "wallet" WHERE company_id = :company_id ORDER BY wallet_id ASC LIMIT 1');
        $stmt->execute(['company_id' => $companyId]);
        $wallet = $stmt->fetch(\PDO::FETCH_ASSOC);
        if (!$wallet) {
            throw new \RuntimeException('Nenhuma carteira encontrada para a empresa informada');
        }
        return ['wallet_id' => (int)$wallet['wallet_id'], 'chain_id' => (int)$wallet['chain_id']];
    }

    private function resolveCommodityId(string $name): int
    {
        $id = $this->commodityModel->getIdByName($name);
        if ($id === null) {
            throw new \RuntimeException('Commodity não encontrada para o produto: ' . $name);
        }
        return $id;
    }

    private function requireStringField(array $cpr, array $candidates, string $label): string
    {
        foreach ($candidates as $field) {
            if (array_key_exists($field, $cpr)) {
                $v = $this->normalizeStringValue($cpr[$field]);
                if ($v !== '') {
                    return $v;
                }
            }
        }
        throw new \InvalidArgumentException("Campo {$label} ausente ou inválido na CPR.");
    }

    private function requireNumericField(array $cpr, array $candidates, string $label): int
    {
        foreach ($candidates as $field) {
            if (array_key_exists($field, $cpr)) {
                $v = $this->normalizeNumericValue($cpr[$field]);
                if ($v !== null) {
                    return $v;
                }
            }
        }
        throw new \InvalidArgumentException("Campo {$label} ausente ou inválido na CPR.");
    }

    private function normalizeStringValue($value): string
    {
        if (is_array($value)) {
            $value = reset($value);
        }
        if (!is_scalar($value)) {
            return '';
        }
        $s = trim((string)$value);
        if ($s === '') {
            return '';
        }
        $parts = preg_split('/\s*;\s*/', $s) ?: [];
        $first = $parts[0] ?? $s;
        return trim((string)$first);
    }

    private function normalizeNumericValue($value): ?int
    {
        if (is_array($value)) {
            $value = reset($value);
        }
        if (is_string($value)) {
            $value = str_replace([' ', ','], ['', '.'], $value);
        }
        if (is_numeric($value)) {
            return (int)round((float)$value);
        }
        return null;
    }
}
