<?php

declare(strict_types=1);

namespace Seka\Portal\Modul\Vzdelavanie\Services\Pouzivatel\Testovanie;

use Exception;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Seka\Portal\Modul\Vzdelavanie\Events\Testovanie\Frontend\TestGenerated;
use Seka\Portal\Modul\Vzdelavanie\Models\Pouzivatel\Testovanie\Odpoved as OdpovedOtazkaTestuPouzivatela;
use Seka\Portal\Modul\Vzdelavanie\Models\Pouzivatel\Testovanie\Otazka as OtazkaTestuPouzivatela;
use Seka\Portal\Modul\Vzdelavanie\Models\Pouzivatel\Testovanie\Test as TestPouzivatela;
use Seka\Portal\Modul\Vzdelavanie\Models\Testovanie\DruhSpravnejOdpovedeEnum;
use Seka\Portal\Modul\Vzdelavanie\Models\Testovanie\Odpoved;
use Seka\Portal\Modul\Vzdelavanie\Models\Testovanie\Otazka;
use Seka\Portal\Modul\Vzdelavanie\Models\Testovanie\Test;

/**
 * Class VygenerovanieTestu
 *
 * @package Seka\Portal\Modul\Vzdelavanie\Services\Pouzivatel\Testovanie
 */
final class VygenerovanieTestu
{
    /**
     * @param \Seka\Portal\Modul\Vzdelavanie\Models\Testovanie\Test $test
     *
     * @return \Seka\Portal\Modul\Vzdelavanie\Models\Pouzivatel\Testovanie\Test
     * @throws \Exception|\Throwable
     */
    public static function generovat(Test $test): TestPouzivatela
    {
        DB::beginTransaction();

        try {
            // vygenerovat test
            $vygenerovanyTest = (new TestPouzivatela())->fill($test->only([
                'nazov',
                'popis',
            ]));
            $vygenerovanyTest->pouzivatel()->associate(Auth::user());
            $vygenerovaneOtazky = collect();
            $maxPocetSpravnychOdpovedi = 0;

            $test
                ->otazky()
                ->with('odpovede')
                ->publikovana()
                ->inRandomOrder()
                ->take($test->pocet_otazok)
                ->get()
                ->each(
                    function (Otazka $otazka) use ($vygenerovaneOtazky, &$maxPocetSpravnychOdpovedi) {
                        $pocetOdpovedi = 3;

                        switch ($otazka->druh_spravnej_odpovede) {
                            case DruhSpravnejOdpovedeEnum::JEDNA:
                            default:
                                $pocetSpravnychOdpovedi = 1;
                                break;
                            case DruhSpravnejOdpovedeEnum::MINIMALNE_JEDNA:
                                $pocetSpravnychOdpovedi = rand(1, 2);
                                break;
                            case DruhSpravnejOdpovedeEnum::DVE:
                                $pocetSpravnychOdpovedi = 2;
                                break;
                            case DruhSpravnejOdpovedeEnum::TRI:
                                $pocetSpravnychOdpovedi = 3;
                                break;
                        }

                        $otazka->odpovede = self::ziskajOdpovede($otazka, $pocetOdpovedi, $pocetSpravnychOdpovedi);
                        $vygenerovaneOtazky->add($otazka);

                        $maxPocetSpravnychOdpovedi += $pocetSpravnychOdpovedi;
                    },
                );

            $vygenerovanyTest->setAttribute('max_pocet_spravnych_odpovedi', $maxPocetSpravnychOdpovedi);
            $vygenerovanyTest->save();

            $vygenerovaneOtazky->each(function (Otazka $otazka) use ($vygenerovanyTest) {
                $genOtazka = new OtazkaTestuPouzivatela();
                $genOtazka->fill($otazka->only([
                    'znenie',
                    'druh_spravnej_odpovede',
                ]));
                $genOtazka->test()->associate($vygenerovanyTest)->save();

                $otazka->odpovede->each(function ($odpoved) use ($genOtazka) {
                    $genOdpoved = new OdpovedOtazkaTestuPouzivatela();
                    $genOdpoved->fill($odpoved->only([
                        'znenie',
                        'hodnotenie',
                    ]));
                    $genOdpoved->zvolena = false;
                    $genOdpoved->otazka()->associate($genOtazka)->save();
                });

                unset($otazka);
            });

            unset($vygenerovaneOtazky);

            // zaznamenat udalost noveho vygenerovaneho testu
            event(new TestGenerated($test));

            DB::commit();

            return $vygenerovanyTest;
        } catch (Exception $exc) {
            DB::rollBack();

            throw $exc;
        }
    }

    /**
     * @param \Seka\Portal\Modul\Vzdelavanie\Models\Testovanie\Otazka $otazka
     * @param int                                                     $pocetOdpovedi
     * @param int                                                     $pocetSpravnychOdpovedi
     *
     * @return \Illuminate\Support\Collection
     */
    private static function ziskajOdpovede(Otazka $otazka, int $pocetOdpovedi, int $pocetSpravnychOdpovedi): Collection
    {
        /** @var \Illuminate\Database\Eloquent\Collection $publikovaneOdpovede */
        $publikovaneOdpovede = $otazka->odpovede->filter(function (Odpoved $odp) {
            return $odp->jePublikovana();
        })->shuffle();
        $odpovede = collect();
        $pocetNespravnychOdpovedi = $pocetOdpovedi - $pocetSpravnychOdpovedi;

        return $odpovede
            ->concat(
                $publikovaneOdpovede->filter(function (Odpoved $odp) {
                    return $odp->jeSpravna();
                })->take($pocetSpravnychOdpovedi),
            )
            ->concat(
                $publikovaneOdpovede->filter(function (Odpoved $odp) {
                    return $odp->jeNespravna();
                })->take($pocetNespravnychOdpovedi),
            )
            ->shuffle();
    }
}
