<?php

namespace App\Console\Commands\Market;

use App\Events\MarketTradeLiteUpdated;
use App\Events\MarketTradePressureUpdated;
use App\Events\OrderBookSnapshot;
use App\Models\Market\Market;
use App\Models\Order\Order;
use App\Repositories\Order\OrderRepository;
use App\Services\Liquidity\Binance\BinanceApi;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class MarketLiquidityCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'market:run-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) return;

        $marketName = market_sanitize($market);

        $binanceApi = new BinanceApi();

        $orderRepository = new OrderRepository();

        $ticketIncrement = 1;

        try {

            // Stream Orderbook
            $binanceApi->depthStream($marketName, function ($data, $symbol, $bidsCollection, $asksCollection) use ($model, $market, $orderRepository, &$ticketIncrement) {

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

                $ticketIncrement++;

                if($ticketIncrement % 3 == 0) {

                    // 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) {

                    }

                }
            });

            $marketsBuyTrades = 0;
            $marketsSellTrades = 0;

            $trigerredTime = 0;

            // Stream Trade Orders
            $binanceApi->tradeStream($marketName, function ($data, $symbol, $trade) use ($model, $market, $orderRepository, &$marketsBuyTrades, &$marketsSellTrades, &$trigerredTime) {

                $trigerredInitial = (int)($trade->T / 1000);

                if($trigerredInitial != $trigerredTime && ($trigerredTime+1) != $trigerredInitial) {

                    $trigerredTime = $trigerredInitial;

                    $tradeObject = (object)[];
                    $tradeObject->market = $model;
                    $tradeObject->order_side = $trade->m ? 'buy' : 'sell';
                    $tradeObject->price = $trade->p;
                    $tradeObject->base_currency = $trade->q;
                    $tradeObject->created_at = Carbon::createFromTimestampMs($trade->T);

                    try {
                        event(new MarketTradeLiteUpdated($tradeObject, false));
                    } catch (\Exception $e) {

                    }

                }

                try {

                    if($trade->m) {
                        $marketsBuyTrades++;
                    } else {
                        $marketsSellTrades++;
                    }

                    if(($marketsBuyTrades + $marketsSellTrades) >= 30) {

                        $buyPercentage = number_format($marketsBuyTrades / 30 * 100, 2, '.', '');
                        $sellPercentage = number_format($marketsSellTrades / 30 * 100, 2, '.', '');

                        event(new MarketTradePressureUpdated($model->name, $buyPercentage, $sellPercentage));

                        $marketsSellTrades = 0;
                        $marketsBuyTrades = 0;
                    }

                } catch (\Exception $e) {

                }
            });

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

    }
}
