<?php

namespace App\Livewire\Portal;

use App\Domain\Financial\Models\Invoice;
use App\Domain\Financial\Services\PaymentProofService;
use App\Domain\Identity\Services\BranchSettingsService;
use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Exceptions\DomainException;
use App\Livewire\Portal\Concerns\PortalScreen;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Title;
use App\Domain\Shared\Traits\ChecksTemporaryUploads;
use Livewire\Component;
use Livewire\WithFileUploads;

/**
 * "I transferred the money" — the member's half of the InstaPay flow.
 *
 * Nothing here creates a payment. It records a claim, and staff turn that into
 * money only after checking the academy's own statement.
 */
#[Layout('layouts.portal')]
#[Title('تسجيل تحويل')]
class PortalPayProof extends Component
{
    use PortalScreen, WithFileUploads, ChecksTemporaryUploads;

    #[Locked]
    public int $invoiceId;

    public string $amount = '';

    public string $senderReference = '';

    public string $senderPhone = '';

    public string $transferredAt = '';

    public $proof;

    public function mount(string $invoice): void
    {
        $this->authorizePortal('portal.pay');

        $found = Invoice::where('uuid', $invoice)->firstOrFail();

        $this->assertPayable($found);

        $this->invoiceId = $found->id;
        $this->amount = number_format($found->due_amount / 100, 2, '.', '');
        $this->senderPhone = auth()->user()?->phone ?? '';
        $this->transferredAt = now()->format('Y-m-d');
    }

    protected function rules(): array
    {
        return [
            'amount' => ['required', 'numeric', 'min:0.01', 'max:9999999'],
            'senderReference' => ['required', 'string', 'min:4', 'max:64'],
            'senderPhone' => ['nullable', 'string', 'max:20'],
            'transferredAt' => ['required', 'date', 'before_or_equal:today'],
            // Images and PDFs only, and small enough that a phone can send it
            // on a bad connection.
            'proof' => ['required', 'file', 'max:' . config('uploads.max_kb'), 'mimes:png,jpg,jpeg,webp,pdf'],
        ];
    }

    protected function messages(): array
    {
        return [
            'amount.required' => __('المبلغ مطلوب'),
            'amount.numeric' => __('المبلغ يجب أن يكون رقماً'),
            'amount.min' => __('المبلغ يجب أن يكون أكبر من صفر'),
            'senderReference.required' => __('رقم عملية التحويل مطلوب'),
            'senderReference.min' => __('رقم العملية قصير جداً'),
            'transferredAt.required' => __('تاريخ التحويل مطلوب'),
            'transferredAt.before_or_equal' => __('لا يمكن أن يكون تاريخ التحويل في المستقبل'),
            'proof.required' => __('صورة إثبات التحويل مطلوبة'),
            'proof.mimes' => __('الملف يجب أن يكون صورة أو PDF'),
            'proof.max' => __('حجم الملف يتجاوز :mb ميجابايت', ['mb' => intdiv((int) config('uploads.max_kb'), 1024)]),
        ];
    }

    public function submit(PaymentProofService $proofs): void
    {
        if (! $this->temporaryUploadIsUsable('proof', $this->proof)) {
            return;
        }

        $this->validate();

        $invoice = Invoice::findOrFail($this->invoiceId);

        // Re-checked here and not only in mount(): the invoice may have been
        // settled or cancelled between rendering the form and submitting it.
        $this->assertPayable($invoice);

        // The file lands on the private disk. A transfer screenshot carries a
        // phone number, a name and a bank balance — the parent excuse form
        // wrote its attachments to the public disk and that is exactly the
        // mistake not to repeat.
        $path = $this->proof->store("proofs/{$invoice->academy_id}", 'local');

        try {
            $proofs->submit([
                'invoice_id' => $invoice->id,
                'branch_id' => $invoice->branch_id ?: $this->resolveBranchId($invoice),
                'amount_claimed' => (int) round(((float) $this->amount) * 100),
                'method' => 'instapay',
                'sender_reference' => $this->senderReference,
                'sender_phone' => $this->senderPhone ?: null,
                'transferred_at' => $this->transferredAt,
                'proof_path' => $path,
                'proof_mime' => $this->proof->getMimeType(),
                'proof_size' => $this->proof->getSize(),
            ], auth()->user());
        } catch (DomainException $e) {
            // Never leave an orphan file behind when the record was refused.
            \Illuminate\Support\Facades\Storage::disk('local')->delete($path);

            $this->addError('senderReference', $e->getMessage());

            return;
        }

        session()->flash('success', __('تم استلام إثبات التحويل — ستراجعه الإدارة قريباً'));

        $this->redirect(route('portal.invoice', $invoice->uuid), navigate: true);
    }

    public function render()
    {
        $invoice = Invoice::findOrFail($this->invoiceId);
        $branchId = $invoice->branch_id ?: $this->resolveBranchId($invoice);

        $settings = app(BranchSettingsService::class);

        return view('livewire.portal.portal-pay-proof', [
            'invoice' => $invoice,
            // Per-branch handle and instructions live in branch_settings, the
            // generic per-branch key-value store that already exists.
            'handle' => $branchId ? $settings->get($branchId, 'instapay.handle') : null,
            'instructions' => $branchId ? $settings->get($branchId, 'instapay.instructions') : null,
        ]);
    }

    private function assertPayable(Invoice $invoice): void
    {
        abort_unless(
            $invoice->billable_type === Participant::class
            && $this->portal()->mayPayFor((int) $invoice->billable_id),
            403,
            __('لا تملك صلاحية الدفع عن هذا العضو')
        );

        if ($invoice->due_amount <= 0) {
            abort(403, __('لا يوجد مستحق على هذه الفاتورة'));
        }
    }

    /**
     * Branch is required at the service boundary. A payment with no branch
     * lands in the all-branches total and in no branch, and the columns stop
     * summing with no error anywhere.
     */
    private function resolveBranchId(Invoice $invoice): ?int
    {
        if ($invoice->billable_type !== Participant::class) {
            return null;
        }

        $participant = Participant::find($invoice->billable_id);

        return $participant?->branch_id
            ?? auth()->user()?->preferred_branch_id
            ?? auth()->user()?->branch_id;
    }
}
