<?php

declare(strict_types=1);

namespace Seka\Vzdelavanie\Commands\Console;

use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use PhpOffice\PhpSpreadsheet\IOFactory;
use Illuminate\Database\Eloquent\Builder;
use PhpOffice\PhpSpreadsheet\Worksheet\Row;
use Seka\Vzdelavanie\Enums\ModelStatusEnum;
use Seka\Vzdelavanie\Models\Testovanie\Test;
use Seka\Vzdelavanie\Models\Testovanie\Otazka;
use Seka\Vzdelavanie\Models\Testovanie\Odpoved;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Seka\Vzdelavanie\Models\Testovanie\HodnotenieOdpovedeEnum;
use Seka\Vzdelavanie\Models\Testovanie\DruhSpravnejOdpovedeEnum;

/**
 * Class ImportTestov
 *
 * @package Seka\Vzdelavanie\Commands\Console
 */
final class ImportTestov extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'import:testy {subor}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Import otazok a odpovedi testov';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * @return void
     */
    public function handle(): void
    {
        try {
            $nazvyZositov = [];
            $subor = sprintf('%s%s%s', base_path(), DIRECTORY_SEPARATOR, $this->argument('subor'));
            $reader = IOFactory::load($subor);

            // odfiltruje len viditelne zosity
            foreach ($reader->getWorksheetIterator() as $worksheet) {
                if ($worksheet->getSheetState() === Worksheet::SHEETSTATE_VISIBLE) {
                    $nazvyZositov[] = $worksheet->getTitle();
                }

                continue;
            }

            $nazvyZositov = $this->vratMultidimenzionalnePole($nazvyZositov);

            $this->output->title('Zoznam zositov (druhov testov):');
            $this->output->table(['Nazov'], $nazvyZositov);

            DB::beginTransaction();

            // zneplatnenie existujucich testov
            Test::query()->where('stav', ModelStatusEnum::PUBLIKOVANE())->take(5)->each(function (Test $test) {
                $test
                    ->setAttribute('stav', ModelStatusEnum::NEPUBLIKOVANE())
                    ->setAttribute('popis', $test->popis . PHP_EOL . PHP_EOL . 'Zneplatnený pri importe nových testov ' . date('d.m.Y H:i:s'))
                    ->save();
            });

            // iteracia zosita za zositom
            foreach ($reader->getWorksheetIterator() as $sheet) {
                /**
                 * Ak nie je zosit zobrazeny, tak sa nespracuje
                 * @var \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $sheet
                 */
                if ($sheet->getSheetState() !== $sheet::SHEETSTATE_VISIBLE) continue;

                $test = new Test();
                $test->setAttribute('nazov', ucfirst($sheet->getTitle()));
                $test->setAttribute('popis', 'Test obsahuje otázky a odpovede kategórie ' . $sheet->getTitle());
                $test->setAttribute('stav', ModelStatusEnum::PUBLIKOVANE());
                $test->save();
                $test = $test->fresh();

                $this->line('Import testu "'. $sheet->getTitle() .'"...');
                // vytvor objekt grafickeho priebehu procesu importu, max hodnota je pocet riadkov zositu bez prveho riadku tj. hlavicky
                $pb = $this->output->createProgressBar($sheet->getHighestDataRow() - 1);

                // iteracia po riadkoch zosita
                foreach ($sheet->getRowIterator() as $row) {
                    // ak sa jedna o prvy riadok, je to hlavicka, neimportuje sa
                    if ($row->getRowIndex() == 1) {
                        $this->overHlavickuZosita($row);

                        continue;
                    }

                    $this->importOtazokAOdpovedi($test, $row);

                    $pb->advance();
                }

                $pb->finish();
                $this->output->newLine(2);

                $pocetZverejnenychOtazokTestu = $test->otazky()->where(function (Builder $query) {
                    $query->where('stav', ModelStatusEnum::PUBLIKOVANE());
                })->count();

                $test->setAttribute('pocet_otazok', (int) ($pocetZverejnenychOtazokTestu / 2));
                $test->save();

                $test->refresh();

                // ak test nema publikovane ziadne otazky, tak on sam nebude publikovany
                if ($test->otazky()->publikovana()->count() <= 0) {
                    $test->setAttribute('stav', ModelStatusEnum::NEPUBLIKOVANE());
                    $test->save();
                }
            }

            $this->output->success('Import prebehol uspesne!');

            DB::commit();
        } catch (Exception $e) {
            DB::rollBack();

            $this->output->error($e->getMessage());
        }
    }

    /**
     * @param \Seka\Vzdelavanie\Models\Testovanie\Test $test
     * @param \PhpOffice\PhpSpreadsheet\Worksheet\Row  $row
     * @return void
     */
    private function importOtazokAOdpovedi(Test $test, Row $row): void
    {
        /** @var Otazka $otazka */
        $otazka = null;
        /** @var Odpoved $odpoved */
        $odpoved = null;

        // iteracia stlpcov v riadku
        foreach ($row->getCellIterator() as $cell) {
            // stlpec A obsahuje identifikator edubase_id
            // stlpec B obsahuje info o aktivnej otazke v edubase aplikacii
            if (in_array(mb_strtoupper($cell->getColumn()), ['A', 'B'], true)) {
                continue;
            }

            // ak sa jedna o stlpec s informaciou a stave otazky (TRUE == aktivna; FALSE == neaktivna)
            if ($this->jeStavOtazky($cell->getColumn())) {
                $otazka = new Otazka();
                $otazka->setAttribute(
                    'stav',
                    true === $this->vratBoolean($cell->getValue()) ? ModelStatusEnum::PUBLIKOVANE() : ModelStatusEnum::NEPUBLIKOVANE()
                );
            }

            // ak je stlpec prazdny, tak chod na dalsi stlpec
            if (is_null($cell->getValue())) {
                continue;
            }

            // ak sa jedna o stlpec obsahujuci otazku
            if ($this->jeOtazka($cell->getColumn())) {
                $otazka->setAttribute('znenie', $cell->getValue());
                $otazka->setAttribute('druh_spravnej_odpovede', DruhSpravnejOdpovedeEnum::JEDNA());
                $test->otazky()->save($otazka);

                continue;
            }

            // ak sa jedna o stlpec obsahujuci odpoved
            if ($this->jeOdpoved($cell->getColumn())) {
                $odpoved = (new Odpoved())->fill([
                    'znenie' => $cell->getValue(),
                    'stav' => ModelStatusEnum::PUBLIKOVANE(),
                ]);

                continue;
            }

            // ak sa jedna o stlpec obsahujuci hodnotenie odpovede
            if ($this->jeHodnotenie($cell->getColumn())) {
                $odpoved->setAttribute(
                    'hodnotenie',
                    ((bool) $cell->getValue()) ? HodnotenieOdpovedeEnum::SPRAVNA() : HodnotenieOdpovedeEnum::NESPRAVNA()
                );
                $otazka->odpovede()->save($odpoved);

                continue;
            }
        }
    }

    /**
     * @param string $stlpec
     * @return bool
     */
    private function jeStavOtazky(string $stlpec): bool
    {
        // stlpec obsahujuci stav otazky (ci je aktivna alebo neaktivna)
        $stavOtazky = ['C'];

        return in_array($stlpec, $stavOtazky);
    }

    /**
     * @param string $stlpec
     * @return bool
     */
    private function jeOtazka(string $stlpec): bool
    {
        // stlpec obsahujuci otazku
        $otazka = ['D'];

        return in_array($stlpec, $otazka);
    }

    /**
     * Podla zoznamu stlpcov, ktore maju obsahovat odpovede zisti, ci sa jedna o stlpec odpovede
     *
     * @param string $stlpec
     * @return bool
     */
    private function jeOdpoved(string $stlpec): bool
    {
        // zoznam stlpcov obsahujucich znenie odpovede
        $znenieOdpovede = ['E', 'G', 'I', 'K', 'M', 'O', 'Q', 'S', 'U'];

        return in_array($stlpec, $znenieOdpovede);
    }

    /**
     * Podla zoznamu stlpcov, ktore maju obsahovat hodnotenie odpovede zisti, ci sa jedna o stlpec hodnotenia odpovede
     *
     * @param string $stlpec
     * @return bool
     */
    private function jeHodnotenie(string $stlpec): bool
    {
        // zoznam stlpcov obsahujucich hodnotenie odpovede
        $hodnotenieOdpovede = ['F', 'H', 'J', 'L', 'N', 'P', 'R', 'T', 'V'];

        return in_array($stlpec, $hodnotenieOdpovede);
    }

    /**
     * @param array $pole
     * @return array
     */
    private function vratMultidimenzionalnePole(array $pole)
    {
        $polozky = [];

        foreach ($pole as $polozka) {
            $polozky[] = [$polozka];
        }

        return $polozky;
    }

    /**
     * @param string|bool $hodnota
     * @return bool
     */
    private function vratBoolean($hodnota): bool
    {
        if (is_bool($hodnota)) {
            return (bool) $hodnota;
        }

        if (is_string($hodnota)) {
            return (bool) strcasecmp($hodnota, 'TRUE') === 0;
        }

        return false;
    }

    private function overHlavickuZosita(Row $row)
    {
        // iteracia stlpcov v riadku
        foreach ($row->getCellIterator() as $cell) {
            switch (mb_strtoupper($cell->getColumn())) {
                case 'A':
                    if (mb_strtoupper($cell->getValue()) !== 'EDUBASE_ID') {
                        throw new Exception('Nepodporovany format suboru.');
                    }
                    break;
                case 'B':
                    if (mb_strtoupper($cell->getValue()) !== 'AKTIVNA_EDUBASE') {
                        throw new Exception('Nepodporovany format suboru.');
                    }
                    break;
                case 'C':
                    if (mb_strtoupper($cell->getValue()) !== 'OTAZKA_AKTIVNA') {
                        throw new Exception('Nepodporovany format suboru.');
                    }
                    break;
                case 'D':
                    if (mb_strtoupper($cell->getValue()) !== 'ZNENIE_OTAZKY') {
                        throw new Exception('Nepodporovany format suboru.');
                    }
                    break;
                case 'E':
                    if (mb_strtoupper($cell->getValue()) !== 'ZNENIE_ODPOVEDE_1') {
                        throw new Exception('Nepodporovany format suboru.');
                    }
                    break;
                case 'F':
                    if (mb_strtoupper($cell->getValue()) !== 'HODNOTENIE_ODPOVEDE_1') {
                        throw new Exception('Nepodporovany format suboru.');
                    }
                    break;
                default:
                    break;
            }
        }
    }
}
