<?php

namespace App\Livewire\AssistanceApplications;

use App\Enums\FormType;
use App\Exceptions\TooManyActiveApplicationsException;
use App\Mail\AssistanceApplicationReceived;
use App\Mail\NewAssistanceApplication;
use App\Models\ApplicationAnswer;
use App\Models\AssistanceApplication;
use App\Models\FormQuestion;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Validation\Rule;
use Livewire\Component;
use Throwable;

/**
 * @property Collection<string, Collection<int, FormQuestion>> $groupedQuestions
 */
class Create extends Component
{
    /** @var array<int, string|bool|null> */
    public array $answers = [];

    /** @var Collection<int, FormQuestion> */
    public Collection $questions;

    // Profile fields for unauthenticated users (untyped so Livewire can handle edge cases from browser autofill)
    /** @var string|null */
    public $profile_first_name = '';

    /** @var string|null */
    public $profile_last_name = '';

    /** @var string|null */
    public $profile_email = '';

    /** @var string|null */
    public $profile_address_street = '';

    /** @var string|null */
    public $profile_address_city = '';

    /** @var string|null */
    public $profile_address_postal = '';

    /** @var string|null */
    public $profile_phone = '';

    public function mount(): void
    {
        // Check if assistance is enabled
        if (! Setting::get('enable_assistance_applications', true)) {
            session()->flash('error', 'Assistance applications are currently not being accepted.');

            $this->redirect(Auth::check() ? route('dashboard') : route('home'), navigate: true);

            return;
        }

        if (Auth::check()) {
            if (! Auth::user()->hasCompletedProfileForAdoption()) {
                session()->flash('message', 'Please complete your profile (address and phone number) before submitting an assistance application.');

                $this->redirect(route('profile.edit'), navigate: true);

                return;
            }

            // Check if user can submit
            if (! Auth::user()->canSubmitAssistanceApplication()) {
                $maxApplications = (int) Setting::get('max_active_assistance_applications', 2);
                session()->flash('error', "You have reached the maximum of {$maxApplications} active assistance applications. Please wait for one to be archived before submitting a new one.");

                $this->redirect(route('dashboard'), navigate: true);

                return;
            }
        }

        // Load active questions for assistance form with groups
        $this->questions = FormQuestion::query()
            ->with('group')
            ->forFormType(FormType::Assistance)
            ->active()
            ->sortedByGroupAndQuestion()
            ->get();

        // Initialize answers array with empty values
        foreach ($this->questions as $question) {
            $this->answers[$question->id] = $question->type->value === 'switch' ? null : '';
        }
    }

    public function submit(): void
    {
        if (Auth::check() && ! Auth::user()->hasCompletedProfileForAdoption()) {
            $this->redirect(route('profile.edit'), navigate: true);

            return;
        }

        $validated = $this->validate($this->rules());

        // Find or create user for unauthenticated submission (atomic via firstOrCreate to
        // avoid a duplicate-email race when the same guest double-submits).
        if (Auth::check()) {
            $user = Auth::user();

            // Re-check submission eligibility
            if (! $user->canSubmitAssistanceApplication()) {
                $maxApplications = (int) Setting::get('max_active_assistance_applications', 2);
                $this->addError('general', "You have reached the maximum of {$maxApplications} active assistance applications.");

                return;
            }
        } else {
            $user = User::firstOrCreate(
                ['email' => $validated['profile_email']],
                [
                    'first_name' => trim($validated['profile_first_name']),
                    'last_name' => trim($validated['profile_last_name']),
                    'address_street' => $validated['profile_address_street'],
                    'address_city' => $validated['profile_address_city'],
                    'address_postal' => $validated['profile_address_postal'],
                    'phone' => $validated['profile_phone'],
                    'password' => bcrypt(str()->random(32)),
                    'email_verified_at' => null,
                ]
            );
        }

        try {
            $application = DB::transaction(function () use ($validated, $user) {
                // Re-check under a row lock so two rapid submissions from the same authenticated
                // user can't both pass the earlier count check before either commits.
                if (Auth::check() && ! $user->canSubmitAssistanceApplication(lockForUpdate: true)) {
                    throw new TooManyActiveApplicationsException;
                }

                $application = AssistanceApplication::create([
                    'user_id' => $user->id,
                    'status' => 'submitted',
                ]);

                // Store answers with question snapshots
                foreach ($this->questions as $question) {
                    $answer = $validated['answers'][$question->id] ?? null;

                    // Convert boolean/checkbox values to string for storage
                    if ($question->type->value === 'switch') {
                        $answer = $answer ? '1' : '0';
                    }

                    ApplicationAnswer::create([
                        'answerable_type' => AssistanceApplication::class,
                        'answerable_id' => $application->id,
                        'question_snapshot' => $question->toSnapshot(),
                        'answer' => $answer,
                        'sort_order' => $question->sort_order,
                    ]);
                }

                return $application;
            });
        } catch (TooManyActiveApplicationsException) {
            $maxApplications = (int) Setting::get('max_active_assistance_applications', 2);
            $this->addError('general', "You have reached the maximum of {$maxApplications} active assistance applications.");

            return;
        } catch (Throwable $e) {
            Log::error('Assistance application submission failed.', [
                'exception' => $e,
                'user_id' => $user->id,
            ]);

            $this->addError('general', 'Something went wrong while submitting your application. Please try again, or contact us if the problem continues.');

            return;
        }

        // Load relationships for email
        $application->load('user');

        // Send confirmation email to the applicant
        if (Setting::get('email_assistance_application_received_enabled', true)) {
            Mail::to($application->user)->send(new AssistanceApplicationReceived($application));
        }

        // Notify admins who have opted in to receive new assistance alerts
        $adminsToNotify = User::where('is_admin', true)
            ->where('receive_new_assistance_alerts', true)
            ->get();

        foreach ($adminsToNotify as $admin) {
            Mail::to($admin)->send(new NewAssistanceApplication($application));
        }

        session()->flash('message', 'Your assistance application has been submitted successfully!');

        if (Auth::check()) {
            $this->redirect(route('dashboard'), navigate: true);
        } else {
            $this->redirect(route('home'), navigate: true);
        }
    }

    /**
     * Build validation rules dynamically based on questions.
     *
     * @return array<string, array<int, string>>
     */
    protected function rules(): array
    {
        $rules = [];

        // Add profile validation for unauthenticated users
        if (! Auth::check()) {
            $rules['profile_first_name'] = ['required', 'string', 'max:255', 'regex:/\S/'];
            $rules['profile_last_name'] = ['required', 'string', 'max:255', 'regex:/\S/'];
            $rules['profile_email'] = ['required', 'email', 'max:255'];
            $rules['profile_address_street'] = ['required', 'string', 'max:255'];
            $rules['profile_address_city'] = ['required', 'string', 'max:255'];
            $rules['profile_address_postal'] = ['required', 'string', 'max:20'];
            $rules['profile_phone'] = ['required', 'string', 'max:20'];
        }

        foreach ($this->questions as $question) {
            $questionRules = [];

            // Add type-specific validation
            switch ($question->type->value) {
                case 'string':
                    if ($question->is_required) {
                        $questionRules[] = 'required';
                    } else {
                        $questionRules[] = 'nullable';
                    }
                    $questionRules[] = 'string';
                    $questionRules[] = 'max:255';
                    break;
                case 'textarea':
                    if ($question->is_required) {
                        $questionRules[] = 'required';
                    } else {
                        $questionRules[] = 'nullable';
                    }
                    $questionRules[] = 'string';
                    $questionRules[] = 'max:2000';
                    break;
                case 'dropdown':
                    if ($question->is_required) {
                        $questionRules[] = 'required';
                    } else {
                        $questionRules[] = 'nullable';
                    }
                    $questionRules[] = 'string';
                    if ($question->options) {
                        $questionRules[] = Rule::in($question->options);
                    }
                    break;
                case 'switch':
                    if ($question->is_required) {
                        $questionRules[] = 'required';
                    } else {
                        $questionRules[] = 'nullable';
                    }
                    $questionRules[] = Rule::in([0, 1, '0', '1', true, false]);
                    break;
            }

            $rules["answers.{$question->id}"] = $questionRules;
        }

        return $rules;
    }

    /**
     * Get custom validation messages.
     *
     * @return array<string, string>
     */
    protected function messages(): array
    {
        $messages = [
            'profile_first_name.required' => 'Please enter your first name.',
            'profile_first_name.regex' => 'Please enter your first name.',
            'profile_last_name.required' => 'Please enter your last name.',
            'profile_last_name.regex' => 'Please enter your last name.',
            'profile_email.required' => 'Please enter your email address.',
            'profile_email.email' => 'Please enter a valid email address.',
            'profile_address_street.required' => 'Please enter your street address.',
            'profile_address_city.required' => 'Please enter your city.',
            'profile_address_postal.required' => 'Please enter your postal code.',
            'profile_phone.required' => 'Please enter your phone number.',
        ];

        foreach ($this->questions as $question) {
            $label = $question->label;
            $messages["answers.{$question->id}.required"] = "Please answer: {$label}";
            $messages["answers.{$question->id}.max"] = "{$label} is too long.";
            $messages["answers.{$question->id}.in"] = "Please select a valid option for: {$label}";
        }

        return $messages;
    }

    /**
     * Get questions grouped by their group.
     *
     * @return Collection<string, Collection<int, FormQuestion>>
     */
    public function getGroupedQuestionsProperty(): Collection
    {
        return $this->questions->groupBy(function ($question) {
            return $question->group?->name ?? 'General Questions'; // @phpstan-ignore nullsafe.neverNull (group FK nullable)
        });
    }

    public function render(): mixed
    {
        return view('livewire.assistance-applications.create', [
            'questions' => $this->questions,
            'groupedQuestions' => $this->groupedQuestions,
        ]);
    }
}
