<?php

namespace App\Http\Controllers\Admin;

use App\Exceptions\Payments\MonnifyException;
use App\Exceptions\Payments\PaymentPointException;
use App\Http\Controllers\Controller;
use App\Models\ReservedAccount;
use App\Models\User;
use App\Services\AuditLogService;
use App\Services\ReservedAccountProvisioningService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Inertia\Inertia;
use Inertia\Response;
use Throwable;

class ReservedAccountController extends Controller
{
    public function index(Request $request, ReservedAccountProvisioningService $provisioningService): Response
    {
        return Inertia::render('Admin/ReservedAccounts/Index', [
            'reservedAccounts' => ReservedAccount::query()
                ->with('user:id,name,email')
                ->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')))
                ->when($request->filled('provider'), fn ($q) => $q->where('provider', $request->string('provider')))
                ->latest()
                ->paginate(20)
                ->withQueryString(),
            'users' => User::query()
                ->orderBy('name')
                ->limit(200)
                ->get(['id', 'name', 'email']),
            'filters' => $request->only(['status', 'provider']),
            'statuses' => ['active', 'inactive'],
            'providers' => ['monnify', 'paymentpoint'],
            'monnifyConfigured' => $provisioningService->monnifyConfigured(),
            'paymentpointConfigured' => $provisioningService->paymentpointConfigured(),
        ]);
    }

    public function store(Request $request, AuditLogService $auditLogService): RedirectResponse
    {
        $data = $this->validated($request);
        $payload = $this->normalizePayload($data);

        $account = ReservedAccount::create($payload);

        $auditLogService->log(
            admin: $request->user(),
            action: 'reserved_account.created',
            entity: $account,
            oldValues: null,
            newValues: $account->fresh()->toArray(),
            ipAddress: $request->ip(),
            userAgent: $request->userAgent()
        );

        return back()->with('success', 'Reserved account created.');
    }

    public function update(Request $request, ReservedAccount $reservedAccount, AuditLogService $auditLogService): RedirectResponse
    {
        $old = $reservedAccount->toArray();
        $data = $this->validated($request, $reservedAccount);
        $payload = $this->normalizePayload($data);

        $reservedAccount->update($payload);

        $auditLogService->log(
            admin: $request->user(),
            action: 'reserved_account.updated',
            entity: $reservedAccount,
            oldValues: $old,
            newValues: $reservedAccount->fresh()->toArray(),
            ipAddress: $request->ip(),
            userAgent: $request->userAgent()
        );

        return back()->with('success', 'Reserved account updated.');
    }

    public function provision(
        Request $request,
        ReservedAccountProvisioningService $provisioningService,
        AuditLogService $auditLogService
    ): RedirectResponse {
        $request->validate([
            'user_id' => ['required', 'exists:users,id'],
            'provider' => ['nullable', Rule::in(['monnify', 'paymentpoint'])],
        ]);

        /** @var User $user */
        $user = User::query()->findOrFail((int) $request->integer('user_id'));
        $provider = (string) ($request->input('provider') ?: 'monnify');

        $existing = ReservedAccount::query()
            ->where('user_id', $user->id)
            ->where('provider', $provider)
            ->first();

        $data = $request->validate([
            'user_id' => ['required', 'exists:users,id'],
            'provider' => ['required', Rule::in(['monnify', 'paymentpoint'])],
            'account_reference' => [
                'nullable',
                'string',
                'max:191',
                Rule::unique('reserved_accounts', 'account_reference')->ignore($existing?->id),
            ],
            'preferred_banks' => ['nullable', 'array'],
            'preferred_banks.*' => ['string', 'max:50'],
            'get_all_available_banks' => ['nullable', 'boolean'],
            'bank_codes' => ['nullable', 'array'],
            'bank_codes.*' => ['string', 'max:50'],
        ]);
        $provider = (string) $data['provider'];

        $before = $existing?->toArray();

        try {
            $reservedAccount = $provider === 'paymentpoint'
                ? $provisioningService->provisionPaymentPointForUser($user, [
                    'account_reference' => $data['account_reference'] ?? null,
                    'bank_codes' => array_values(array_unique(array_filter((array) ($data['bank_codes'] ?? [])))),
                ])
                : $provisioningService->provisionMonnifyForUser($user, [
                    'account_reference' => $data['account_reference'] ?? null,
                    'preferred_banks' => array_values(array_unique(array_filter((array) ($data['preferred_banks'] ?? [])))),
                    'get_all_available_banks' => (bool) ($data['get_all_available_banks'] ?? false),
                ]);

            $auditLogService->log(
                admin: $request->user(),
                action: "reserved_account.{$provider}_provisioned",
                entity: $reservedAccount,
                oldValues: $before,
                newValues: $reservedAccount->fresh()->toArray(),
                ipAddress: $request->ip(),
                userAgent: $request->userAgent()
            );

            return back()->with('success', ucfirst($provider).' reserved account provisioned successfully.');
        } catch (MonnifyException|PaymentPointException $e) {
            $auditLogService->log(
                admin: $request->user(),
                action: "reserved_account.{$provider}_provision_failed",
                entity: User::class,
                entityId: $user->id,
                oldValues: $before,
                newValues: [
                    'user_id' => $user->id,
                    'provider' => $provider,
                    'account_reference' => $data['account_reference'] ?? null,
                    'preferred_banks' => $data['preferred_banks'] ?? [],
                    'bank_codes' => $data['bank_codes'] ?? [],
                    'get_all_available_banks' => (bool) ($data['get_all_available_banks'] ?? false),
                    'error' => $e->getMessage(),
                ],
                ipAddress: $request->ip(),
                userAgent: $request->userAgent()
            );

            return back()->with('error', $e->getMessage());
        } catch (Throwable $e) {
            report($e);

            $auditLogService->log(
                admin: $request->user(),
                action: "reserved_account.{$provider}_provision_failed",
                entity: User::class,
                entityId: $user->id,
                oldValues: $before,
                newValues: [
                    'user_id' => $user->id,
                    'provider' => $provider,
                    'account_reference' => $data['account_reference'] ?? null,
                    'preferred_banks' => $data['preferred_banks'] ?? [],
                    'bank_codes' => $data['bank_codes'] ?? [],
                    'get_all_available_banks' => (bool) ($data['get_all_available_banks'] ?? false),
                    'error' => 'Unexpected error',
                ],
                ipAddress: $request->ip(),
                userAgent: $request->userAgent()
            );

            return back()->with('error', 'Unable to provision reserved account right now.');
        }
    }

    protected function validated(Request $request, ?ReservedAccount $reservedAccount = null): array
    {
        return $request->validate([
            'user_id' => ['required', 'exists:users,id'],
            'provider' => [
                'required',
                Rule::in(['monnify', 'paymentpoint']),
                function ($attribute, $value, $fail) use ($request, $reservedAccount) {
                    $exists = ReservedAccount::query()
                        ->where('user_id', (int) $request->input('user_id'))
                        ->where('provider', (string) $value)
                        ->when($reservedAccount, fn ($query) => $query->whereKeyNot($reservedAccount->id))
                        ->exists();

                    if ($exists) {
                        $fail('This user already has a reserved account for the selected provider.');
                    }
                },
            ],
            'account_reference' => [
                'required',
                'string',
                'max:191',
                Rule::unique('reserved_accounts', 'account_reference')->ignore($reservedAccount?->id),
            ],
            'status' => ['required', Rule::in(['active', 'inactive'])],
            'accounts_json' => ['required'],
        ]);
    }

    protected function normalizePayload(array $data): array
    {
        $accountsJson = $data['accounts_json'];

        if (is_string($accountsJson)) {
            $decoded = json_decode($accountsJson, true);
            if (json_last_error() !== JSON_ERROR_NONE || ! is_array($decoded)) {
                abort(422, 'accounts_json must be a valid JSON object or array.');
            }
            $accountsJson = $decoded;
        }

        return [
            'user_id' => (int) $data['user_id'],
            'provider' => $data['provider'],
            'account_reference' => $data['account_reference'],
            'accounts_json' => $accountsJson,
            'status' => $data['status'],
        ];
    }
}
