<?php

namespace App\Services;

use App\Enums\EmailConfirmationType;
use App\Enums\InvestmentStatus;
use App\Enums\WithdrawalRequestStatus;
use App\Models\EmailConfirmationToken;
use App\Models\Investment;
use App\Models\InvestmentSetting;
use App\Models\User;
use App\Models\WithdrawalRequest;
use App\Support\InvestmentLedger;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

final class EmailConfirmationExecutor
{
    /**
     * @throws ValidationException
     */
    public function fulfillInvestment(EmailConfirmationToken $record): void
    {
        $pendingInvestment = null;

        DB::transaction(function () use ($record, &$pendingInvestment): void {
            /** @var EmailConfirmationToken $locked */
            $locked = EmailConfirmationToken::query()
                ->whereKey($record->id)
                ->lockForUpdate()
                ->first();

            if ($locked === null) {
                throw ValidationException::withMessages([
                    'token' => ['Enlace inválido.'],
                ]);
            }

            if ($locked->used_at !== null) {
                throw ValidationException::withMessages([
                    'token' => ['Este enlace ya fue utilizado.'],
                ]);
            }

            if ($locked->expires_at->isPast()) {
                throw ValidationException::withMessages([
                    'token' => ['Este enlace expiró. Solicita una nueva operación desde la plataforma.'],
                ]);
            }

            $userId = $locked->user_id;
            if ($userId === null) {
                throw ValidationException::withMessages([
                    'token' => ['Enlace inválido.'],
                ]);
            }

            $user = User::query()->whereKey($userId)->firstOrFail();
            $payload = $locked->payload ?? [];

            match ($locked->typeEnum()) {
                EmailConfirmationType::InvestmentBalance => $this->activateFromBalance($user, $payload),
                EmailConfirmationType::InvestmentBlockchain => $pendingInvestment = $this->createPendingFromBlockchain($user, $payload),
                default => throw ValidationException::withMessages([
                    'token' => ['Este enlace no corresponde a una inversión.'],
                ]),
            };

            $locked->markUsed();
        });

        if ($pendingInvestment instanceof Investment) {
            app(AdminEventNotifier::class)->notifyInvestmentPending($pendingInvestment);
        }
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    private function activateFromBalance(User $user, array $payload): void
    {
        $amount = (float) ($payload['amount'] ?? 0);
        $durationMonths = (int) ($payload['duration_months'] ?? 0);
        if ($amount <= 0 || $durationMonths < 1) {
            throw ValidationException::withMessages([
                'token' => ['Datos de confirmación incompletos.'],
            ]);
        }

        $settings = InvestmentSetting::current();
        $snapshot = $payload['monthly_return_snapshot'] ?? null;
        if ($snapshot !== null) {
            $settings->setAttribute('monthly_return', $snapshot);
        }

        InvestmentLedger::activateInvestmentFromPortfolioBalance(
            $user,
            round($amount, 2),
            $durationMonths,
            $settings,
            (int) $user->id,
        );
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    private function createPendingFromBlockchain(User $user, array $payload): Investment
    {
        $amount = (float) ($payload['amount'] ?? 0);
        $durationMonths = (int) ($payload['duration_months'] ?? 0);
        $txid = trim((string) ($payload['txid'] ?? ''));
        $snapshot = (float) ($payload['monthly_return_snapshot'] ?? 0);

        if ($amount <= 0 || $durationMonths < 1 || $txid === '') {
            throw ValidationException::withMessages([
                'token' => ['Datos de confirmación incompletos.'],
            ]);
        }

        if (Investment::query()->where('txid', $txid)->exists()) {
            throw ValidationException::withMessages([
                'token' => ['El TXID ya está registrado; no se puede repetir la inversión.'],
            ]);
        }

        return $user->investments()->create([
            'amount' => round($amount, 2),
            'duration_months' => $durationMonths,
            'monthly_return_snapshot' => $snapshot > 0 ? $snapshot : InvestmentSetting::current()->monthly_return,
            'txid' => $txid,
            'status' => InvestmentStatus::Pending,
        ]);
    }

    /**
     * @throws ValidationException
     */
    public function fulfillWithdrawal(EmailConfirmationToken $record): void
    {
        $pendingRequest = null;

        DB::transaction(function () use ($record, &$pendingRequest): void {
            /** @var EmailConfirmationToken|null $locked */
            $locked = EmailConfirmationToken::query()
                ->whereKey($record->id)
                ->lockForUpdate()
                ->first();

            if ($locked === null || $locked->used_at !== null || $locked->expires_at->isPast()) {
                throw ValidationException::withMessages([
                    'token' => ['Este enlace no es válido o ya expiró.'],
                ]);
            }

            if ($locked->typeEnum() !== EmailConfirmationType::WithdrawalRequest) {
                throw ValidationException::withMessages([
                    'token' => ['Este enlace no corresponde a un retiro.'],
                ]);
            }

            $wrId = $locked->withdrawal_request_id;
            if ($wrId === null) {
                throw ValidationException::withMessages([
                    'token' => ['Datos incompletos.'],
                ]);
            }

            /** @var WithdrawalRequest|null $wr */
            $wr = WithdrawalRequest::query()
                ->whereKey($wrId)
                ->lockForUpdate()
                ->first();

            if ($wr === null || $wr->status !== WithdrawalRequestStatus::PendingEmail) {
                throw ValidationException::withMessages([
                    'token' => ['Esta solicitud ya no puede confirmarse desde el correo.'],
                ]);
            }

            $wr->forceFill(['status' => WithdrawalRequestStatus::Pending])->save();
            $locked->markUsed();
            $pendingRequest = $wr;
        });

        if ($pendingRequest instanceof WithdrawalRequest) {
            app(AdminEventNotifier::class)->notifyWithdrawalRequested($pendingRequest);
        }
    }

    /**
     * @throws ValidationException
     */
    public function fulfillAccrual(EmailConfirmationToken $record): int
    {
        return (int) DB::transaction(function () use ($record): int {
            /** @var EmailConfirmationToken|null $locked */
            $locked = EmailConfirmationToken::query()
                ->whereKey($record->id)
                ->lockForUpdate()
                ->first();

            if ($locked === null || $locked->used_at !== null || $locked->expires_at->isPast()) {
                throw ValidationException::withMessages([
                    'token' => ['Este enlace no es válido o ya expiró.'],
                ]);
            }

            if ($locked->typeEnum() !== EmailConfirmationType::AccrualRun) {
                throw ValidationException::withMessages([
                    'token' => ['Este enlace no corresponde a una acreditación.'],
                ]);
            }

            $runDate = (string) (($locked->payload ?? [])['run_date'] ?? '');
            if ($runDate === '') {
                throw ValidationException::withMessages([
                    'token' => ['Fecha de acreditación no disponible.'],
                ]);
            }

            $count = InvestmentLedger::runAccrualForDate(Carbon::parse($runDate)->startOfDay());

            $locked->markUsed();

            return $count;
        });
    }
}
