<?php

namespace App\Http\Controllers\Portal;

use App\Http\Controllers\Controller;
use App\Models\Invoice;
use App\Models\PayFastPayment;
use App\Services\PayFastService;
use App\Services\WhmcsInvoicePaymentService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use RuntimeException;

class InvoicePaymentController extends Controller
{
    public function __construct(
        private readonly WhmcsInvoicePaymentService $payments,
        private readonly PayFastService $payFast,
    ) {}

    public function show(Invoice $invoice)
    {
        abort_unless((int) $invoice->user_id === (int) Auth::id(), 404);

        try {
            $this->payments->verifyInvoice(Auth::user(), $invoice);
            $invoice->refresh()->load('items');
        } catch (RuntimeException $e) {
            $invoice->refresh()->load('items');

            if (! $this->hasPendingVerifiedPayment($invoice)) {
                report($e);
                return redirect()->route('client.invoices')->with('error', 'Invoice details could not be refreshed. Please try again.');
            }

            report($e);
            session()->flash('status', 'Your payment is recorded and is being finalized. Do not pay again.');
        }

        if ($this->hasPendingVerifiedPayment($invoice)
            && ! in_array(strtolower((string) $invoice->status), ['paid', 'cancelled', 'refunded'], true)) {
            $invoice->status = 'payment_pending_whmcs';
        }

        return view('client.invoice-show', [
            'invoice' => $invoice,
            'bankChannels' => config('zt.manual_payments.bank_channels', []),
            'payfastEnabled' => $this->payFast->isEnabled(),
        ]);
    }

    public function pay(Invoice $invoice): RedirectResponse
    {
        abort_unless((int) $invoice->user_id === (int) Auth::id(), 404);

        return redirect()->route('client.invoices.show', $invoice);
    }

    public function payfast(Invoice $invoice)
    {
        abort_unless((int) $invoice->user_id === (int) Auth::id(), 404);

        try {
            $this->payments->verifyInvoice(Auth::user(), $invoice);
            $invoice->refresh()->load('items');

            if (! $this->payments->isPayable($invoice)) {
                return redirect()->route('client.invoices.show', $invoice)
                    ->with('error', 'This invoice is not payable in its current status.');
            }

            if ($this->hasPendingVerifiedPayment($invoice)) {
                return redirect()->route('client.invoices.show', $invoice)
                    ->with('status', 'A payment has already been received for this invoice and is being finalized. Do not pay again.');
            }

            $payment = $this->payFast->createPaymentForm(Auth::user(), $invoice);
        } catch (RuntimeException $e) {
            $this->payFast->recordFailure($e, [
                'stage' => 'invoice_payment_start_exception',
                'user_id' => Auth::id(),
                'invoice_id' => $invoice->id,
                'whmcs_invoice_id' => $invoice->whmcs_invoice_id,
                'payment_type' => 'invoice',
            ]);
            report($e);
            return redirect()->route('client.invoices.show', $invoice)
                ->with('error', 'Online payment could not be started. Please try again or contact support.');
        }

        return view('client.payfast-redirect', [
            'invoice' => $invoice,
            'payment' => $payment,
        ]);
    }

    public function payfastSuccess(Request $request): RedirectResponse
    {
        $payment = $this->payFast->paymentFromPayload($request->all());

        if (! $payment || ! $payment->invoice) {
            return $this->returnWithoutPayment('PayFast returned successfully, but the payment reference could not be found. Contact support before paying again.');
        }

        $this->assertPaymentOwner($payment);
        $payment->refresh();

        if ($payment->status === 'completed') {
            return $this->returnToInvoice($payment, 'status', 'Payment verified successfully. The invoice has been updated.');
        }

        if (in_array($payment->status, ['failed', 'rejected', 'manual_review'], true)) {
            return $this->returnToInvoice(
                $payment,
                'error',
                'The payment could not be verified. Please contact support before trying again.'
            );
        }

        return $this->returnToInvoice(
            $payment,
            'status',
            'Payment received. Verification is in progress and the invoice will update automatically.'
        );
    }

    public function payfastFailure(Request $request): RedirectResponse
    {
        $payment = $this->payFast->paymentFromPayload($request->all());

        if (! $payment || ! $payment->invoice) {
            return $this->returnWithoutPayment('Payment was cancelled or failed.');
        }

        $this->assertPaymentOwner($payment);
        $payment->refresh();

        // A browser failure redirect must never undo a payment already confirmed
        // by the independent PayFast server callback.
        if ($payment->status === 'completed') {
            return $this->returnToInvoice($payment, 'status', 'Payment was already verified and the invoice has been updated.');
        }

        return $this->returnToInvoice(
            $payment,
            'error',
            $payment->payfast_error_message ?: 'Payment was cancelled or failed.'
        );
    }


    private function hasPendingVerifiedPayment(Invoice $invoice): bool
    {
        return PayFastPayment::query()
            ->where('invoice_id', $invoice->id)
            ->whereIn('status', ['verified', 'whmcs_sync_failed', 'completed', 'manual_review'])
            ->exists();
    }

    private function assertPaymentOwner(PayFastPayment $payment): void
    {
        if (Auth::check() && (int) $payment->user_id !== (int) Auth::id()) {
            abort(404);
        }
    }

    private function returnToInvoice(PayFastPayment $payment, string $key, string $message): RedirectResponse
    {
        if (! Auth::check()) {
            return redirect()->route('login')->with($key, $message);
        }

        return redirect()->route('client.invoices.show', $payment->invoice)->with($key, $message);
    }

    private function returnWithoutPayment(string $message): RedirectResponse
    {
        if (! Auth::check()) {
            return redirect()->route('login')->with('error', $message);
        }

        return redirect()->route('client.invoices')->with('error', $message);
    }
}
