<?php

declare(strict_types=1);

namespace Seka\Portal\Modul\Terminy\Livewire\Backend\Adresar\Components;

use Exception;
use Geocoder\Laravel\Facades\Geocoder;
use Geocoder\Model\Address;
use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Livewire\Attributes\Locked;
use Livewire\Attributes\On;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Seka\Portal\Modul\Terminy\Entities\MiestoKonania\MiestoKonania;

/**
 * Class Form
 *
 * @package Seka\Portal\Modul\Terminy\Livewire\Backend\Adresar\Components
 */
final class Form extends Component
{
    use AuthorizesRequests;

    #[Locked]
    public ?int $id = null;
    #[Validate]
    public ?string $nazov = null;
    #[Validate]
    public ?string $ulica = null;
    #[Validate]
    public ?string $obec = null;
    #[Validate]
    public ?string $psc = null;
    #[Validate]
    public ?string $krajina = null;
    #[Validate]
    public ?float $latitude = null;
    #[Validate]
    public ?float $longitude = null;
    public bool $trashed = false;
    public bool $exists = false;

    public function mount(?MiestoKonania $miesto = null): void
    {
        $this->fillForm($miesto);
    }

    /**
     * @param \Seka\Portal\Modul\Terminy\Entities\MiestoKonania\MiestoKonania|null $miesto
     *
     * @return void
     */
    public function fillForm(?MiestoKonania $miesto): void
    {
        $this->id = $miesto?->id ?? null;
        $this->fill([
            'nazov' => $miesto?->nazov,
            'ulica' => $miesto?->ulica,
            'obec' => $miesto?->obec,
            'psc' => $miesto?->psc,
            'krajina' => $miesto?->krajina,
            'latitude' => $miesto?->latitude,
            'longitude' => $miesto?->longitude,
            'trashed' => $miesto?->trashed(),
            'exists' => $miesto?->exists,
        ]);
    }

    /**
     * @return \Illuminate\Contracts\View\View
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function render(): View
    {
        return view('Terminy::livewire.backend.adresar.components.form');
    }

    /**
     * @throws \Illuminate\Validation\ValidationException
     * @throws \Throwable
     */
    public function save()
    {
        $this->validate();

        try {
            $this->authorize('terminy');

            $existBeforeSave = $this->exists ?? false;

            DB::beginTransaction();

            if ($existBeforeSave) {
                $miestoKonania = MiestoKonania::query()->findOrFail($this->id);
            } else {
                $miestoKonania = new MiestoKonania();
            }

            $miestoKonania->fill(
                $this->only('nazov', 'ulica', 'obec', 'psc', 'krajina', 'latitude', 'longitude'),
            );
            $miestoKonania->vytvoril()->associate(auth()->user());
            $miestoKonania->save();
            $miestoKonania->refresh();

            DB::commit();

            if ($existBeforeSave) {
                $message = 'Miesto bolo aktualizované';
            } else {
                $message = 'Nové miesto bolo vytvorené';
            }

            flash()->overlay($message, 'Hlásenie')->success();
        } catch (Exception $e) {
            DB::rollBack();

            Log::error($e->getMessage(), $e->getTrace());

            flash()->overlay('Miesto nebolo úspešne uložené.', 'Hlásenie')->error();
        } finally {
            return redirect()->route('modul.terminy.backend.adresar.index');
        }
    }

    /**
     * Obnovit zneplatnene miesto
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     * @throws \Throwable
     */
    public function restore(): void
    {
        try {
            $this->authorize('terminy');

            if (!$this->trashed || !$this->exists) {
                return;
            }

            DB::beginTransaction();

            MiestoKonania::onlyTrashed()->findOrFail($this->id)->restore();

            DB::commit();

            flash()->success('Miesto bolo úspešne obnovené');
        } catch (Exception $e) {
            DB::rollBack();

            Log::error($e->getMessage(), $e->getTrace());

            flash()->error('Miesto sa nepodarilo obnoviť.');
        } finally {
            $this->fillForm(MiestoKonania::findOrFail($this->id));
        }
    }

    /**
     * Zneplatnit miesto
     *
     * @throws \Throwable
     */
    public function trash(): void
    {
        try {
            $this->authorize('terminy');

            DB::beginTransaction();

            if ($this->exists) {
                // soft delete
                MiestoKonania::query()->findOrFail($this->id)->delete();
            }

            DB::commit();

            flash()->success('Miesto bolo úspešne zneplatnené');
        } catch (Exception $e) {
            DB::rollBack();

            Log::error($e->getMessage(), $e->getTrace());

            flash()->error('Miesto nebolo zneplatnené.');
        } finally {
            $this->fillForm(MiestoKonania::onlyTrashed()->findOrFail($this->id));
        }
    }

    /**
     * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function cancel()
    {
        return redirect()->route('modul.terminy.backend.adresar.index');
    }

    /**
     * @param float $lat
     * @param float $long
     *
     * @throws \ReflectionException
     */
    #[On('geocodingReceived')]
    public function geocoding(float $lat, float $long): void
    {
        $result = Geocoder::reverse($lat, $long)->get()->first();

        if ($result instanceof Address) {
            /** @var $result Address */
            $this->ulica = sprintf('%s %s', $result->getStreetName(), $result->getStreetNumber());
            $this->obec = $result->getLocality();
            $this->psc = $result->getPostalCode() ? preg_replace('/\s+/', '', trim($result->getPostalCode())) : '';
            $this->krajina = $result->getCountry()->getName() ? trim($result->getCountry()->getName()) : '';
            $this->latitude = $result->getCoordinates()->getLatitude();
            $this->longitude = $result->getCoordinates()->getLongitude();
        }
    }

    /**
     * @return string[]
     */
    public function rules(): array
    {
        return [
            'nazov' => 'required|min:5|max:128',
            'ulica' => 'required',
            'obec' => 'required',
            'psc' => 'required|numeric',
            'krajina' => 'required',
            'latitude' => 'required|numeric|regex:/^-?\d{1,2}.\d{0,16}$/',
            'longitude' => 'required|numeric|regex:/^-?\d{1,3}.\d{0,16}$/',
        ];
    }

    /**
     * @return string[]
     */
    public function messages(): array
    {
        return [
            'nazov.required' => 'Názov miesta je vyžadovaný',
            'nazov.min' => 'Názov miesta by mal obsahovať aspoň :min znakov',
            'nazov.max' => 'Názov miesta smie obsahovať najviac :max znakov',
            'ulica.required' => 'Ulica je vyžadovaná',
            'obec.required' => 'Obec je vyžadovaná',
            'psc.required' => 'PSČ je vyžadované',
            'psc.numeric' => 'PSČ je vyžadované vo formáte čísla',
            'krajina.required' => 'Krajina je vyžadovaná',
            'latitude.required' => 'Zemepisná šírka je vyžadovaná',
            'latitude.numeric' => 'Zemepisná šírka je vyžadovaná vo formáte čísla',
            'latitude.regex' => 'Zemepisná šírka je vyžadovaná vo formáte čísla s desatinnou bodkou',
            'longitude.required' => 'Zemepisná dĺžka je vyžadovaná',
            'longitude.numeric' => 'Zemepisná dĺžka je vyžadovaná vo formáte čísla',
            'longitude.regex' => 'Zemepisná dĺžka je vyžadovaná vo formáte čísla s desatinnou bodkou',
        ];
    }
}
