<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

/**
 * @property string $balance
 * @property string $locked_balance
 * @property-read string $available_balance
 * @param numeric-string $amount
 */

class AccountCryptoBalance extends Model
{
    use HasFactory;

    protected $table = 'account_crypto_balances';

    protected $fillable = [
        'account_id',
        'code',
        'network',
        'balance',
        'locked_balance',
        'is_active',
    ];

    protected $casts = [
        'balance' => 'decimal:8',        // crypto precision
        'locked_balance' => 'decimal:8',
        'is_active' => 'boolean',
    ];

    /* =====================
     |  Relationships
     ===================== */

    public function account()
    {
        return $this->belongsTo(Account::class);
    }

    public function transactions()
    {
        return $this->hasMany(Transaction::class, 'crypto_balance_id');
    }
    public function deposits()
    {
        return $this->hasMany(Deposit::class, 'user_crypto_balance_id');
    }

    public function cryptoFiatSwaps()
    {
        return $this->hasMany(CryptoFiatSwap::class, 'crypto_balance_id');
    }

    /* =====================
     |  Accessors
     ===================== */

    public function getAvailableBalanceAttribute(): string
    {
        return bcsub(
            (string) $this->balance,
            (string) $this->locked_balance,
            8
        );
    }

    public function getDashboardBalancesAttribute(): array
    {
        return [
            'total' => (string) $this->balance,
            'locked' => (string) $this->locked_balance,
            'available' => $this->available_balance,
        ];
    }

    public function hasSufficientBalance(string $amount): bool
    {
        return bccomp(
            (string) $this->available_balance,
            (string) $amount,
            8
        ) >= 0;
    }

    public function lock(string $amount): void
    {
        if (!$this->hasSufficientBalance($amount)) {
            throw new \RuntimeException('Insufficient crypto balance.');
        }

        $this->balance = bcsub($this->balance, $amount, 8);
        $this->locked_balance = bcadd($this->locked_balance, $amount, 8);

        $this->save();
    }
    public function release(string $amount): void
    {
        $this->locked_balance = bcsub($this->locked_balance, $amount, 8);
        $this->balance = bcadd($this->balance, $amount, 8);

        $this->save();
    }
    public function finalize(string $amount): void
    {
        $this->locked_balance = bcsub($this->locked_balance, $amount, 8);
        $this->save();
    }

    public function credit(string $amount): void
    {
        $this->balance = bcadd($this->balance, $amount, 8);
        $this->save();
    }

    public function debit(string $amount): void
    {
        if (bccomp($this->balance, $amount, 8) < 0) {
            throw new \RuntimeException('Insufficient crypto balance.');
        }

        $this->balance = bcsub($this->balance, $amount, 8);
        $this->save();
    }

    public function label(): string
    {
        return strtoupper($this->code) . ' (' . strtoupper($this->network) . ')';
    }

    public function scopeActive($query)
    {
        return $query->where('is_active', true);
    }


    /**
     * Get the USD (or any fiat) rate for this crypto.
     */

    public function getRate(string $currency = 'usd'): string
    {
        $id = $this->getCoinGeckoId($this->code);
        $cacheKey = "crypto_rate_{$id}_{$currency}";

        return Cache::remember($cacheKey, 60, function () use ($id, $currency) {
            $attempts = 0;
            $maxAttempts = 3;

            while ($attempts < $maxAttempts) {
                try {
                    /** @var \Illuminate\Http\Client\Response $response */
                    $response = Http::timeout(5)->get('https://api.coingecko.com/api/v3/simple/price', [
                        'ids' => $id,
                        'vs_currencies' => $currency
                    ]);

                    if ($response->successful()) {
                        $data = $response->json();
                        $rate = $data[$id][$currency] ?? null;
                        if ($rate !== null && bccomp((string) $rate, '0', 8) > 0) {
                            return (string) $rate;
                        }
                    }
                } catch (\Throwable $e) {
                    report($e);
                }

                $attempts++;
                usleep(200_000 * $attempts); // wait longer on each retry
            }

            return '0';
        });
    }


    protected function getCoinGeckoId(string $code): string
    {
        return match (strtoupper($code)) {
            'BTC' => 'bitcoin',
            'ETH' => 'ethereum',
            'USDT' => 'tether',
            'LTC' => 'litecoin',
            default => strtolower($code)
        };
    }

    /**
     * Get balance in a fiat or another coin
     */
    public function balanceIn(string $currency = 'usd'): string
    {
        $rate = $this->getRate($currency);

        if (bccomp($rate, '0', 8) <= 0) {
            return '0';
        }

        return bcmul($this->balance, $rate, 8);
    }

    public function availableBalanceIn(string $currency = 'usd'): string
    {
        $rate = $this->getRate($currency);

        if (bccomp($rate, '0', 8) <= 0) {
            return '0';
        }

        return bcmul($this->available_balance, $rate, 8);
    }

    public function cryptoDeposits()
    {
        return $this->hasMany(\App\Models\Deposit::class, 'user_crypto_balance_id')
            ->where('deposit_type', 'crypto');
    }

    public function fiatDeposits()
    {
        return $this->hasMany(\App\Models\Deposit::class, 'user_crypto_balance_id')
            ->where('deposit_type', 'fiat');
    }

}

