<?php

namespace App\Console;

use App\Models\User;
use App\Models\Admin;
use App\Models\Bid;
use App\Models\Sell;
use App\Models\Investment;
use App\Models\Ownership;
use App\Models\Product;
use App\Models\Market;
use Illuminate\Support\Str;
use Carbon\Carbon;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Database\Eloquent\Builder;

class Kernel extends ConsoleKernel
{
    protected function schedule(Schedule $schedule)
    {
        $schedule->call(function () {
            $this->runManualBiddingProcess();
        })
            ->everyMinute()
            ->name('run-manual-bids')
            ->withoutOverlapping();

        $schedule->call(function () {
            $this->runManualSellingProcess();
        })
            ->everyMinute()
            ->name('run-manual-sells')
            ->withoutOverlapping();

        $schedule->call(function () {
            // AI make bids and sells
            $this->runAiBiddingProcess();
            $this->runAiSellingProcess();
        })
            ->everyMinute()
            ->name('run-ai-bids-and-sells')
            ->withoutOverlapping();

        // AI process bids
        $schedule->call(function () {
            $bids = Bid::with(['market', 'product'])->where([
                'type' => 'ai_bid',
                'status' => 0
            ])->take(100)->get();

            if (count($bids) > 0)
                foreach ($bids as $manualBid) {
                    $user = User::find($manualBid->user_id);
                    if (is_null($user)) {
                        $manualBid->delete();

                        continue;
                    }

                    $bidTime = Carbon::parse($manualBid->bid_time);
                    $bidTimePlus24Hours = $bidTime->copy()->addHours(24);

                    if (Carbon::now()->gte($bidTimePlus24Hours)) {
                        $this->processAiBid($manualBid, $user);
                    }
                }
        })
            ->everyMinute()
            ->name('process-ai-bids')
            ->withoutOverlapping();

        // AI process sells
        $schedule->call(function () {
            // $sells = Sell::with(['market', 'product', 'ownership'])->where([
            //     'type' => 'ai_sell',
            //     'status' => 0
            // ])->take(100)->get();
            $sells = Sell::with(['market', 'product', 'ownership'])->where([
                'type' => 'ai_sell',
                'status' => 0
            ])->get();

            if (count($sells) > 0)
                foreach ($sells as $sell) {
                    $user = User::find($sell->user_id);
                    if (is_null($user)) {
                        $sell->delete();

                        continue;
                    }

                    $timestamp = Carbon::parse($sell->sell_time);
                    $timestampPlusOneHour = $timestamp->addHours(1);

                    if (Carbon::now()->greaterThan($timestampPlusOneHour)) {
                        $this->processAiSell($sell, $user);
                    }
                }
        })
            ->everyMinute()
            ->name('process-ai-sells')
            ->withoutOverlapping();



        // finish up completed ai trades
        $schedule->call(function () {
            $this->finishCompletedAiTrades();
        })
            ->everyMinute()
            ->name('finish-completed-ai-trades')
            ->withoutOverlapping();
    }

    /**
     * Runs the manual bidding process.
     *
     * This method retrieves all manual bids with a status of 0 and processes
     * each bid if 24 hours have passed since the bid time. For each bid,
     * the associated user is retrieved, and the bid is processed accordingly.
     *
     * @return void
     */
    private function runManualBiddingProcess(): void
    {
        $manualBids = Bid::with(['market', 'product'])
            ->where('type', 'manual_bid')
            ->where('status', 0)
            ->get();

        if (count($manualBids) > 0)
            foreach ($manualBids as $manualBid) {
                $user = User::findOrFail($manualBid->user_id);
                if (is_null($user)) {
                    $manualBid->delete();

                    continue;
                }
                
                // banned user
                if($user->status == 0)
                    continue; 

                $bidTime = Carbon::parse($manualBid->bid_time);
                $bidTimePlus24Hours = $bidTime->copy()->addHours(24);

                if (Carbon::now()->gt($bidTimePlus24Hours)) {
                    $this->processManualBid($manualBid, $user);
                }
            }
    }

    /**
     * Process a manual bid.
     *
     * @param  mixed  $bid
     * @param  User  $user
     * @return void
     */
    private function processManualBid($bid, $user)
    {
        $margin = $bid->product->value - ($bid->market->bid_margin / 100) * $bid->product->value;

        if ($bid->amount >= $margin) {
            $bid->update([
                'status' => 1
            ]);

            Ownership::create([
                'slug' => Str::random(9),
                'user_id' => $user->id,
                'product_id' => $bid->product->id,
                'value' => $bid->amount,
                'quantity' => $bid->quantity,
                'type' => 'manual',
            ]);

            $owner = $bid->product->user;
            $quantityToUpdate = 0;
            $quantityObtained = 0;

            if (!is_null($bid->product->quantity) && $bid->product->quantity >= $bid->quantity) {
                $quantityToUpdate = $bid->product->quantity - $bid->quantity;
                $quantityObtained = $bid->quantity;
            } else {
                $quantityToUpdate = 0;
                $quantityObtained = $bid->product->quantity;
            }

            $bidProduct = tap($bid->product)->update([
                'quantity' => $quantityToUpdate,
            ]);

            $profit = (5 / 100) * $bidProduct->value;
            $withprofit = $profit + $bidProduct->value;
            $totalAmount = $withprofit * $quantityObtained;

            if (!is_null($owner))
                $owner->update([
                    'balance' => $owner->balance + $totalAmount,
                ]);
        } else {
            $total = $bid->amount * $bid->quantity;

            $user->update([
                'balance' => $user->balance + $total,
            ]);

            $bid->update([
                'status' => 2
            ]);
        }
    }

    /**
     * Process all manual sells that are 1 hour old.
     *
     * Manual sells are processed after 1 hour of the sell time. 
     *
     * @return void
     */
    private function runManualSellingProcess()
    {
        $sells = Sell::with(['market', 'product', 'ownership'])->where([
            'type' => 'manual_sell',
            'status' => 0
        ])->get();

        if (count($sells) > 0)
            foreach ($sells as $sell) {
                $user = User::find($sell->user_id);
                if (is_null($user)) {
                    $sell->delete();

                    continue;
                }
                
                // banned user
                if($user->status == 0)
                    continue; 

                $timestamp = Carbon::parse($sell->sell_time);
                $timestampPlusOneHour = $timestamp->addHours(1);

                if (Carbon::now()->greaterThan($timestampPlusOneHour)) {
                    $this->processManualSell($sell, $user);
                }
            }
    }

    /**
     * Process a manual sell.
     *
     * @param  mixed  $sell
     * @param  User  $user
     * @return void
     */
    private function processManualSell($sell, User $user)
    {
        $margin = $sell->initial_price + (($sell->market->sell_margin / 100) * $sell->initial_price);

        if ($sell->amount <= $margin) {
            $sell->update([
                'status' => 1,
            ]);

            $sell->ownership->delete();
            $total = $sell->amount * $sell->quantity;
            $profit = $sell->amount - $sell->initial_price;

            //Fund User
            $user->update([
                'balance' => $user->balance + $total,
            ]);

            // Fund agent
            $agentProfit = ($profit / 2) * 0.15;
            $agent = Admin::where('role_id', 4)->first();
            if (!is_null($agent))
                $this->fundAgent($agent, $agentProfit);

            //Fund Referrers
            $this->fundReferrers($user, $profit);
        } else {
            $sell->update([
                'status' => 2,
            ]);
        }
    }

    /**
     * Run the AI bidding process.
     *
     * This method retrieves all AI investments with a status of 2 and
     * processes each bot if the end date is greater than today. For
     * each bot, a random product is selected and a bid is placed with
     * a random amount between 1% - 5% of the product's value. The bot's
     * balance is then updated by subtracting the bid amount. The bot
     * will not place a bid if the bot's balance is less than the bid
     * amount or if the bot is not funded.
     *
     * @return void
     */
    private function runAiBiddingProcess()
    {
        $aiBots = Investment::where('status', 2)->take(100)->get();
        $products = Product::inRandomOrder()->get();
        $today = Carbon::now();

        if (count($aiBots) < 1) return;

        foreach ($aiBots as $bot) {
            $user = User::find($bot->user_id);
            if (is_null($user)) 
                continue;
            
            // banned user
            if($user->status == 0)
                continue; 

            $endDate = Carbon::parse($bot->end_date);
            if ($endDate->greaterThan($today)) {
                if ($bot->funded == 1) {
                    foreach ($products as $product) {
                        if (!is_null($product) && !is_null($product->value)) {
                            $rand = rand(1, 5);
                            $amount = ($rand / 100) * $product->value + $product->value;
                            $botBal = $bot->balance;
                            if ($botBal >= $amount && $amount > 0) {
                                Bid::create([
                                    'user_id' => $user->id,
                                    'store_name' => $user->username,
                                    'product_id' => $product->id,
                                    'market_id' => $product->market->id,
                                    'investment_id' => $bot->id,
                                    'amount' => $amount,
                                    'quantity' => 1,
                                    'status' => 0,
                                    'type' => 'ai_bid',
                                    'bid_time' => now(),
                                ]);

                                $bot->update([
                                    'balance' => $bot->balance - $amount,
                                ]);
                            } else continue;
                        } else continue;
                    }
                }
            } else {
                $bot->update([
                    'status' => 3,
                ]);
                
                $newBal = $bot->f_pfit > 0 ? $bot->amount + $bot->f_pfit - $bot->moved : $bot->amount + $bot->pfit - $bot->moved;
                $bot->update([
                    'balance' => $newBal,
                ]);
                
            }
        }
    }

    /**
     * Process an AI bid.
     *
     * @param  mixed  $bid
     * @param  User  $user
     * @return void
     */
    private function processAiBid($bid, User $user)
    {
        $bid->update([
            'status' => 1
        ]);

        Ownership::create([
            'slug' => Str::random(9),
            'user_id' => $user->id,
            'product_id' => $bid->product->id,
            'investment_id' => $bid->investment_id,
            'value' => $bid->amount,
            'quantity' => $bid->quantity,
            'type' => 'ai',
        ]);
    }

    /**
     * Run the AI selling process.
     *
     * This method retrieves all AI investments with a status of 2 and
     * processes each bot if the end date is greater than today. For
     * each bot, a random market is selected and a sell is placed with
     * a random amount between 1% - 5% of the product's value.
     *
     * @return void
     */
    private function runAiSellingProcess()
    {
        $aiBots = Investment::where('status', 2)->get();
        if (count($aiBots) < 1) return;

        $ownerships = null;

        foreach ($aiBots as $bot) {
            $user = User::find($bot->user_id);
            if (is_null($user)) 
                continue;
                
            // banned user
            if($user->status == 0)
                continue; 

            $ownerships = Ownership::with('product')->where([
                'user_id' => $user->id,
                'investment_id' => $bot->id,
            ])->get();

            if (count($ownerships) < 1) continue;

            $this->establishOwnership($user, $bot, $ownerships);

            // If completed AI trade
            $endDate = Carbon::parse($bot->end_date);
            $today = Carbon::now();
            // $ownerships = Ownership::with('product')->where([
            //     'user_id' => $user->id,
            //     'investment_id' => $bot->id,
            // ])->get();

            if ($today->greaterThan($endDate) && count($ownerships) < 1) {
                $bot->update([
                    'status' => 3,
                ]);
                
                $newBal = $bot->f_pfit > 0 ? $bot->amount + $bot->f_pfit - $bot->moved : $bot->amount + $bot->pfit - $bot->moved;
                $bot->update([
                    'balance' => $newBal,
                ]);
            }
        }
    }

    /**
     * Process an AI sell.
     *
     * @param  mixed  $sell
     * @param  User  $user
     * @return void
     */
    private function processAiSell($sell, User $user)
    {
        $bot = Investment::find($sell->investment_id);
        if (is_null($bot)) return;

        $sell->update([
            'status' => 1,
        ]);

        $total = $sell->amount * $sell->quantity;
        $profit = ($sell->amount - $sell->initial_price) * $sell->quantity;

        //Fund AI
        $bot->update([
            'pause_time' => now(),
            'balance' => $bot->balance + $total, // OLD CODE 30-05-2025
            // 'balance' => $bot->f_pfit > 0 ? ($bot->amount + $bot->f_pfit) : ($bot->amount + $bot->pfit),
            'pfit' => $bot->pfit + $profit
        ]);

        //Delete Ownership
        if (!is_null($sell->ownership))
            $sell->ownership->delete();

        // Fund agent
        $agentProfit = $profit / 2 * 0.15;
        $agent = Admin::where('role_id', 4)->first();
        if (!is_null($agent))
            $this->fundAgent($agent, $agentProfit);

        //Fund Referrers
        $this->fundReferrers($user, $profit);
    }

    /**
     * Finish up all completed AI trades.
     *
     * @return void
     */
    private function finishCompletedAiTrades()
    {
        $completedBots = Investment::where('status', 3)->get();

        if (count($completedBots) < 1) return;

        foreach ($completedBots as $bot) {
            $user = User::find($bot->user_id);
            if (is_null($user)) 
                continue;

            // For ownerships
            $ownerships = Ownership::with('product')->where([
                'user_id' => $user->id,
                'investment_id' => $bot->id,
            ])->get();

            if (count($ownerships) > 0) {
                $this->establishOwnership($user, $bot, $ownerships);
            }

            // For bids
            $bids = Bid::with(['product', 'market'])->where([
                'investment_id' => $bot->id,
                'user_id' => $user->id,
                'status' => 0,
            ])->get();

            if (count($bids) > 0) {
                foreach ($bids as $bid) {
                    $this->processAiBid($bid, $user);
                }
            }

            // For sells
            $sells = Sell::with(['market', 'product', 'ownership'])->where([
                'user_id' => $user->id,
                'investment_id' => $bot->id,
                'status' => 0,
            ])->get();

            if (count($sells) > 0) {
                foreach ($sells as $sell) {
                    $this->processAiSell($sell, $user);
                }
            }
            
            // Update balance based on f_pfit or pfit after completing AI trade
            $expDate = Carbon::parse($bot->end_date);
            if($expDate->isToday() && $bot->moved < 1) {
                $bot->update([
                    'balance' => $bot->f_pfit > 0 ? $bot->amount + $bot->f_pfit - $bot->moved : $bot->amount + $bot->pfit - $bot->moved,
                ]);
            }
        }
    }

    /**
     * Establish ownership for traded product.
     *
     * This method loops through all the given ownerships and creates a sell
     * for each one that does not already have a sell with a status of 0.
     * The sell is created with a random market, the user's id, the
     * ownership's id, the product's id, the bot's id, the ownership's value,
     * the amount to sell (which is the ownership's value plus the bot's plan
     * limit percentage), the ownership's quantity, and a status of 0.
     *
     * @param  User  $user
     * @param  Investment  $bot
     * @param  Builder[]|\Illuminate\Database\Eloquent\Collection  $ownerships
     * @return void
     */
    private function establishOwnership($user, $bot, $ownerships)
    {
        foreach ($ownerships as $ownership) {
            if ($ownership->sells()->where('status', 0)->doesntExist()) {
                $market = Market::where('type', 'sell')->inRandomOrder()->first();
                if (is_null($market)) continue;

                $limit = ($bot->plan->limit_max / 100) * $ownership->value;
                $amount = $limit + $ownership->value;
                Sell::create([
                    'user_id' => $user->id,
                    'ownership_id' => $ownership->id,
                    'product_id' => $ownership->product->id,
                    'market_id' => $market->id,
                    'investment_id' => $bot->id,
                    'initial_price' => $ownership->value,
                    'amount' => $amount,
                    'quantity' => $ownership->quantity,
                    'status' => 0,
                    'type' => 'ai_sell',
                    'sell_time' => now(),
                ]);
            }
        }
    }

    /**
     * Increase the balance of a given agent by a given amount.
     * 
     * @param Admin $agent The agent to increase the balance of.
     * @param float $amount The amount to increase the balance by.
     * @return void
     */
    private function fundAgent($agent, $amount)
    {
        $agent->balance += $amount;
        $agent->save();
    }

    /**
     * Distributes a specified amount among a user's referrers based on their rebate percentages.
     *
     * This function calculates the fund amount for each level of referrers
     * (up to three levels) using their respective rebate percentages and updates
     * their rebate balances.
     *
     * @param User $user The user whose referrers will receive the fund.
     * @param float $amount The total amount to be distributed among the referrers.
     * @return void
     * @throws \Exception
     */
    private function fundReferrers($user, $amount)
    {
        $referrers = [
            $user->referrer,
            $user->referrer?->referrer,
            $user->referrer?->referrer?->referrer,
        ];

        $rebatePercentages = [
            $user->referrer?->first_rebate,
            $user->referrer?->referrer?->second_rebate,
            $user->referrer?->referrer?->referrer?->third_rebate,
        ];

        // Loop through each referrer level
        foreach ($referrers as $index => $referrer) {
            // Check if the referrer exists and the rebate percentage is available
            if ($referrer instanceof User && isset($rebatePercentages[$index])) {
                $fundAmount = $rebatePercentages[$index] / 100 * $amount;

                // Update the rebates for the referrer
                User::where('id', $referrer->id)->update([
                    'rebates' => $referrer->rebates + $fundAmount,
                ]);
            }
        }
    }
}
