<?php

namespace App\Http\Controllers\Admin;

use App\Concerns\ProfileValidationRules;
use App\Http\Controllers\Controller;
use App\Models\InvestmentSetting;
use App\Models\InvestmentTransaction;
use App\Models\User;
use App\Support\Impersonation;
use App\Support\InvestmentLedger;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\Password;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Inertia\Response;

class AdminUserDirectoryController extends Controller
{
    use ProfileValidationRules;

    /**
     * Listado paginado de cuentas de usuario (solo administración).
     */
    public function index(Request $request): Response
    {
        $q = trim((string) $request->query('q', ''));
        $filter = (string) $request->query('filter', 'all');
        if (! in_array($filter, ['all', 'kyc_review', 'blocked', 'unverified'], true)) {
            $filter = 'all';
        }

        $users = User::query()
            ->when($q !== '', function ($query) use ($q): void {
                $needle = '%'.$q.'%';
                $query->where(function ($sub) use ($needle, $q): void {
                    $sub->where('email', 'like', $needle)
                        ->orWhere('first_name', 'like', $needle)
                        ->orWhere('last_name', 'like', $needle)
                        ->orWhere('referral_code', 'like', $needle);
                    if (is_numeric($q)) {
                        $sub->orWhere('id', (int) $q);
                    }
                });
            })
            ->when($filter === 'kyc_review', static function ($query): void {
                $query->where('kyc_status', 'pending_review')->where('is_admin', false);
            })
            ->when($filter === 'blocked', static fn ($query) => $query->whereNotNull('blocked_at'))
            ->when($filter === 'unverified', static fn ($query) => $query->whereNull('email_verified_at'))
            ->orderByDesc('id')
            ->select([
                'id',
                'first_name',
                'last_name',
                'email',
                'is_admin',
                'kyc_status',
                'email_verified_at',
                'referral_program_enabled',
                'jabes_women_enabled',
                'blocked_at',
                'created_at',
            ])
            ->paginate(30)
            ->withQueryString();

        return Inertia::render('admin/users-directory', [
            'users' => $users,
            'filters' => [
                'q' => $q,
                'filter' => $filter,
            ],
        ]);
    }

    public function edit(Request $request, User $user): Response
    {
        /** @var User $admin */
        $admin = $request->user();

        $referrer = $user->referrer()->first(['id', 'first_name', 'last_name', 'email', 'referral_code', 'referral_program_enabled']);

        $directReferrals = $user->directReferrals()
            ->orderByDesc('id')
            ->get(['id', 'first_name', 'last_name', 'email', 'referral_code', 'referral_program_enabled']);

        $settings = InvestmentSetting::current();

        $consolidationPreview = InvestmentLedger::previewUserBalanceConsolidation($user->id);

        return Inertia::render('admin/user-edit', [
            'balance_consolidation_preview' => [
                'has_work' => $consolidationPreview['has_work'],
                'total_withdrawable_usd' => number_format($consolidationPreview['total_withdrawable_usd'], 2, '.', ''),
                'container_balance_before_usd' => number_format($consolidationPreview['container_balance_before_usd'], 2, '.', ''),
                'container_balance_after_usd' => number_format($consolidationPreview['container_balance_after_usd'], 2, '.', ''),
                'actions' => array_map(static fn (array $action): array => [
                    'investment_id' => $action['investment_id'],
                    'txid' => $action['txid'],
                    'amount_usd' => number_format($action['amount_usd'], 2, '.', ''),
                    'description' => $action['description'],
                ], $consolidationPreview['actions']),
            ],
            'manual_balance_credits' => InvestmentLedger::adminBalanceCreditsForUser($user->id),
            'manual_referral_progress_adjustments' => InvestmentLedger::manualReferralProgressAdjustmentsForUser($user->id),
            'referral_earnings_progress' => [
                'direct_earned_usd' => number_format(InvestmentLedger::referralProgressEarningsAtLevel($user->id, 1), 2, '.', ''),
                'indirect_earned_usd' => number_format(InvestmentLedger::referralProgressEarningsAtLevel($user->id, 2), 2, '.', ''),
                'direct_limit_usd' => $settings->referral_direct_earning_limit !== null
                    ? number_format((float) $settings->referral_direct_earning_limit, 2, '.', '')
                    : null,
                'indirect_limit_usd' => $settings->referral_indirect_earning_limit !== null
                    ? number_format((float) $settings->referral_indirect_earning_limit, 2, '.', '')
                    : null,
            ],
            'user' => [
                'id' => $user->id,
                'withdrawable_balance_usd' => number_format(
                    InvestmentLedger::totalWithdrawableForUser($user->id),
                    2,
                    '.',
                    '',
                ),
                'first_name' => $user->first_name,
                'last_name' => $user->last_name,
                'email' => $user->email,
                'contact_number' => $user->contact_number,
                'country' => $user->country,
                'is_admin' => $user->is_admin,
                'referral_program_enabled' => (bool) $user->referral_program_enabled,
                'jabes_women_enabled' => (bool) $user->jabes_women_enabled,
                'access_blocked' => $user->blocked_at !== null,
                'blocked_at' => $user->blocked_at?->toIso8601String(),
                'can_toggle_access_block' => ! $user->is_admin && $user->id !== $admin->id,
                'can_set_password' => ! $user->is_admin,
                'can_manage_manual_referrer' => true,
                'can_impersonate' => ! $user->is_admin
                    && $user->id !== $admin->id
                    && ! Impersonation::isActive($request),
                'referral_code' => (string) $user->referral_code,
                'referrer' => $referrer
                    ? [
                        'id' => $referrer->id,
                        'name' => $referrer->name,
                        'email' => $referrer->email,
                        'referral_code' => $referrer->referral_code,
                        'referral_program_enabled' => (bool) $referrer->referral_program_enabled,
                    ]
                    : null,
                'direct_referrals' => $directReferrals->map(static fn (User $u): array => [
                    'id' => $u->id,
                    'name' => $u->name,
                    'email' => $u->email,
                    'referral_code' => $u->referral_code,
                    'referral_program_enabled' => (bool) $u->referral_program_enabled,
                ]),
            ],
        ]);
    }

    /**
     * Búsqueda ligera para asignar referidor manualmente (admin).
     */
    public function referrerSuggestions(Request $request): JsonResponse
    {
        $q = trim((string) $request->query('q', ''));
        if (mb_strlen($q) < 2) {
            return response()->json(['users' => []]);
        }

        $excludeIds = collect();
        $excludeId = $request->query('exclude');
        if ($excludeId !== null && $excludeId !== '' && ctype_digit((string) $excludeId)) {
            $excludeIds->push((int) $excludeId);
        }
        $excludeIdsParam = $request->query('exclude_ids');
        if (is_string($excludeIdsParam) && $excludeIdsParam !== '') {
            foreach (explode(',', $excludeIdsParam) as $part) {
                $part = trim($part);
                if ($part !== '' && ctype_digit($part)) {
                    $excludeIds->push((int) $part);
                }
            }
        }
        $excludeIds = $excludeIds->unique()->values()->all();

        $needle = '%'.$q.'%';
        $codeNeedle = '%'.mb_strtoupper($q).'%';

        $users = User::query()
            ->when(
                $excludeIds !== [],
                static fn ($query) => $query->whereNotIn('id', $excludeIds),
            )
            ->where(function ($query) use ($needle, $codeNeedle): void {
                $query->where('email', 'like', $needle)
                    ->orWhere('first_name', 'like', $needle)
                    ->orWhere('last_name', 'like', $needle)
                    ->orWhere('referral_code', 'like', $codeNeedle);
            })
            ->orderBy('id')
            ->limit(15)
            ->get(['id', 'first_name', 'last_name', 'email', 'referral_code', 'referral_program_enabled']);

        return response()->json([
            'users' => $users->map(static fn (User $u): array => [
                'id' => $u->id,
                'name' => $u->name,
                'email' => $u->email,
                'referral_code' => $u->referral_code,
                'referral_program_enabled' => (bool) $u->referral_program_enabled,
            ]),
        ]);
    }

    /**
     * Asigna manualmente a otro usuario como referido directo de {@see $user}.
     * Actualiza {@see User::$referred_by_user_id} del referido a {@see $user} (mismo efecto que en el registro con código).
     */
    public function attachManualDirectReferral(Request $request, User $user): RedirectResponse
    {
        $validated = $request->validate([
            'referred_user_id' => ['required', 'integer', 'exists:users,id'],
        ]);

        $referredUserId = (int) $validated['referred_user_id'];

        if ($referredUserId === $user->id) {
            throw ValidationException::withMessages([
                'referred_user_id' => ['Un usuario no puede ser referido de sí mismo.'],
            ]);
        }

        /** @var User $referred */
        $referred = User::query()->findOrFail($referredUserId);

        if ($referred->is_admin) {
            throw ValidationException::withMessages([
                'referred_user_id' => ['No se pueden asignar cuentas de administrador como referidos.'],
            ]);
        }

        if (User::manualReferralWouldCreateCycle($referred->id, $user->id)) {
            throw ValidationException::withMessages([
                'referred_user_id' => ['Esa asignación crearía un ciclo en la cadena de referidos.'],
            ]);
        }

        $referred->update([
            'referred_by_user_id' => $user->id,
        ]);

        Inertia::flash('toast', [
            'type' => 'success',
            'message' => 'Referido vinculado a esta cuenta.',
        ]);

        return redirect()->route('admin.users.edit', $user);
    }

    /**
     * Quita la vinculación de un referido directo previamente asignado a {@see $user}.
     */
    public function detachManualDirectReferral(User $user, User $referred): RedirectResponse
    {
        if ($referred->referred_by_user_id !== $user->id) {
            abort(404);
        }

        $referred->update([
            'referred_by_user_id' => null,
        ]);

        Inertia::flash('toast', [
            'type' => 'success',
            'message' => 'Referido desvinculado de esta cuenta.',
        ]);

        return redirect()->route('admin.users.edit', $user);
    }

    public function update(Request $request, User $user): RedirectResponse
    {
        if (! $user->is_admin) {
            if (trim((string) $request->input('password', '')) === '') {
                $request->request->remove('password');
                $request->request->remove('password_confirmation');
            }
        }

        $validator = Validator::make($request->all(), [
            ...$this->profileRules($user->id),
            'referral_program_enabled' => ['required', 'boolean'],
            'jabes_women_enabled' => ['required', 'boolean'],
            'access_blocked' => ['required', 'boolean'],
            'referred_by_user_id' => ['nullable', 'integer', 'exists:users,id'],
            'password' => $user->is_admin
                ? ['prohibited']
                : ['nullable', 'string', Password::default(), 'confirmed'],
        ]);

        $validator->after(function (\Illuminate\Validation\Validator $v) use ($user): void {
            $rid = $v->getValue('referred_by_user_id');

            if ($rid === null || $rid === '') {
                return;
            }

            $rid = (int) $rid;

            if ($rid === $user->id) {
                $v->errors()->add('referred_by_user_id', 'Un usuario no puede ser referidor de sí mismo.');
            } elseif (User::manualReferralWouldCreateCycle($user->id, $rid)) {
                $v->errors()->add(
                    'referred_by_user_id',
                    'Esa asignación crearía un ciclo en la cadena de referidos.',
                );
            }
        });

        $validated = $validator->validate();

        if ($validated['access_blocked']) {
            if ($user->id === $request->user()?->id) {
                throw ValidationException::withMessages([
                    'access_blocked' => ['No puedes suspender tu propia cuenta.'],
                ]);
            }

            if ($user->is_admin) {
                throw ValidationException::withMessages([
                    'access_blocked' => ['No se pueden suspender cuentas de administrador desde el panel.'],
                ]);
            }
        }

        $blockedAt = $validated['access_blocked']
            ? ($user->blocked_at ?? now())
            : null;

        $update = [
            'first_name' => $validated['first_name'],
            'last_name' => $validated['last_name'],
            'email' => $validated['email'],
            'contact_number' => $validated['contact_number'],
            'country' => $validated['country'],
            'referral_program_enabled' => $validated['referral_program_enabled'],
            'jabes_women_enabled' => $validated['jabes_women_enabled'],
            'blocked_at' => $blockedAt,
        ];

        $rawReferrer = $validated['referred_by_user_id'] ?? null;
        $update['referred_by_user_id'] = ($rawReferrer !== null && $rawReferrer !== '')
            ? (int) $rawReferrer
            : null;

        if (! $user->is_admin && isset($validated['password']) && is_string($validated['password']) && $validated['password'] !== '') {
            $update['password'] = Hash::make($validated['password']);
        }

        $user->update($update);

        $passwordChanged = ! $user->is_admin && isset($update['password']);

        Inertia::flash('toast', [
            'type' => 'success',
            'message' => $passwordChanged
                ? 'Datos del usuario y contraseña actualizados.'
                : 'Datos del usuario actualizados.',
        ]);

        return redirect()->route('admin.users.edit', $user);
    }

    /**
     * Acredita saldo retirable manualmente al usuario (inversionista o administrador).
     */
    public function addManualBalance(Request $request, User $user): RedirectResponse
    {
        $validated = $request->validate([
            'amount' => ['required', 'numeric', 'min:0.01', 'max:999999999.99'],
            'note' => ['nullable', 'string', 'max:500'],
        ]);

        $amount = round((float) $validated['amount'], 2);

        /** @var User $admin */
        $admin = $request->user();

        try {
            InvestmentLedger::applyAdminBalanceCredit(
                $user,
                $amount,
                $validated['note'] ?? null,
                $admin->id,
            );
        } catch (ValidationException $exception) {
            throw $exception;
        }

        $user->refresh();
        $newBalance = InvestmentLedger::totalWithdrawableForUser($user->id);

        Inertia::flash('toast', [
            'type' => 'success',
            'message' => sprintf(
                'Se acreditaron US$ %s a %s. Nuevo saldo retirable: US$ %s.',
                number_format($amount, 2, '.', ','),
                $user->name,
                number_format($newBalance, 2, '.', ','),
            ),
        ]);

        return redirect()->route('admin.users.edit', $user);
    }

    /**
     * Suma al progreso de ganancias L1/L2 sin acreditar saldo retirable.
     */
    public function addManualReferralProgress(Request $request, User $user): RedirectResponse
    {
        $validated = $request->validate([
            'level' => ['required', 'integer', 'in:1,2'],
            'amount' => ['required', 'numeric', 'min:0.01', 'max:999999999.99'],
            'note' => ['nullable', 'string', 'max:500'],
        ]);

        $amount = round((float) $validated['amount'], 2);
        $level = (int) $validated['level'];

        /** @var User $admin */
        $admin = $request->user();

        InvestmentLedger::applyManualReferralProgressAdjustment(
            $user,
            $level,
            $amount,
            $validated['note'] ?? null,
            $admin->id,
        );

        $levelLabel = $level === 1 ? 'directas (L1)' : 'indirectas (L2)';
        $newProgress = InvestmentLedger::referralProgressEarningsAtLevel($user->id, $level);

        Inertia::flash('toast', [
            'type' => 'success',
            'message' => sprintf(
                'Se sumaron US$ %s al progreso de ganancias %s de %s. Nuevo acumulado: US$ %s.',
                number_format($amount, 2, '.', ','),
                $levelLabel,
                $user->name,
                number_format($newProgress, 2, '.', ','),
            ),
        ]);

        return redirect()->route('admin.users.edit', $user);
    }

    /**
     * Revierte una acreditación manual de saldo previamente aplicada al usuario.
     */
    public function revertManualBalance(
        Request $request,
        User $user,
        InvestmentTransaction $transaction,
    ): RedirectResponse {
        abort_unless(
            $transaction->investment?->user_id === $user->id,
            404,
        );

        /** @var User $admin */
        $admin = $request->user();

        try {
            InvestmentLedger::revertAdminBalanceCredit($transaction, $admin);
        } catch (ValidationException $exception) {
            throw $exception;
        }

        $user->refresh();
        $newBalance = InvestmentLedger::totalWithdrawableForUser($user->id);

        Inertia::flash('toast', [
            'type' => 'success',
            'message' => sprintf(
                'Se revirtió la acreditación de US$ %s. Nuevo saldo retirable: US$ %s.',
                number_format((float) $transaction->amount, 2, '.', ','),
                number_format($newBalance, 2, '.', ','),
            ),
        ]);

        return redirect()->route('admin.users.edit', $user);
    }

    /**
     * Vista previa JSON para consolidar saldo retirable al balance general del usuario.
     */
    public function consolidateBalancePreview(User $user): JsonResponse
    {
        $preview = InvestmentLedger::previewUserBalanceConsolidation($user->id);

        return response()->json([
            'has_work' => $preview['has_work'],
            'total_withdrawable_usd' => number_format($preview['total_withdrawable_usd'], 2, '.', ''),
            'container_balance_before_usd' => number_format($preview['container_balance_before_usd'], 2, '.', ''),
            'container_balance_after_usd' => number_format($preview['container_balance_after_usd'], 2, '.', ''),
            'actions' => array_map(static fn (array $action): array => [
                'investment_id' => $action['investment_id'],
                'txid' => $action['txid'],
                'amount_usd' => number_format($action['amount_usd'], 2, '.', ''),
                'description' => $action['description'],
            ], $preview['actions']),
        ]);
    }

    /**
     * Consolida saldo retirable disperso en el balance general del usuario.
     */
    public function consolidateBalance(Request $request, User $user): RedirectResponse
    {
        $preview = InvestmentLedger::previewUserBalanceConsolidation($user->id);

        if (! $preview['has_work']) {
            Inertia::flash('toast', [
                'type' => 'info',
                'message' => 'No hay saldo retirable disperso que consolidar para este usuario.',
            ]);

            return redirect()->route('admin.users.edit', $user);
        }

        $result = InvestmentLedger::consolidateUserWithdrawableBalance($user);

        Inertia::flash('toast', [
            'type' => 'success',
            'message' => sprintf(
                'Se consolidaron US$ %s al balance general. Nuevo saldo en contenedor: US$ %s.',
                number_format($result['moved_total_usd'], 2, '.', ','),
                number_format($result['container_balance_after_usd'], 2, '.', ','),
            ),
        ]);

        return redirect()->route('admin.users.edit', $user);
    }

    /**
     * Registra un retiro administrativo directo contra el balance general del usuario.
     */
    public function recordWithdrawal(Request $request, User $user): RedirectResponse
    {
        $validated = $request->validate([
            'amount' => ['required', 'numeric', 'min:0.01'],
        ]);

        try {
            InvestmentLedger::recordUserBalanceWithdrawal($user, (float) $validated['amount']);
        } catch (ValidationException $exception) {
            throw $exception;
        }

        $newBalance = InvestmentLedger::totalWithdrawableForUser($user->id);

        Inertia::flash('toast', [
            'type' => 'success',
            'message' => sprintf(
                'Retiro registrado. Nuevo saldo retirable: US$ %s.',
                number_format($newBalance, 2, '.', ','),
            ),
        ]);

        return redirect()->route('admin.users.edit', $user);
    }
}
