<?php

namespace App\Services;

use App\Enums\PaymentStatusEnum;
use App\Enums\ProviderWithdrawalStatusEnum;
use App\Models\PaymentSplit;
use App\Models\Provider;
use App\Models\ProviderWithdrawal;
use App\Services\Pagarme\PagarmeTransferService;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ProviderWithdrawalService
{
    public function __construct(
        private readonly PagarmeTransferService $pagarmeTransfer,
        private readonly ProviderBankAccountService $providerBankAccounts,
    ) {}

    public function handleTransferWebhook(array $data): void
    {
        $transferId = data_get($data, 'id');

        if (empty($transferId)) {
            return;
        }

        $withdrawal = ProviderWithdrawal::query()
            ->where('transfer_id', $transferId)
            ->first();

        if (! $withdrawal) {
            Log::channel('pagarme')->warning('Saque do prestador não encontrado para o webhook de transferência', [
                'transfer_id' => $transferId,
            ]);

            return;
        }

        $status = data_get($data, 'status');

        if ($status === ProviderWithdrawalStatusEnum::TRANSFERRED->value) {
            $withdrawal->forceFill([
                'status'       => ProviderWithdrawalStatusEnum::TRANSFERRED,
                'completed_at' => now(),
            ])->save();
        } elseif (in_array($status, [ProviderWithdrawalStatusEnum::FAILED->value, ProviderWithdrawalStatusEnum::CANCELED->value], true)) {
            $withdrawal->paymentSplits()
                ->update(['provider_withdrawal_id' => null]);

            $withdrawal->forceFill([
                'status'        => ProviderWithdrawalStatusEnum::fromString($status),
                'failed_at'     => $status === ProviderWithdrawalStatusEnum::FAILED->value ? now() : null,
                'bank_response' => $status === ProviderWithdrawalStatusEnum::FAILED->value ? (data_get($data, 'bank_response')) : null,
            ])->save();
        } elseif ($status === ProviderWithdrawalStatusEnum::PROCESSING->value) {
            $withdrawal->forceFill([
                'status' => ProviderWithdrawalStatusEnum::PROCESSING,
            ])->save();
        }
    }

    //

    public function getAvailableBalance(Provider $provider): float
    {
        $earnings = (float) PaymentSplit::query()
            ->where('provider_id', $provider->id)
            ->whereHas('payment', fn ($q) => $this->availablePaymentQuery($q))
            ->sum('gross_amount');

        $withdrawn = (float) ProviderWithdrawal::query()
            ->where('provider_id', $provider->id)
            ->whereIn('status', [
                ProviderWithdrawalStatusEnum::PENDING_TRANSFER,
                ProviderWithdrawalStatusEnum::PROCESSING,
                ProviderWithdrawalStatusEnum::TRANSFERRED,
            ])
            ->sum('gross_amount');

        return max(0, $earnings - $withdrawn);
    }

    public function getPendingBalance(Provider $provider): float
    {
        return (float) PaymentSplit::query()
            ->where('provider_id', $provider->id)
            ->whereHas('payment', fn ($q) => $this->pendingPaymentQuery($q))
            ->sum('gross_amount');
    }

    //

    public function getPaymentSplits(Provider $provider): Collection
    {
        return PaymentSplit::query()
            ->where('provider_id', $provider->id)
            ->with(['payment.schedule.client.user', 'payment.servicePackage.items.schedule.client.user'])
            ->orderBy('created_at', 'desc')
            ->get();
    }

    //

    public function requestWithdrawal(Provider $provider): ProviderWithdrawal
    {
        if (empty($provider->recipient_id)) {
            throw new \RuntimeException(__('messages.provider_requires_pagarme_recipient'));
        }

        $primaryAccount = $this->providerBankAccounts->ensureGatewayMatchesPrimary($provider);

        return DB::transaction(function () use ($provider, $primaryAccount) {
            Provider::query()
                ->where('id', $provider->id)
                ->lockForUpdate()
                ->first();

            $pending = ProviderWithdrawal::query()
                ->where('provider_id', $provider->id)
                ->whereIn('status', [
                    ProviderWithdrawalStatusEnum::PENDING_TRANSFER,
                    ProviderWithdrawalStatusEnum::PROCESSING,
                ])
                ->exists();

            if ($pending) {
                throw new \RuntimeException(__('messages.withdrawal_already_in_progress'));
            }

            $available = $this->getAvailableBalance($provider);

            if ($available <= 0) {
                throw new \RuntimeException(__('messages.insufficient_withdrawal_balance'));
            }

            $paymentTransferFeeAmount = $this->paymentTransferFeeAmount();

            $netAmount = max(0, round($available - $paymentTransferFeeAmount, 2));

            if ($netAmount <= 0) {
                throw new \RuntimeException(__('messages.insufficient_balance_for_transfer_fee'));
            }

            $amountInCents = (int) round($netAmount * 100);

            $idempotencyKey = sprintf('withdrawal-%d-%s', $provider->id, now()->timestamp);

            $bankAccount = $primaryAccount->toGatewayBankAccount();

            $withdrawal = ProviderWithdrawal::create([
                'provider_id'        => $provider->id,
                'recipient_id'       => $provider->recipient_id,
                'idempotency_key'    => $idempotencyKey,
                'gross_amount'       => $available,
                'gateway_fee_amount' => $paymentTransferFeeAmount,
                'net_amount'         => $netAmount,
                'status'             => ProviderWithdrawalStatusEnum::PENDING_TRANSFER,
                'bank_account'       => $bankAccount,

                'metadata' => [
                    'provider_id'                  => $provider->id,
                    'provider_bank_account_id'     => $primaryAccount->id,
                    'gross_amount'                 => $available,
                    'payment_transfer_fee_amount'  => $paymentTransferFeeAmount,
                    'net_amount'                   => $netAmount,
                ],
            ]);

            try {
                $transfer = $this->pagarmeTransfer->createTransfer(
                    $amountInCents,
                    $provider->recipient_id,
                    $idempotencyKey,
                );

                $withdrawal->forceFill([
                    'transfer_id'        => $transfer->id,
                    'status'             => $transfer->status ?? ProviderWithdrawalStatusEnum::PROCESSING->value,
                    'type'               => $transfer->type,
                    'gateway_payload'    => $transfer->toArray(),
                ])->save();

                PaymentSplit::query()
                    ->where('provider_id', $provider->id)
                    ->whereNull('provider_withdrawal_id')
                    ->whereHas('payment', fn ($q) => $this->availablePaymentQuery($q))
                    ->update(['provider_withdrawal_id' => $withdrawal->id]);

                return $withdrawal->fresh();
            } catch (\Throwable $e) {
                $withdrawal->forceFill([
                    'status'        => ProviderWithdrawalStatusEnum::FAILED,
                    'failed_at'     => now(),
                    'bank_response' => $e->getMessage(),
                ])->save();

                throw $e;
            }
        });
    }

    //

    public function getWithdrawals(Provider $provider): Collection
    {
        return ProviderWithdrawal::query()
            ->where('provider_id', $provider->id)
            ->orderBy('created_at', 'desc')
            ->get();
    }

    public function getAllWithdrawals(): Collection
    {
        return ProviderWithdrawal::query()
            ->with(['provider.user', 'paymentSplits'])
            ->orderBy('created_at', 'desc')
            ->get();
    }

    public function getWithdrawal(int $id, Provider $provider): ProviderWithdrawal
    {
        return ProviderWithdrawal::query()
            ->where('id', $id)
            ->where('provider_id', $provider->id)
            ->firstOrFail();
    }

    //

    public function getWithdrawalFees(): array
    {
        return [
            'payment_transfer_fee_amount' => $this->paymentTransferFeeAmount(),
        ];
    }

    private function paymentTransferFeeAmount(): float
    {
        return round((float) config('services.pagarme.transfer_fee_amount'), 2);
    }

    //

    private function availableScheduleQuery($query)
    {
        return $query
            ->where('code_verified', true)
            ->when(
                ! app()->environment('local', 'development'),
                fn ($query) => $query->whereRaw(
                    '(date + end_time) <= ?',
                    [$this->withdrawalReleaseCutoff()]
                )
            );
    }

    private function unavailableScheduleQuery($query)
    {
        return $query->where(function ($query) {
            $query->where('code_verified', false)
                ->when(
                    ! app()->environment('local', 'development'),
                    fn ($query) => $query->orWhereRaw(
                        '(date + end_time) > ?',
                        [$this->withdrawalReleaseCutoff()]
                    )
                );
        });
    }

    private function availablePaymentQuery($query)
    {
        return $query
            ->where('status', PaymentStatusEnum::PAID)
            ->where(function ($query) {
                $query->whereHas('schedule', fn ($query) => $this->availableScheduleQuery($query))
                    ->orWhere(function ($query) {
                        $query->whereNotNull('service_package_id')
                            ->whereHas('servicePackage.items')
                            ->whereDoesntHave('servicePackage.items.schedule', fn ($query) => $this->unavailableScheduleQuery($query));
                    });
            });
    }

    private function pendingPaymentQuery($query)
    {
        return $query
            ->where('status', PaymentStatusEnum::PAID)
            ->where(function ($query) {
                $query->whereHas('schedule', fn ($query) => $this->pendingScheduleQuery($query))
                    ->orWhereHas('servicePackage.items.schedule', fn ($query) => $this->pendingScheduleQuery($query));
            });
    }

    private function pendingScheduleQuery($query)
    {
        return $query
            ->whereNotIn('status', ['cancelled', 'rejected'])
            ->where(function ($q) {
                $q->where('code_verified', false)
                    ->orWhereRaw(
                        '(date + end_time) > ?',
                        [$this->withdrawalReleaseCutoff()]
                    );
            });
    }

    private function withdrawalReleaseCutoff(): string
    {
        $releaseDays = max(0, (int) config('services.pagarme.withdrawal_release_days'));

        return Carbon::now()->subDays($releaseDays)->format('Y-m-d H:i:s');
    }
}
