<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\AuditLog;
use App\Models\Bet;
use App\Models\BetItem;
use App\Models\Game;
use App\Models\Setting;
use App\Models\SportMatch;
use App\Models\Transaction;
use App\Models\UserBonus;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class BetController extends Controller
{
    public function index(Request $request): JsonResponse
    {
        $bets = $request->user()
            ->bets()
            ->with('items')
            ->latest()
            ->paginate(20);

        return response()->json([
            'bets' => $bets->map(fn($b) => $this->formatBet($b)),
            'meta' => [
                'total'        => $bets->total(),
                'current_page' => $bets->currentPage(),
                'last_page'    => $bets->lastPage(),
            ],
        ]);
    }

    public function store(Request $request): JsonResponse
    {
        $minBet  = (float) Setting::get('min_bet', 0.10);
        $maxBet  = (float) Setting::get('max_bet', 10000);
        $maxLegs = (int) Setting::get('max_parlay_legs', 10);

        $data = $request->validate([
            'stake'             => "required|numeric|min:{$minBet}|max:{$maxBet}",
            'items'             => "required|array|min:1|max:{$maxLegs}",
            'items.*.id'        => 'required|integer',
            'items.*.type'      => 'required|in:game,sport',
            'items.*.odds'      => 'required|numeric|min:1.01|max:1000',
            'items.*.selection' => 'nullable|in:home,draw,away',
        ]);

        $user = $request->user();

        // Self-exclusion check
        $excluded = \App\Models\SelfExclusion::where('user_id', $user->id)
            ->where('ends_at', '>', now())
            ->whereNull('reactivated_at')
            ->exists();
        if ($excluded) {
            return response()->json(['message' => 'Your account is self-excluded from betting.'], 403);
        }

        if ((float) $user->balance < (float) $data['stake']) {
            return response()->json(['message' => 'Insufficient balance.'], 422);
        }

        // Check for active bonus max-bet rule
        $activeBonus = UserBonus::where('user_id', $user->id)->where('status', 'active')->first();
        if ($activeBonus && $activeBonus->max_bet_while_active > 0 && $data['stake'] > $activeBonus->max_bet_while_active) {
            return response()->json([
                'message' => "While a bonus is active, your maximum bet is \${$activeBonus->max_bet_while_active}.",
            ], 422);
        }

        // Determine if all items are sport bets (for pending vs instant settlement)
        $hasSportBet  = collect($data['items'])->contains('type', 'sport');
        $hasCasinoBet = collect($data['items'])->contains('type', 'game');

        return DB::transaction(function () use ($data, $user, $hasSportBet, $hasCasinoBet, $activeBonus) {
            $totalOdds       = array_reduce($data['items'], fn($carry, $item) => $carry * $item['odds'], 1.0);
            $potentialPayout = round($data['stake'] * $totalOdds, 2);

            $balanceBefore = (float) $user->balance;
            $user->decrement('balance', $data['stake']);
            $user->refresh();

            Transaction::create([
                'user_id'        => $user->id,
                'type'           => 'bet',
                'amount'         => -$data['stake'],
                'balance_before' => $balanceBefore,
                'balance_after'  => (float) $user->balance,
                'status'         => 'completed',
                'processed_at'   => now(),
            ]);

            // Parlay = mixed/sport; single = single item
            $betType = count($data['items']) > 1 ? 'parlay' : ($hasSportBet ? 'sport' : 'single');

            $bet = Bet::create([
                'user_id'          => $user->id,
                'stake'            => $data['stake'],
                'total_odds'       => $totalOdds,
                'potential_payout' => $potentialPayout,
                'type'             => $betType,
                'status'           => 'pending',
            ]);

            foreach ($data['items'] as $item) {
                $title = 'Game';
                if ($item['type'] === 'game') {
                    $game  = Game::find($item['id']);
                    $title = $game?->title ?? 'Casino Game';
                } else {
                    $match = SportMatch::find($item['id']);
                    if ($match) {
                        $title = $match->home_team . ' vs ' . $match->away_team;
                    }
                }

                BetItem::create([
                    'bet_id'         => $bet->id,
                    'game_id'        => $item['type'] === 'game'  ? $item['id'] : null,
                    'sport_match_id' => $item['type'] === 'sport' ? $item['id'] : null,
                    'item_type'      => $item['type'],
                    'title'          => $title,
                    'selection'      => $item['selection'] ?? null,
                    'odds'           => $item['odds'],
                ]);
            }

            // Casino game bets: settle immediately (instant play)
            // Sport bets: remain pending until match data is resolved
            $message = 'Bet placed! Awaiting result.';

            if (!$hasSportBet || $hasCasinoBet) {
                // Settle casino items immediately
                $this->settleCasinoBet($bet, $user);
                $bet->refresh();
                $message = $bet->status === 'won'
                    ? "🎉 You won \${$bet->actual_payout}!"
                    : "Better luck next time!";
            }

            // Add wagering to active bonus
            if ($activeBonus) {
                $activeBonus->addWagering((float) $data['stake']);
            }

            AuditLog::record('bet_placed', $user->id, "Bet #{$bet->reference} placed — \${$data['stake']} stake", [
                'bet_id' => $bet->id, 'stake' => $data['stake'], 'type' => $betType,
            ]);

            $bet->refresh()->load('items');

            return response()->json([
                'bet'     => $this->formatBet($bet),
                'balance' => (float) $user->fresh()->balance,
                'message' => $message,
            ], 201);
        });
    }

    /** Settle a pending sport bet when match result is known */
    public function settleSportBets(): int
    {
        $settled = 0;
        $pendingBets = Bet::where('status', 'pending')
            ->whereIn('type', ['sport', 'parlay'])
            ->with(['items.sportMatch'])
            ->get();

        foreach ($pendingBets as $bet) {
            $allSettled = true;
            $won        = true;

            foreach ($bet->items as $item) {
                if ($item->item_type !== 'sport') continue;
                $match = $item->sportMatch;
                if (!$match || $match->status !== 'finished') {
                    $allSettled = false;
                    break;
                }
                // Determine item result based on match scores
                $result = $this->determineMatchResult($match, $item->selection);
                $item->update(['result' => $result]);
                if ($result !== 'won') $won = false;
            }

            if ($allSettled) {
                $this->finaliseSettlement($bet, $won);
                $settled++;
            }
        }
        return $settled;
    }

    private function determineMatchResult(SportMatch $match, ?string $selection): string
    {
        $home = (int) $match->home_score;
        $away = (int) $match->away_score;

        $actualResult = $home > $away ? 'home' : ($away > $home ? 'away' : 'draw');

        return $selection === $actualResult ? 'won' : 'lost';
    }

    private function finaliseSettlement(Bet $bet, bool $won): void
    {
        $status       = $won ? 'won' : 'lost';
        $actualPayout = $won ? $bet->potential_payout : 0;

        $bet->update([
            'status'        => $status,
            'actual_payout' => $actualPayout,
            'settled_at'    => now(),
        ]);

        foreach ($bet->items as $item) {
            if (!$item->result) $item->update(['result' => $status]);
        }

        if ($won) {
            $user          = $bet->user;
            $balanceBefore = (float) $user->balance;
            $user->increment('balance', $actualPayout);

            Transaction::create([
                'user_id'        => $user->id,
                'type'           => 'win',
                'amount'         => $actualPayout,
                'balance_before' => $balanceBefore,
                'balance_after'  => (float) $user->fresh()->balance,
                'status'         => 'completed',
                'notes'          => 'Sport bet win: ' . $bet->reference,
                'processed_at'   => now(),
            ]);

            $pointsPerDollar = (int) Setting::get('vip_points_per_dollar', 10);
            $user->increment('vip_points', (int) ($bet->stake * $pointsPerDollar));
        }
    }

    private function settleCasinoBet(Bet $bet, $user): void
    {
        $houseWinRate = (int) Setting::get('house_win_rate', 45);
        $won          = rand(1, 100) > $houseWinRate;
        $status       = $won ? 'won' : 'lost';
        $actualPayout = $won ? $bet->potential_payout : 0;

        $bet->update([
            'status'        => $status,
            'actual_payout' => $actualPayout,
            'settled_at'    => now(),
        ]);

        foreach ($bet->items as $item) {
            $item->update(['result' => $status]);
        }

        if ($won) {
            $balanceBefore = (float) $user->balance;
            $user->increment('balance', $actualPayout);
            $user->refresh();

            Transaction::create([
                'user_id'        => $user->id,
                'type'           => 'win',
                'amount'         => $actualPayout,
                'balance_before' => $balanceBefore,
                'balance_after'  => (float) $user->balance,
                'status'         => 'completed',
                'notes'          => 'Bet win: ' . $bet->reference,
                'processed_at'   => now(),
            ]);

            $pointsPerDollar = (int) Setting::get('vip_points_per_dollar', 10);
            $user->increment('vip_points', (int) ($bet->stake * $pointsPerDollar));
        }
    }

    private function formatBet(Bet $bet): array
    {
        return [
            'id'              => $bet->id,
            'reference'       => $bet->reference,
            'stake'           => (float) $bet->stake,
            'totalOdds'       => (float) $bet->total_odds,
            'potentialPayout' => (float) $bet->potential_payout,
            'actualPayout'    => (float) $bet->actual_payout,
            'status'          => $bet->status,
            'type'            => $bet->type,
            'settledAt'       => $bet->settled_at?->toISOString(),
            'createdAt'       => $bet->created_at->toISOString(),
            'games'           => $bet->items->pluck('title')->join(' + '),
            'items'           => $bet->items->map(fn($i) => [
                'id'        => $i->id,
                'title'     => $i->title,
                'type'      => $i->item_type,
                'selection' => $i->selection,
                'odds'      => (float) $i->odds,
                'result'    => $i->result,
            ]),
        ];
    }
}
