<?php

namespace App\Console\Commands\Market;

use App\Events\MarketStatsUpdated;
use App\Events\MarketTradeUpdated;
use App\Events\OrderBookSnapshot;
use App\Models\Market\Market;
use App\Models\Order\Order;
use App\Models\Transaction\Transaction;
use App\Repositories\Order\OrderRepository;
use App\Services\Market\MarketService;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class MarketCustomLiquidityCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'market:custom-token-liquidity {market}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    protected $firstStart = true;

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle($market = null)
    {
        if(!$market) {
            $market = $this->argument('market');
        }

        $model = Market::whereName($market)->first();

        if(!$model || !$model->custom_liquidity) return;

        $orderRepository = new OrderRepository();

        $ticketIncrement = 1;

        try {

            // Stream Orderbook
            while(true) {

                if($ticketIncrement > 10) $ticketIncrement = 1;

                $ticketIncrement++;

                $orders = $this->generateOrders(40, $model);

                list($asksCollection, $bidsCollection) = array_chunk($orders, ceil(count($orders) / 2));

                    // Asks
                    $asks = new Collection($asksCollection);

                    $ratio = $model->discount * 0.01;

                    $asks = $asks->map(function ($item, $key) use ($asks, $ratio, $model) {
                        return [
                            'price' => math_formatter(($asks[$key][0] + ($ratio * $asks[$key][0])), $model->quote_precision),
                            'quantity' => $asks[$key][1],
                        ];
                    });

                    Cache::put("markets_liquidity.$market.asks", $asks);
                    Cache::put("markets_liquidity.$market.asks_total", $asks->sum('quantity'));

                    // Bids
                    $bids = new Collection($bidsCollection);

                    $ratio = $model->discount_bid * 0.01;

                    $bids = $bids->map(function ($item, $key) use ($bids, $ratio, $model) {
                        return [
                            'price' => math_formatter(($bids[$key][0] + ($ratio * $bids[$key][0])), $model->quote_precision),
                            'quantity' => $bids[$key][1],
                        ];
                    });

                    Cache::put("markets_liquidity.$market.bids_total", $bids->sum('quantity'));
                    Cache::put("markets_liquidity.$market.bids", $bids);


                    $bidsModelCache = Cache::get("bidsModelCache.$market");
                    $bidsModelCacheUpdated = Cache::get("bidsModelCache.$market.updated");

                    if (!$bidsModelCache || $bidsModelCacheUpdated || $this->firstStart) {
                        Cache::put("bidsModelCache.$market", $orderRepository->get($market, Order::SIDE_BUY));
                        Cache::put("bidsModelCache.$market.updated", false);
                    }

                    $bidsModelCache = Cache::get("bidsModelCache.$market");

                    $bids = collect($bidsModelCache->map(function ($item) use ($model) {
                        return [
                            'price' => math_formatter($item->price, $model->quote_precision),
                            'quantity' => $item->quantity
                        ];
                    })
                        ->toArray())
                        ->merge(Cache::get("markets_liquidity.$market.bids"))
                        ->sortByDesc('price')
                        ->groupBy(['price'])->map(function ($item) use ($model) {
                            return ['price' => math_formatter($item->first()['price'], $model->quote_precision),
                                'quantity' => $item->sum('quantity')
                            ];
                        })->values();

                    $asksModelCache = Cache::get("asksModelCache.$market");
                    $asksModelCacheUpdated = Cache::get("asksModelCache.$market.updated");

                    if (!$asksModelCache || $asksModelCacheUpdated || $this->firstStart) {
                        Cache::put("asksModelCache.$market", $orderRepository->get($market, Order::SIDE_SELL));
                        Cache::put("asksModelCache.$market.updated", false);
                    }

                    $asksModelCache = Cache::get("asksModelCache.$market");

                    $asks = collect($asksModelCache
                        ->map(function ($item) use ($model) {
                            return [
                                'price' => math_formatter($item->price, $model->quote_precision),
                                'quantity' => $item->quantity
                            ];
                        })
                        ->toArray())
                        ->merge(Cache::get("markets_liquidity.$market.asks"))
                        ->sortBy('price')
                        ->groupBy(['price'])->map(function ($item) use ($model) {
                            return [
                                'price' => math_formatter($item->first()['price'], $model->quote_precision),
                                'quantity' => $item->sum('quantity')
                            ];
                        })->values();

                    $this->firstStart = false;

                    try {

                        event(new OrderBookSnapshot(
                            $market,
                            $bids,
                            $asks,
                        ));

                    } catch (\Exception $e) {

                    }

                if(rand(1, 5) == 1) {

                    $qPrice = $bids[0]['price'];
                    $pVol = $bids[0]['quantity'];
                    $qVol = math_multiply($qPrice, $pVol);

                    // Cursor order transaction
                    $cursorTransactions = [
                        'is_maker' => true,
                        'process_id' => generate_uuid(),
                        'order_id' => null,
                        'user_id' => null,
                        'market_id' => $model->id,
                        'order_type' => 'market',
                        'order_side' => rand(1,2) == 1 ? 'sell' : 'buy',
                        'fee' => 0,
                        'referral_fee' => 0,
                        'is_volume' => 0,
                        'price' => $qPrice,
                        'base_currency' => $pVol,
                        'quote_currency' => $qVol,
                    ];

                    $cursorTransaction = (new Transaction)->create($cursorTransactions);
                    event(new MarketTradeUpdated($cursorTransaction, false));
                    (new MarketService())->updateStats($model->id, $qPrice, $pVol, $qVol);
                    event(new MarketStatsUpdated($model));
                }

                sleep(2);
            }

        } catch (\Exception $e) {

            $this->info("Restart the market liquidity on exception");
            Log::error($e);
            return $this->handle();
        }

    }

    public function generateOrders($totalOrders, $market) {
        for($i=0; $i<$totalOrders; $i++) {
            $orders[] = [
                $this->randomFloat($market->custom_liquidity_start_price, $market->custom_liquidity_end_price, $market->quote_precision),
                $this->randomFloat($market->custom_liquidity_start_amount, $market->custom_liquidity_end_amount, $market->base_precision)
            ];
        }
        rsort($orders);

        return $orders;
    }

    public function randomFloat(int|float $min, int|float $max, $decimals = 2): float
    {
        $times = 10 ** $decimals;
        return (float) random_int(round($min * $times), round($max * $times)) / $times;
    }
}
