<?php

namespace App\Services\Payments;

use App\Contracts\BanglaQrProviderInterface;
use App\Models\ManualPaymentVerification;
use App\Models\PaymentCallbackLog;
use App\Models\PaymentRequest;
use App\Models\PaymentTransaction;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class BanglaQrPaymentService
{
    public function __construct(private readonly BanglaQrProviderInterface $provider)
    {
    }

    public function initiatePaymentRequest(PaymentRequest $paymentRequest, ?int $adminId = null): PaymentTransaction
    {
        return $this->initiate([
            'source_type' => $paymentRequest->attribute ?: 'payment_request',
            'source_id' => $paymentRequest->attribute_id,
            'source_reference' => $paymentRequest->attribute . '-' . $paymentRequest->attribute_id,
            'order_id' => $paymentRequest->attribute === 'order' ? $paymentRequest->attribute_id : null,
            'payment_request_id' => $paymentRequest->id,
            'amount' => (float) $paymentRequest->payment_amount,
            'created_by_admin_id' => $adminId,
        ]);
    }

    public function initiate(array $data): PaymentTransaction
    {
        $amount = round((float) ($data['amount'] ?? 0), 8);
        if ($amount <= 0) {
            throw ValidationException::withMessages([
                'amount' => translate('Payment amount can not be 0'),
            ]);
        }

        $sourceType = $data['source_type'] ?? 'manual';
        $sourceId = $data['source_id'] ?? null;

        $transaction = PaymentTransaction::where('payment_method', 'bangla_qr')
            ->where('source_type', $sourceType)
            ->where('source_id', $sourceId)
            ->where('amount', $amount)
            ->whereIn('status', ['pending', 'initiated', 'pending_provider_setup'])
            ->latest()
            ->first();

        if ($transaction) {
            return $transaction;
        }

        $transaction = PaymentTransaction::create([
            'transaction_id' => (string) Str::uuid(),
            'payment_method' => 'bangla_qr',
            'provider_name' => $this->setting('provider', 'sslcommerz'),
            'merchant_id' => $this->setting('merchant_id'),
            'terminal_id' => $this->setting('terminal_id'),
            'store_id' => $this->setting('store_id'),
            'qr_type' => 'dynamic',
            'source_type' => $sourceType,
            'source_id' => $sourceId,
            'source_reference' => $data['source_reference'] ?? null,
            'order_id' => $data['order_id'] ?? null,
            'payment_request_id' => $data['payment_request_id'] ?? null,
            'amount' => $amount,
            'currency' => $data['currency'] ?? 'BDT',
            'status' => 'pending',
            'expires_at' => now()->addMinutes((int) $this->setting('expiry_minutes', 10)),
            'created_by_admin_id' => $data['created_by_admin_id'] ?? null,
        ]);

        if (! $this->setting('initiate_url')) {
            $transaction->update([
                'status' => 'pending_provider_setup',
                'request_payload' => [
                    'message' => 'Bangla QR dynamic API initiate_url is not configured.',
                ],
            ]);

            return $transaction->fresh();
        }

        $payload = [
            'merchant_id' => $this->setting('merchant_id'),
            'terminal_id' => $this->setting('terminal_id'),
            'store_id' => $this->setting('store_id'),
            'amount' => $transaction->amount,
            'currency' => $transaction->currency,
            'reference' => $transaction->transaction_id,
            'callback_url' => route('bangla-qr.callback'),
            'expires_at' => optional($transaction->expires_at)->toIso8601String(),
        ];

        $providerResponse = $this->provider->initiatePayment($transaction, $payload);
        $body = $providerResponse['body'] ?? [];
        if (empty($providerResponse['configured'])) {
            $transaction->update([
                'status' => 'pending_provider_setup',
                'request_payload' => ['request' => $payload, 'response' => $body],
            ]);

            return $transaction->fresh();
        }

        if (empty($providerResponse['successful'])) {
            $transaction->update([
                'status' => 'initiate_failed',
                'request_payload' => ['request' => $payload, 'response' => $body],
            ]);

            return $transaction->fresh();
        }

        $transaction->update([
            'status' => data_get($body, 'status', 'initiated'),
            'qr_reference' => data_get($body, 'qr_reference', data_get($body, 'reference', $transaction->transaction_id)),
            'qr_payload' => data_get($body, 'qr_payload', data_get($body, 'qr_string', data_get($body, 'qr_url'))),
            'provider_transaction_id' => data_get($body, 'provider_transaction_id', data_get($body, 'transaction_id')),
            'request_payload' => ['request' => $payload, 'response' => $body],
        ]);

        return $transaction->fresh();
    }

    public function handleCallback(Request $request): PaymentTransaction
    {
        $payload = $request->all();
        $headers = collect($request->headers->all())
            ->map(fn ($values) => implode(', ', $values))
            ->all();

        $transaction = $this->findTransactionFromPayload($payload);
        $signatureStatus = $this->provider->verifyCallback($request);

        $log = PaymentCallbackLog::create([
            'payment_transaction_id' => $transaction?->id,
            'provider_name' => $this->setting('provider', 'sslcommerz'),
            'event_type' => data_get($payload, 'event', data_get($payload, 'status')),
            'status' => 'received',
            'signature_status' => $signatureStatus,
            'source_reference' => data_get($payload, 'reference', data_get($payload, 'qr_reference')),
            'headers' => $headers,
            'payload' => $payload,
        ]);

        if (! $transaction) {
            $log->update(['status' => 'failed', 'error_message' => 'Payment transaction not found.']);
            throw ValidationException::withMessages([
                'payment' => translate('Payment transaction not found.'),
            ]);
        }

        if ($signatureStatus !== 'valid') {
            $transaction->update(['callback_payload' => $payload]);
            $log->update(['status' => 'ignored', 'error_message' => 'Callback signature is not valid.']);
            return $transaction->fresh();
        }

        $callbackAmount = data_get($payload, 'amount');
        if ($callbackAmount !== null && abs((float) $callbackAmount - (float) $transaction->amount) > 0.00001) {
            $transaction->update(['callback_payload' => $payload, 'status' => 'amount_mismatch']);
            $log->update(['status' => 'failed', 'error_message' => 'Callback amount mismatch.']);
            return $transaction->fresh();
        }

        $status = strtolower((string) data_get($payload, 'status', data_get($payload, 'payment_status', '')));
        if (! in_array($status, ['success', 'successful', 'paid', 'completed', 'complete'], true)) {
            $transaction->update(['callback_payload' => $payload, 'status' => $status ?: 'callback_received']);
            $log->update(['status' => 'processed', 'processed_at' => now()]);
            return $transaction->fresh();
        }

        if ($transaction->status !== 'paid') {
            $providerStatus = $this->provider->checkStatus($transaction);
            if (! empty($providerStatus['configured'])) {
                $verifiedTransaction = $this->applyProviderStatus($transaction, $providerStatus['body'] ?? []);
                if ($verifiedTransaction->status !== 'paid' || ! $verifiedTransaction->verified_by_system) {
                    $log->update([
                        'status' => 'pending_provider_verification',
                        'error_message' => 'Signed callback received, but provider status API has not confirmed success.',
                    ]);

                    return $verifiedTransaction;
                }

                $log->update(['status' => 'processed', 'processed_at' => now()]);

                return $verifiedTransaction;
            }

            if (! $this->setting('allow_signed_callback_without_status', false)) {
                $transaction->update([
                    'status' => 'pending_provider_verification',
                    'provider_transaction_id' => data_get($payload, 'provider_transaction_id', data_get($payload, 'transaction_id', $transaction->provider_transaction_id)),
                    'callback_payload' => $payload,
                    'verification_payload' => [
                        'message' => 'Signed callback received, but status API is not configured.',
                    ],
                ]);
                $log->update([
                    'status' => 'pending_provider_verification',
                    'error_message' => 'Status API is required before marking Bangla QR payment paid.',
                ]);

                return $transaction->fresh();
            }

            $this->markTransactionPaid($transaction, [
                'provider_transaction_id' => data_get($payload, 'provider_transaction_id', data_get($payload, 'transaction_id', $transaction->provider_transaction_id)),
                'callback_payload' => $payload,
                'verified_by_system' => true,
                'paid_at' => now(),
            ]);
            $this->markPaymentRequestPaid($transaction->fresh());
        }

        $log->update(['status' => 'processed', 'processed_at' => now()]);

        return $transaction->fresh();
    }

    public function status(PaymentTransaction $transaction): array
    {
        $providerStatus = $this->provider->checkStatus($transaction);
        if (empty($providerStatus['configured'])) {
            return [
                'status' => $transaction->status,
                'verified_by_system' => $transaction->verified_by_system,
                'manually_verified' => $this->isManuallyApproved($transaction),
                'message' => 'Bangla QR status API is not configured.',
            ];
        }

        $body = $providerStatus['body'] ?? [];
        $transaction = $this->applyProviderStatus($transaction, $body);

        return [
            'status' => $transaction->fresh()->status,
            'verified_by_system' => (bool) $transaction->fresh()->verified_by_system,
            'manually_verified' => $this->isManuallyApproved($transaction->fresh()),
            'provider_response' => $body,
        ];
    }

    public function submitManualVerification(array $data, ?int $adminId = null): ManualPaymentVerification
    {
        $amount = round((float) ($data['amount'] ?? 0), 8);
        $submittedTransactionId = trim((string) ($data['submitted_transaction_id'] ?? ''));
        if ($amount <= 0 || $submittedTransactionId === '') {
            throw ValidationException::withMessages([
                'manual_verification' => translate('Amount and customer transaction ID are required for static Bangla QR verification.'),
            ]);
        }

        return DB::transaction(function () use ($data, $adminId, $amount, $submittedTransactionId) {
            $existingPaid = PaymentTransaction::where('payment_method', 'bangla_qr')
                ->where('provider_transaction_id', $submittedTransactionId)
                ->where('status', 'paid')
                ->lockForUpdate()
                ->exists();
            if ($existingPaid) {
                throw ValidationException::withMessages([
                    'submitted_transaction_id' => translate('This Bangla QR transaction ID has already been used.'),
                ]);
            }

            $existingQueue = ManualPaymentVerification::where('submitted_transaction_id', $submittedTransactionId)
                ->whereIn('status', ['pending', 'approved'])
                ->lockForUpdate()
                ->latest()
                ->first();
            if ($existingQueue) {
                return $existingQueue->fresh();
            }

            $transaction = PaymentTransaction::create([
                'transaction_id' => (string) Str::uuid(),
                'payment_method' => 'bangla_qr',
                'provider_name' => $this->setting('provider', 'sslcommerz'),
                'merchant_id' => $this->setting('merchant_id'),
                'terminal_id' => $this->setting('terminal_id'),
                'store_id' => $this->setting('store_id'),
                'qr_type' => 'static',
                'qr_reference' => $data['qr_reference'] ?? null,
                'source_type' => $data['source_type'] ?? 'showroom_pos_static_qr',
                'source_id' => $data['source_id'] ?? null,
                'source_reference' => $data['source_reference'] ?? null,
                'order_id' => $data['order_id'] ?? null,
                'payment_request_id' => $data['payment_request_id'] ?? null,
                'amount' => $amount,
                'currency' => $data['currency'] ?? 'BDT',
                'status' => 'manual_verification_pending',
                'request_payload' => [
                    'submitted_transaction_id' => $submittedTransactionId,
                    'evidence_payload' => $data['evidence_payload'] ?? [],
                ],
                'created_by_admin_id' => $adminId,
            ]);

            return ManualPaymentVerification::create([
                'payment_transaction_id' => $transaction->id,
                'source_type' => $transaction->source_type,
                'source_id' => $transaction->source_id,
                'submitted_transaction_id' => $submittedTransactionId,
                'amount' => $amount,
                'status' => 'pending',
                'evidence_payload' => $data['evidence_payload'] ?? [],
                'submitted_by_admin_id' => $adminId,
                'note' => $data['note'] ?? null,
            ]);
        });
    }

    public function approveManualVerification(ManualPaymentVerification $verification, ?int $adminId = null, ?string $note = null): ManualPaymentVerification
    {
        return DB::transaction(function () use ($verification, $adminId, $note) {
            $verification = ManualPaymentVerification::whereKey($verification->id)->lockForUpdate()->firstOrFail();
            if ($verification->status !== 'pending') {
                throw ValidationException::withMessages([
                    'manual_verification' => translate('Only pending manual verification requests can be approved.'),
                ]);
            }

            $transaction = PaymentTransaction::whereKey($verification->payment_transaction_id)->lockForUpdate()->first();
            if (! $transaction) {
                throw ValidationException::withMessages([
                    'payment_transaction_id' => translate('Linked Bangla QR transaction was not found.'),
                ]);
            }

            $this->markTransactionPaid($transaction, [
                'provider_transaction_id' => $verification->submitted_transaction_id,
                'verification_payload' => [
                    'manual_verification_id' => $verification->id,
                    'verified_by_admin_id' => $adminId,
                    'verified_at' => now()->toIso8601String(),
                    'note' => $note,
                ],
                'verified_by_system' => false,
                'paid_at' => now(),
            ]);
            $this->markPaymentRequestPaid($transaction->fresh());

            $verification->forceFill([
                'status' => 'approved',
                'verified_by_admin_id' => $adminId,
                'verified_at' => now(),
                'note' => $note ?: $verification->note,
            ])->save();

            return $verification->fresh();
        });
    }

    public function rejectManualVerification(ManualPaymentVerification $verification, ?int $adminId = null, ?string $note = null): ManualPaymentVerification
    {
        return DB::transaction(function () use ($verification, $adminId, $note) {
            $verification = ManualPaymentVerification::whereKey($verification->id)->lockForUpdate()->firstOrFail();
            if ($verification->status !== 'pending') {
                throw ValidationException::withMessages([
                    'manual_verification' => translate('Only pending manual verification requests can be rejected.'),
                ]);
            }

            $transaction = PaymentTransaction::whereKey($verification->payment_transaction_id)->lockForUpdate()->first();
            if ($transaction && $transaction->status !== 'paid') {
                $transaction->update([
                    'status' => 'manual_verification_rejected',
                    'verification_payload' => [
                        'manual_verification_id' => $verification->id,
                        'rejected_by_admin_id' => $adminId,
                        'rejected_at' => now()->toIso8601String(),
                        'note' => $note,
                    ],
                ]);
            }

            $verification->forceFill([
                'status' => 'rejected',
                'verified_by_admin_id' => $adminId,
                'verified_at' => now(),
                'note' => $note ?: $verification->note,
            ])->save();

            return $verification->fresh();
        });
    }

    public function isManuallyApproved(PaymentTransaction $transaction): bool
    {
        return ManualPaymentVerification::where('payment_transaction_id', $transaction->id)
            ->where('status', 'approved')
            ->exists();
    }

    private function markPaymentRequestPaid(PaymentTransaction $transaction): void
    {
        if (! $transaction->payment_request_id) {
            return;
        }

        DB::transaction(function () use ($transaction) {
            $paymentRequest = PaymentRequest::whereKey($transaction->payment_request_id)
                ->lockForUpdate()
                ->first();
            if (! $paymentRequest || (int) $paymentRequest->is_paid === 1) {
                return;
            }

            $paymentRequest->payment_method = 'bangla_qr';
            $paymentRequest->is_paid = 1;
            $paymentRequest->transaction_id = $transaction->provider_transaction_id ?: $transaction->transaction_id;
            $paymentRequest->save();

            if (function_exists($paymentRequest->success_hook)) {
                call_user_func($paymentRequest->success_hook, $paymentRequest);
            }
        });
    }

    private function markTransactionPaid(PaymentTransaction $transaction, array $attributes): void
    {
        DB::transaction(function () use ($transaction, $attributes) {
            $transaction = PaymentTransaction::whereKey($transaction->id)->lockForUpdate()->firstOrFail();
            $providerTransactionId = $attributes['provider_transaction_id'] ?? null;
            if ($providerTransactionId) {
                $duplicate = PaymentTransaction::where('payment_method', 'bangla_qr')
                    ->where('provider_transaction_id', $providerTransactionId)
                    ->where('id', '!=', $transaction->id)
                    ->where('status', 'paid')
                    ->lockForUpdate()
                    ->exists();
                if ($duplicate) {
                    throw ValidationException::withMessages([
                        'provider_transaction_id' => translate('This Bangla QR provider transaction ID has already been used.'),
                    ]);
                }
            }

            if ($transaction->status === 'paid'
                && $providerTransactionId
                && $transaction->provider_transaction_id
                && $transaction->provider_transaction_id !== $providerTransactionId) {
                throw ValidationException::withMessages([
                    'provider_transaction_id' => translate('This Bangla QR invoice is already paid with another transaction ID.'),
                ]);
            }

            $transaction->forceFill(array_merge([
                'status' => 'paid',
            ], $attributes))->save();
        });
    }

    private function applyProviderStatus(PaymentTransaction $transaction, array $body): PaymentTransaction
    {
        $status = strtolower((string) data_get($body, 'status', data_get($body, 'payment_status', '')));
        $amount = data_get($body, 'amount', data_get($body, 'paid_amount'));

        if ($amount !== null && abs((float) $amount - (float) $transaction->amount) > 0.00001) {
            $transaction->update([
                'status' => 'amount_mismatch',
                'verification_payload' => $body,
            ]);

            return $transaction->fresh();
        }

        if (in_array($status, ['success', 'successful', 'paid', 'completed', 'complete'], true)) {
            $this->markTransactionPaid($transaction, [
                'provider_transaction_id' => data_get($body, 'provider_transaction_id', data_get($body, 'transaction_id', $transaction->provider_transaction_id)),
                'verification_payload' => $body,
                'verified_by_system' => true,
                'paid_at' => $transaction->paid_at ?: now(),
            ]);

            $this->markPaymentRequestPaid($transaction->fresh());

            return $transaction->fresh();
        }

        if ($status !== '') {
            $transaction->update([
                'status' => $status,
                'verification_payload' => $body,
            ]);
        } else {
            $transaction->update(['verification_payload' => $body]);
        }

        return $transaction->fresh();
    }

    private function findTransactionFromPayload(array $payload): ?PaymentTransaction
    {
        $references = array_filter([
            data_get($payload, 'reference'),
            data_get($payload, 'qr_reference'),
            data_get($payload, 'merchant_reference'),
            data_get($payload, 'provider_transaction_id'),
            data_get($payload, 'transaction_id'),
        ]);

        if ($references === []) {
            return null;
        }

        return PaymentTransaction::where('payment_method', 'bangla_qr')
            ->where(function ($query) use ($references) {
                foreach ($references as $reference) {
                    $query->orWhere('transaction_id', $reference)
                        ->orWhere('qr_reference', $reference)
                        ->orWhere('provider_transaction_id', $reference);
                }
            })
            ->latest()
            ->first();
    }

    private function setting(string $key, mixed $default = null): mixed
    {
        $configValue = config("services.bangla_qr.$key");
        if (filled($configValue)) {
            return $configValue;
        }

        if (! Schema::hasTable('addon_settings')) {
            return $default;
        }

        $settings = DB::table('addon_settings')
            ->where('key_name', 'bangla_qr')
            ->where('settings_type', 'payment_config')
            ->first();

        if (! $settings) {
            return $default;
        }

        $mode = $settings->mode ?: (env('APP_ENV') === 'live' ? 'live' : 'test');
        $column = $mode === 'live' ? 'live_values' : 'test_values';
        $values = json_decode($settings->{$column} ?? '{}', true);

        return data_get($values, $key, $default);
    }
}
