<?php

declare(strict_types=1);

namespace App\Livewire;

use App\Enums\BookingStatus;
use App\Enums\PaymentMethod;
use App\Enums\PaymentStatus;
use App\Models\Booking;
use App\Services\Payments\PaymentService;
use Livewire\Attributes\Computed;
use Livewire\Component;
use Livewire\WithFileUploads;
use Throwable;

/**
 * Pay for a held booking.
 *
 * The countdown is the reason this component polls. Everything on this screen
 * is true for at most thirty minutes, and the customer must not be able to
 * sit on a dead page believing their slot is still theirs — nor be told it
 * expired while an admin is midway through approving their transfer.
 *
 * The card route is presented first and the transfer route second, because
 * every payment made by link confirms itself and removes an admin task.
 */
class Checkout extends Component
{
    use WithFileUploads;

    public Booking $booking;

    public ?string $error = null;

    public bool $showTransfer = false;

    public $proof;

    public function mount(Booking $booking): void
    {
        $this->booking = $booking;
    }

    #[Computed]
    public function secondsRemaining(): int
    {
        return $this->booking->holdSecondsRemaining();
    }

    #[Computed]
    public function isExpired(): bool
    {
        return $this->booking->status === BookingStatus::Expired
            || ($this->booking->status === BookingStatus::PendingPayment
                && $this->secondsRemaining === 0);
    }

    #[Computed]
    public function isSettled(): bool
    {
        return in_array($this->booking->status, [
            BookingStatus::Confirmed,
            BookingStatus::Completed,
        ], true);
    }

    #[Computed]
    public function awaitingReview(): bool
    {
        return $this->booking->status === BookingStatus::PaymentSubmitted;
    }

    #[Computed]
    public function cardPayment()
    {
        return $this->booking->payments()
            ->where('method', PaymentMethod::RevolutLink->value)
            ->where('status', PaymentStatus::Pending->value)
            ->latest()
            ->first();
    }

    /**
     * Poll target. Cheap: one query, and it stops mattering the moment the
     * booking settles.
     */
    public function refreshStatus(): void
    {
        $this->booking->refresh();
        unset($this->secondsRemaining, $this->isExpired, $this->isSettled, $this->awaitingReview);
    }

    public function payByCard(PaymentService $payments)
    {
        $this->error = null;

        if ($this->isExpired) {
            $this->error = 'This booking has expired.';

            return null;
        }

        try {
            $payment = $payments->startCardPayment($this->booking);
        } catch (Throwable $e) {
            // Never show a gateway error verbatim — it can carry internal
            // detail, and the customer can do nothing with it either way.
            report($e);

            $this->error = 'We could not open the payment page. Please try the bank transfer below, or contact the studio.';

            return null;
        }

        return $this->redirect($payment->checkout_url);
    }

    public function submitTransfer(PaymentService $payments): void
    {
        $this->error = null;

        if ($this->isExpired) {
            $this->error = 'This booking has expired.';

            return;
        }

        $this->validate([
            // Images only, and capped: this is a screenshot of a banking app,
            // not a file the studio needs at full resolution.
            'proof' => ['nullable', 'image', 'max:5120'],
        ]);

        $disk = null;
        $path = null;

        if ($this->proof !== null) {
            $disk = config('filesystems.default');
            $path = $this->proof->store('payment-proofs', $disk);
        }

        $payments->submitTransferProof($this->booking, $disk, $path);

        $this->booking->refresh();
        $this->proof = null;
        unset($this->secondsRemaining, $this->isExpired, $this->awaitingReview);
    }

    #[Computed]
    public function bankDetails(): array
    {
        return [
            'name' => config('studio.bank.account_name'),
            'number' => config('studio.bank.account_number'),
            'sort' => config('studio.bank.sort_code'),
        ];
    }

    #[Computed]
    public function startsAtLabel(): string
    {
        return $this->booking->starts_at
            ->setTimezone(config('studio.timezone'))
            ->format('l j F Y, H:i');
    }

    #[Computed]
    public function endsAtLabel(): string
    {
        return $this->booking->ends_at
            ->setTimezone(config('studio.timezone'))
            ->format('H:i');
    }

    public function render()
    {
        return view('livewire.checkout')->layout('layouts.site');
    }
}
