<?php

namespace App\Livewire\Portal;

use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Models\ServiceRequest;
use App\Domain\Shared\Services\ServiceRequestService;
use App\Domain\Training\Models\TrainingSession;
use App\Livewire\Portal\Concerns\PortalScreen;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Title;
use App\Domain\Shared\Traits\ChecksTemporaryUploads;
use Livewire\Component;
use Livewire\WithFileUploads;

/**
 * Where a member asks the academy for something: a freeze, a transfer, a
 * cancellation, or an excuse from a session.
 *
 * The excuse form this replaces validated its input, stored the attachment, and
 * then flashed "تم تقديم العذر بنجاح" while discarding the excuse behind a
 * `// TODO`. Nothing was recorded and nobody at the academy ever saw it. This
 * one writes a real request into a real queue that a real screen reads.
 */
#[Layout('layouts.portal')]
#[Title('تقديم طلب')]
class PortalRequests extends Component
{
    use PortalScreen, WithFileUploads, ChecksTemporaryUploads;

    public string $type = 'excuse';

    #[Locked]
    public ?int $sessionId = null;

    public string $reason = '';

    public ?int $branchId = null;

    public $attachment;

    public function mount(?int $session = null): void
    {
        $this->authorizePortal('portal.requests');

        // Arrives from the training screen's "قدّم عذر" on a session. It is
        // #[Locked] and re-validated in the service against the member's own
        // groups — a session id from the browser is an input, not a fact.
        $this->sessionId = $session;
    }

    protected function rules(): array
    {
        return [
            'type' => ['required', 'in:excuse,freeze,unfreeze,transfer,cancellation,renewal,other'],
            'reason' => ['required', 'string', 'min:5', 'max:1000'],
            'sessionId' => ['nullable', 'integer'],
            // The transfer target. `nullable|integer` was the only check on a
            // value that goes straight into the request metadata and is read
            // back by staff in ServiceRequestQueue, so a crafted payload could
            // name a closed branch, a deleted one, or a branch belonging to a
            // different academy. The <select> is fed every active branch on
            // purpose — a member transferring between branches is the one
            // portal action that is cross-branch by definition — but "every
            // active branch of this academy" is exactly what the rule has to
            // say, rather than "any integer".
            'branchId' => [
                'nullable',
                'integer',
                Rule::exists('branches', 'id')
                    ->where('academy_id', app('current_academy')->id)
                    ->where('is_active', true)
                    ->whereNull('deleted_at'),
            ],
            'attachment' => ['nullable', 'file', 'max:' . config('uploads.max_kb'), 'mimes:png,jpg,jpeg,webp,pdf'],
        ];
    }

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

    public function submit(ServiceRequestService $requests): void
    {
        if (! $this->temporaryUploadIsUsable('attachment', $this->attachment)) {
            return;
        }

        $this->validate();

        if ($this->type === 'excuse' && ! $this->sessionId) {
            $this->addError('sessionId', __('اختر الحصة التي تريد الاعتذار عنها'));

            return;
        }

        $path = null;

        if ($this->attachment) {
            // Private disk. A medical note is the most sensitive thing this
            // product holds, and the form this replaces wrote them to the
            // PUBLIC disk — orphaned, unauthenticated, undeletable child
            // medical records served off the academy's own certificate.
            $path = $this->attachment->store(
                'requests/' . app('current_academy')->id,
                'local'
            );
        }

        try {
            $requests->open([
                'type' => $this->type,
                'participant_id' => $this->activeParticipantId(),
                'training_session_id' => $this->sessionId,
                'reason' => $this->reason,
                'attachment_path' => $path,
                'attachment_mime' => $this->attachment?->getMimeType(),
                'attachment_size' => $this->attachment?->getSize(),
                'metadata' => $this->branchId ? ['branch_id' => $this->branchId] : [],
            ], auth()->user());
        } catch (DomainException $e) {
            if ($path) {
                \Illuminate\Support\Facades\Storage::disk('local')->delete($path);
            }

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

            return;
        }

        session()->flash('success', __('تم إرسال الطلب — ستصلك النتيجة عند مراجعته'));

        $this->redirect(route('portal.account', ['tab' => 'requests']), navigate: true);
    }

    public function render()
    {
        $participantId = $this->activeParticipantId();
        $participant = $this->portal()->activeParticipant();

        $groupIds = $participant
            ? $participant->activeEnrollments()->pluck('training_group_id')->all()
            : [];

        // Only sessions an excuse can still be filed against, so the form
        // cannot offer something the service will refuse.
        $sessions = $groupIds === [] ? collect() : TrainingSession::whereIn('training_group_id', $groupIds)
            ->whereBetween('session_date', [now()->subDays(7)->toDateString(), now()->addDays(30)->toDateString()])
            ->with('group')
            ->orderBy('session_date')
            ->limit(40)
            ->get();

        return view('livewire.portal.portal-requests', [
            'sessions' => $sessions,
            // Every active branch, deliberately. Branch itself carries no
            // BranchScope — it is the thing being scoped, not a scoped record —
            // and a member choosing where to transfer to must see the branches
            // they are not at. The submitted value is checked in rules().
            'branches' => \App\Domain\Identity\Models\Branch::where('is_active', true)->get(),
            'open' => ServiceRequest::where('participant_id', $participantId)
                ->where('status', 'pending')
                ->count(),
        ]);
    }
}
