<?php

namespace App\Services;

use App\Jobs\EnableCustomerAfterOnlinePaymentJob;
use App\Models\Account;
use App\Models\ActivityLog;
use App\Models\Billing;
use App\Models\Customer;
use App\Models\OnlinePaymentTransaction;
use App\Models\Setting;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class UddoktaPayService
{
    public function __construct(private SettingService $settings) {}

    public function enabled(): bool
    {
        return $this->settings->get('uddoktapay.enabled', '0') === '1' && filled($this->baseUrl()) && filled($this->apiKey());
    }

    public function checkout(Customer $customer, Billing $billing, string $ip, string $email): string
    {
        throw_unless($this->enabled(), ValidationException::withMessages(['payment' => 'Online payment is not configured.']));
        throw_unless(hash_equals((string) $customer->ip_address, trim($ip)), ValidationException::withMessages(['ip_address' => 'IP address does not match your customer account.']));
        abort_unless($billing->customer_id === $customer->id && in_array($billing->status, ['pending', 'partial'], true), 404);

        $amount = (float) ($billing->due_amount ?? $billing->amount);
        throw_if($amount <= 0, ValidationException::withMessages(['payment' => 'This bill has no payable due.']));
        $transaction = OnlinePaymentTransaction::create(['reference' => (string) Str::uuid(), 'customer_id' => $customer->id, 'billing_id' => $billing->id, 'amount' => $amount, 'ip_address' => $ip, 'status' => 'initiated']);

        $response = Http::acceptJson()->asJson()->timeout(20)->connectTimeout(5)->withHeaders(['RT-UDDOKTAPAY-API-KEY' => $this->apiKey()])->post($this->baseUrl().'/api/checkout-v2', [
            'full_name' => $customer->name,
            'email' => $email,
            'amount' => number_format($amount, 2, '.', ''),
            'metadata' => ['reference' => $transaction->reference, 'customer_id' => $customer->id, 'billing_id' => $billing->id, 'ip_address' => $ip],
            'redirect_url' => route('portal.payment.callback'),
            'return_type' => 'GET',
            'cancel_url' => route('portal.dashboard', ['payment' => 'cancelled']),
        ]);
        if ($response->failed() || ! $response->json('payment_url')) {
            $transaction->update(['status' => 'checkout_failed', 'gateway_data' => $response->json() ?: ['body' => str($response->body())->limit(500)->toString()]]);
            throw ValidationException::withMessages(['payment' => 'Payment gateway could not create checkout. Please try again.']);
        }

        return $response->json('payment_url');
    }

    public function verify(string $invoiceId, Customer $portalCustomer): OnlinePaymentTransaction
    {
        $response = Http::acceptJson()->asJson()->timeout(20)->connectTimeout(5)->withHeaders(['RT-UDDOKTAPAY-API-KEY' => $this->apiKey()])->post($this->baseUrl().'/api/verify-payment', ['invoice_id' => $invoiceId]);
        throw_if($response->failed(), ValidationException::withMessages(['payment' => 'Payment verification failed.']));
        $data = $response->json();
        $reference = (string) data_get($data, 'metadata.reference');
        $transaction = OnlinePaymentTransaction::where('reference', $reference)->firstOrFail();
        abort_unless($transaction->customer_id === $portalCustomer->id, 403);

        $valid = ($data['status'] ?? null) === 'COMPLETED'
            && hash_equals((string) $transaction->amount, number_format((float) ($data['amount'] ?? 0), 2, '.', ''))
            && (int) data_get($data, 'metadata.customer_id') === $transaction->customer_id
            && (int) data_get($data, 'metadata.billing_id') === $transaction->billing_id
            && hash_equals($transaction->ip_address, (string) data_get($data, 'metadata.ip_address'));
        throw_unless($valid, ValidationException::withMessages(['payment' => 'Payment information did not pass security verification.']));

        DB::transaction(function () use ($transaction, $invoiceId, $data): void {
            $locked = OnlinePaymentTransaction::whereKey($transaction->id)->lockForUpdate()->firstOrFail();
            if ($locked->status === 'completed') {
                return;
            }
            $transactionId = (string) ($data['transaction_id'] ?? '');
            abort_if($transactionId === '' || OnlinePaymentTransaction::where('transaction_id', $transactionId)->whereKeyNot($locked->id)->exists(), 409, 'Duplicate payment transaction.');

            Account::create(['customer_id' => $locked->customer_id, 'type' => 1, 'amount' => $locked->amount, 'description' => 'UddoktaPay online payment for: '.now()->format('Y-m').' | TXN: '.$transactionId, 'entry_date' => today()]);
            $credit = (float) Account::where('customer_id', $locked->customer_id)->whereIn('type', [1, 99])->sum('amount');
            foreach (Billing::where('customer_id', $locked->customer_id)->orderBy('due_date')->orderBy('id')->lockForUpdate()->get() as $bill) {
                $paid = min((float) $bill->amount, max(0, $credit));
                $credit -= $paid;
                $due = max(0, (float) $bill->amount - $paid);
                $bill->forceFill(['paid_amount' => $paid, 'due_amount' => $due, 'status' => $due <= 0 ? 'paid' : ($paid > 0 ? 'partial' : 'pending')])->save();
            }
            $customer = Customer::with('package')->lockForUpdate()->findOrFail($locked->customer_id);
            $packagePrice = (float) ($customer->monthly_bill ?: $customer->package?->price);
            if ($packagePrice > 0) {
                $baseDate = $customer->mikrotik_disconnect?->isFuture() ? $customer->mikrotik_disconnect->copy() : now();
                $amount = (float) $locked->amount;
                $fullMonths = (int) floor($amount / $packagePrice);
                $remainingAmount = $amount - ($fullMonths * $packagePrice);
                $partialDays = (int) floor(($remainingAmount * 30) / $packagePrice);

                if ($fullMonths > 0) {
                    $baseDate->addMonthsNoOverflow($fullMonths);
                }
                if ($partialDays > 0 || $fullMonths === 0) {
                    $baseDate->addDays(max(1, $partialDays));
                }

                $customer->forceFill(['mikrotik_disconnect' => $baseDate->toDateString(), 'status' => 1])->save();
                ActivityLog::where('action', 'mikrotik_due_disconnected')
                    ->where('resource_type', Customer::class)
                    ->where('resource_id', $customer->id)
                    ->delete();
            }
            $locked->update(['gateway_invoice_id' => $invoiceId, 'transaction_id' => $transactionId, 'status' => 'completed', 'gateway_data' => $data, 'verified_at' => now()]);
        });
        if (Setting::getValue('automation.payment_activation', '1') === '1') {
            try {
                // A verified portal payment should restore an available router
                // immediately; the queue remains the retry path for outages.
                EnableCustomerAfterOnlinePaymentJob::dispatchSync($transaction->customer_id);
            } catch (\Throwable $exception) {
                Log::warning('Immediate online payment activation failed; queued for retry.', [
                    'customer_id' => $transaction->customer_id,
                    'error' => $exception->getMessage(),
                ]);
                EnableCustomerAfterOnlinePaymentJob::dispatch($transaction->customer_id);
            }
        }

        return $transaction->fresh();
    }

    private function baseUrl(): string
    {
        return rtrim((string) $this->settings->get('uddoktapay.base_url', ''), '/');
    }

    private function apiKey(): string
    {
        $value = trim((string) $this->settings->get('uddoktapay.api_key', ''));
        if ($value === '') {
            return '';
        }

        try {
            return Crypt::decryptString($value);
        } catch (\Throwable) {
            return $value;
        }
    }
}
