<?php

namespace App\FiveM\Adapters;

use App\FiveM\Contracts\FrameworkAdapter;
use App\FiveM\DTOs\CharacterData;
use App\FiveM\Models\ESX\AddonAccountData;
use App\FiveM\Models\ESX\Player;
use App\FiveM\Models\ESX\Vehicle;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use RuntimeException;

/**
 * Targets the standard ESX Legacy `users`/`owned_vehicles` shape documented
 * in docs/database-analysis.md. ESX has no first-party house or gang
 * system — housesForCitizen()/allGangs() return empty collections here;
 * a server running esx_property (or similar) should extend this adapter.
 */
class ESXAdapter implements FrameworkAdapter
{
    public function findByCitizenId(string $citizenId): ?CharacterData
    {
        // In ESX, the "citizen id" concept is the `identifier` column.
        $player = Player::query()->find($citizenId);

        return $player ? $this->toCharacterData($player) : null;
    }

    public function findByLicense(string $license): ?CharacterData
    {
        $player = Player::query()->where('license', $license)->first();

        return $player ? $this->toCharacterData($player) : null;
    }

    public function findAllByLicense(string $license): Collection
    {
        return Player::query()->where('license', $license)->get()
            ->map(fn (Player $player) => $this->toCharacterData($player));
    }

    public function paginateCharacters(int $perPage = 25, ?string $search = null): LengthAwarePaginator
    {
        return Player::query()
            ->when($search, fn ($query) => $query->where('name', 'like', "%{$search}%")
                ->orWhere('identifier', 'like', "%{$search}%"))
            ->orderBy('name')
            ->paginate($perPage)
            ->through(fn (Player $player) => $this->toCharacterData($player));
    }

    public function vehiclesForCitizen(string $citizenId): Collection
    {
        return Vehicle::query()->where('owner', $citizenId)->get()
            ->map(fn (Vehicle $vehicle) => [
                'id' => $vehicle->id,
                'model' => $vehicle->vehicle['model'] ?? 'unknown',
                'plate' => $vehicle->plate,
                'garage' => null,
                'state' => 1,
            ]);
    }

    public function housesForCitizen(string $citizenId): Collection
    {
        return collect();
    }

    public function allJobs(): Collection
    {
        return Player::query()
            ->select('job', 'job_grade')
            ->distinct()
            ->get()
            ->mapWithKeys(fn (Player $player) => [
                $player->job => [
                    'label' => $player->job,
                    'grades' => [],
                ],
            ]);
    }

    public function allGangs(): Collection
    {
        return collect();
    }

    public function topByMoney(string $account = 'cash', int $limit = 20): Collection
    {
        abort_unless(in_array($account, ['cash', 'bank', 'black_money'], true), 400, 'Unsupported money account.');

        $table = config('fivem.tables.esx.players', 'users');

        if ($account === 'cash') {
            return Player::query()
                ->orderByDesc('money')
                ->limit($limit)
                ->get()
                ->map(fn (Player $player) => $this->toCharacterData($player));
        }

        return Player::query()
            ->join('addon_account_data', function ($join) use ($table, $account) {
                $join->on('addon_account_data.identifier', '=', "{$table}.identifier")
                    ->where('addon_account_data.account_name', $account);
            })
            ->orderByDesc('addon_account_data.money')
            ->select("{$table}.*")
            ->limit($limit)
            ->get()
            ->map(fn (Player $player) => $this->toCharacterData($player));
    }

    public function adjustMoney(string $citizenId, string $account, int $delta): void
    {
        if ($account === 'cash') {
            /** @var Player|null $player */
            $player = Player::query()->where('identifier', $citizenId)->lockForUpdate()->first();

            if (! $player) {
                throw new RuntimeException("No player found for identifier [{$citizenId}].");
            }

            $next = max(0, (int) $player->money + $delta);

            if ($next === 0 && (int) $player->money + $delta < 0) {
                throw new RuntimeException('Insufficient funds for this operation.');
            }

            $player->money = $next;
            $player->save();

            return;
        }

        /** @var AddonAccountData|null $row */
        $row = AddonAccountData::query()
            ->where('identifier', $citizenId)
            ->where('account_name', $account)
            ->lockForUpdate()
            ->first();

        if (! $row) {
            throw new RuntimeException("No [{$account}] account found for identifier [{$citizenId}].");
        }

        $next = max(0, (int) $row->money + $delta);

        if ($next === 0 && (int) $row->money + $delta < 0) {
            throw new RuntimeException('Insufficient funds for this operation.');
        }

        $row->money = $next;
        $row->save();
    }

    public function paginateVehicles(int $perPage = 25, ?string $search = null): LengthAwarePaginator
    {
        return Vehicle::query()
            ->when($search, fn ($query) => $query->where('plate', 'like', "%{$search}%")
                ->orWhere('owner', 'like', "%{$search}%"))
            ->orderByDesc('id')
            ->paginate($perPage)
            ->through(fn (Vehicle $vehicle) => [
                'id' => $vehicle->id,
                'citizen_id' => $vehicle->owner,
                'model' => $vehicle->vehicle['model'] ?? 'unknown',
                'plate' => $vehicle->plate,
                'garage' => null,
            ]);
    }

    public function deleteVehicle(int|string $vehicleId): void
    {
        Vehicle::query()->whereKey($vehicleId)->delete();
    }

    public function giveItem(string $citizenId, string $itemName, int $amount): void
    {
        /** @var Player|null $player */
        $player = Player::query()->where('identifier', $citizenId)->lockForUpdate()->first();

        if (! $player) {
            throw new RuntimeException("No player found for identifier [{$citizenId}].");
        }

        $inventory = $player->inventory ?? [];
        $existingIndex = collect($inventory)->search(fn (array $item) => ($item['name'] ?? null) === $itemName);

        if ($existingIndex !== false) {
            $inventory[$existingIndex]['count'] = (int) ($inventory[$existingIndex]['count'] ?? 0) + $amount;
        } else {
            $inventory[] = ['name' => $itemName, 'label' => $itemName, 'count' => $amount];
        }

        $player->inventory = $inventory;
        $player->save();
    }

    protected function toCharacterData(Player $player): CharacterData
    {
        $bank = AddonAccountData::query()
            ->where('identifier', $player->identifier)
            ->where('account_name', 'bank')
            ->value('money');

        return new CharacterData(
            citizenId: $player->identifier,
            license: $player->license ?? $player->identifier,
            name: $player->name,
            money: [
                'cash' => (int) $player->money,
                'bank' => (int) ($bank ?? 0),
            ],
            job: [
                'name' => $player->job,
                'label' => $player->job,
                'grade' => (int) $player->job_grade,
                'grade_label' => (string) $player->job_grade,
            ],
            gang: null,
            inventory: collect($player->inventory ?? [])
                ->filter()
                ->map(fn (array $item) => [
                    'name' => $item['name'] ?? 'unknown',
                    'label' => $item['label'] ?? $item['name'] ?? 'Unknown item',
                    'amount' => (int) ($item['count'] ?? $item['amount'] ?? 1),
                    'slot' => 0,
                ])
                ->values()
                ->all(),
            metadata: $player->metadata ?? [],
        );
    }
}
