<?php

declare(strict_types=1);

namespace Seka\Vzdelavanie\Http\Controllers\Frontend\Testovanie;

use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
use Seka\Vzdelavanie\Models\Testovanie\Test;
use Seka\Vzdelavanie\Http\Controllers\Controller;
use Seka\Vzdelavanie\Events\Testovanie\Frontend\TestSpustenie;
use Seka\Vzdelavanie\Services\Pouzivatel\Testovanie\VyhodnotenieTestu;
use Seka\Vzdelavanie\Services\Pouzivatel\Testovanie\VygenerovanieTestu;
use Seka\Vzdelavanie\Services\Pouzivatel\Testovanie\VyhodnotenieOtazky;
use Seka\Vzdelavanie\Models\Pouzivatel\Testovanie\Test as TestPouzivatela;
use Seka\Vzdelavanie\Services\Pouzivatel\Testovanie\UlozenieOdpovediPouzivatela;
use Seka\Vzdelavanie\Models\Pouzivatel\Testovanie\Otazka as OtazkaTestuPouzivatela;
use Seka\Vzdelavanie\Http\Requests\Frontend\Testovanie\VyhodnotenieTestuPutRequest;
use Seka\Vzdelavanie\Models\Pouzivatel\Testovanie\Odpoved as OdpovedOtazkaTestuPouzivatela;
use Seka\Vzdelavanie\Services\Pouzivatel\Testovanie\Exceptions\TestUzBolVyhodnotenyException;
use Seka\Vzdelavanie\Services\Pouzivatel\Testovanie\Exceptions\TestEsteNebolVyhodnotenyException;

/**
 * Class PouzivatelTestController
 *
 * @package Seka\Vzdelavanie\Http\Controllers\Frontend\Testovanie
 */
final class PouzivatelTestController extends Controller
{
    /**
     *
     */
    private const POCET_ZAZNAMOV_NA_STRANU = 10;

    /**
     * @param int $vzdTstTstId
     *
     * @return \Illuminate\Http\RedirectResponse
     */
    public function generovanie(int $vzdTstTstId)
    {
        try {
            $test = Test::query()->publikovany()->findOrFail($vzdTstTstId);

            $vygenerovanyTest = VygenerovanieTestu::generovat($test);

            // presmerovat pouzivatela na vygenerovany test
            return response()->redirectToRoute('vzdelavanie.testy.pouzivatela.test.spustenie', $vygenerovanyTest);
        } catch (Exception $exc) {
            flash()->message($exc->getMessage());
            Log::error($exc->getMessage(), $exc->getTrace());
        }

        return response()->redirectToRoute('vzdelavanie.testy.zoznam');
    }

    /**
     * @param int $vzdPzvtlTstTstId
     *
     * @return mixed
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     * @throws \Throwable
     */
    public function spustenie(int $vzdPzvtlTstTstId)
    {
        $test = null;

        try {
            /** @var TestPouzivatela $test */
            $test = TestPouzivatela::query()->with('otazky.odpovede')->findOrFail($vzdPzvtlTstTstId);

            $this->authorize('spustenie', $test);

            if (true === $test->jeVyhodnoteny()) {
                throw new TestUzBolVyhodnotenyException();
            }

            if (true === $test->nebolSpusteny()) {
                $test->fill([
                    'casova_znacka_spustenia' => now(),
                ])->saveOrFail();
            }

            $test->otazky->makeHidden([
                'druh_spravnej_odpovede',
                'pouzivatel_testovanie_test_id',
                'created_at',
                'updated_at',
            ]);

            $test->otazky->each(function (OtazkaTestuPouzivatela $otazka) {
                $otazka->odpovede->transform(function ($odpoved, $key) {
                    // premenovanie, neskor skrytie povodneho nazvu
                    $odpoved->otazka_id = $odpoved->pouzivatel_testovanie_otazka_id;

                    return $odpoved;
                });

                $otazka->odpovede->makeHidden([
                    'hodnotenie',
                    'created_at',
                    'updated_at',
                    'pouzivatel_testovanie_otazka_id',
                ]);
            });

            // zaznamenat udalost spustenia testu
            event(new TestSpustenie($test, auth()->user()));
        } catch (TestUzBolVyhodnotenyException $exc) {
            flash()->warning($exc->getMessage());
            Log::warning($exc->getMessage(), $exc->getTrace());

            return response()->redirectToRoute('vzdelavanie.testy.pouzivatela.test.vyhodnotenie', $test);
        } catch (Exception $exc) {
            flash()->error($exc->getMessage());
            Log::error($exc->getMessage(), $exc->getTrace());

            return response()->redirectToRoute('vzdelavanie.testy.pouzivatela.historia');
        }

        return view('Vzdelavanie::pages.testovanie.test.nahlad')
            ->withTest($test);
    }

    /**
     * @param \Seka\Vzdelavanie\Http\Requests\Frontend\Testovanie\VyhodnotenieTestuPutRequest $request
     * @param int                                                                             $vzdPzvtlTstTstId
     *
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View|mixed
     * @throws \Illuminate\Contracts\Container\BindingResolutionException|\Throwable
     */
    public function vyhodnotit(VyhodnotenieTestuPutRequest $request, int $vzdPzvtlTstTstId)
    {
        try {
            /** @var TestPouzivatela $test */
            $test = TestPouzivatela::query()->with('otazky.odpovede')->findOrFail($vzdPzvtlTstTstId);

            $this->authorize('vyhodnotit', $test);

            if (true === $test->jeVyhodnoteny()) {
                throw new TestUzBolVyhodnotenyException();
            }

            // ulozenie testu
            UlozenieOdpovediPouzivatela::ulozit($test, $request->validated());

            // vyhodnotenie testu
            VyhodnotenieTestu::vyhodnotenie($test);
        } catch (TestUzBolVyhodnotenyException $exc) {
            flash()->warning($exc->getMessage());
            Log::warning($exc->getMessage(), $exc->getTrace());

            return response()->json([
                'message' => $exc->getMessage(),
            ], 200);
        } catch (Exception $exc) {
            flash()->error($exc->getMessage());
            Log::error($exc->getMessage(), $exc->getTrace());

            return response()->json([
                'message' => $exc->getMessage(),
            ], 500);
        }

        return response()->json();
    }

    /**
     * @param int $vzdPzvtlTstTstId
     *
     * @return \Illuminate\Http\RedirectResponse
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function vyhodnotenie(int $vzdPzvtlTstTstId)
    {
        try {
            $test = TestPouzivatela::query()->with('otazky.odpovede')->findOrFail($vzdPzvtlTstTstId);

            $this->authorize('vyhodnotenie', $test);

            if (! $test->jeVyhodnoteny()) {
                throw new TestEsteNebolVyhodnotenyException();
            }

            $test->otazky = $test->otazky->filter(function (OtazkaTestuPouzivatela $otazka) {
                $zodpovedanaOtazka = false;
                $spravneZodpovedanaOtazka = false;
                $pocetSpravnychOdpovedi = 0;
                $pocetNespravnychOdpovedi = 0;

                $otazka->odpovede = $otazka->odpovede->filter(function (OdpovedOtazkaTestuPouzivatela $odpoved) use (&$pocetSpravnychOdpovedi, &$pocetNespravnychOdpovedi, &$spravneZodpovedanaOtazka, &$zodpovedanaOtazka) {
                    if ($odpoved->jeZvolena()) {
                        if ($odpoved->jeSpravna()) {
                            $pocetSpravnychOdpovedi = ++$pocetSpravnychOdpovedi;
                        } else {
                            $pocetNespravnychOdpovedi = ++$pocetNespravnychOdpovedi;
                        }

                        $zodpovedanaOtazka = true;
                    } else {
                        if ($odpoved->jeSpravna()) {
                            $pocetNespravnychOdpovedi = ++$pocetNespravnychOdpovedi;
                        }
                    }

                    return $odpoved->jeSpravna() || ($odpoved->jeNespravna() && $odpoved->jeZvolena());
                })->sort(function (OdpovedOtazkaTestuPouzivatela $odpoved) {
                    return $odpoved->jeSpravna() ? -1 : 1;
                });

                if (false === $zodpovedanaOtazka) {
                    return false;
                }

                if ($pocetNespravnychOdpovedi === 0) {
                    $spravneZodpovedanaOtazka = VyhodnotenieOtazky::vyhodnot($otazka, $pocetSpravnychOdpovedi);
                }

                return !$spravneZodpovedanaOtazka;
            });
        } catch (TestEsteNebolVyhodnotenyException $exc) {
            flash()->warning($exc->getMessage());
            Log::warning($exc->getMessage(), $exc->getTrace());

            return response()->redirectToRoute('vzdelavanie.testy.pouzivatela.historia');
        } catch (Exception $exc) {
            Log::error($exc->getMessage(), $exc->getTrace());

            return response()->redirectToRoute('vzdelavanie.testy.pouzivatela.historia');
        }

        return view('Vzdelavanie::pages.testovanie.test.vyhodnotenie')
            ->withTest($test);
    }

    /**
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View|mixed
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function historia()
    {
        $user = auth()->user();
        $query = TestPouzivatela::query();

        // ak ma pouzivatel rolu spravcu PEK, tak smie vydiet testy technikov PEK
        if ($user->hasRole('spravca_pek')) {
            /** @var \Illuminate\Database\Eloquent\Collection $pouzivateliaPek */
            $pouzivateliaTableName = app('\App\Models\Pouzivatel\Pouzivatel')->getTable();

            $zoznamIdPouzivatelovPek = DB::table($pouzivateliaTableName)
                ->select('id')
                ->where('pracovisko_id', $user->pracovisko_id)
                ->pluck('id')->all();

            $query = $query->whereIn('pouzivatel_id', $zoznamIdPouzivatelovPek);
        } else {
            $query = $query->wherePouzivatelId(auth()->id());
        }

        $testy = $query->with('pouzivatel')
            ->orderByDesc('casova_znacka_vyhodnotenia')
            ->paginate(self::POCET_ZAZNAMOV_NA_STRANU);

        return view('Vzdelavanie::pages.testovanie.historia.index')
            ->withTesty($testy);
    }
}
