<?php

namespace idoit\Module\UserSettings;

use Exception;
use isys_cmdb_dao_category_s_person_master;
use isys_component_database;
use isys_component_session;
use isys_component_template_language_manager;
use isys_helper_crypt;
use RobThree\Auth\Providers\Qr\BaconQrCodeProvider;
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\TwoFactorAuthException;

/**
 * TFA Service to collect all TFA related actions and data accesses together
 */
class TfaService
{
    private isys_component_template_language_manager $language;
    private isys_cmdb_dao_category_s_person_master $personDao;
    private isys_component_session $session;
    private int $userObjectId;

    /**
     * @param isys_component_template_language_manager $language
     * @param isys_component_database                  $database
     * @param isys_component_session                   $session
     */
    public function __construct(isys_component_template_language_manager $language, isys_component_database $database, isys_component_session $session)
    {
        $this->language = $language;
        $this->personDao = isys_cmdb_dao_category_s_person_master::instance($database);
        $this->session = $session;
        $this->userObjectId = (int)$this->session->get_user_id();
    }

    /**
     * @return string|null
     * @throws \Exception
     */
    public function getTfaSecretForUser(): ?string
    {
        $tfaSecret = $this->personDao->get_data(null, $this->userObjectId)
            ->get_row_value('isys_cats_person_list__tfa_secret');

        return $tfaSecret !== null && strlen($tfaSecret) > 0 ? isys_helper_crypt::decrypt($tfaSecret) : null;
    }

    /**
     * @return bool
     * @throws \Exception
     */
    public function isActive(): bool
    {
        return $this->getTfaSecretForUser() !== null;
    }

    /**
     * @param string $secretKey
     *
     * @return void
     * @throws \isys_exception_dao
     */
    public function activateTfa(string $secretKey): void
    {
        $tfaSecret = $this->personDao->convert_sql_text(isys_helper_crypt::encrypt($secretKey));

        if (!$this->personDao->fieldsExistsInTable('isys_cats_person_list', ['isys_cats_person_list__tfa_secret'])) {
            throw new Exception($this->language->get('LC__USER_SETTINGS__TFA__TABLE_FIELD_ERROR'));
        }

        $query = "UPDATE isys_cats_person_list
            SET isys_cats_person_list__tfa_secret = {$tfaSecret}
            WHERE isys_cats_person_list__isys_obj__id = {$this->userObjectId}
            LIMIT 1";

        $this->personDao->update($query);
        $this->personDao->apply_update();
    }

    /**
     * @return void
     * @throws \Exception
     */
    public function deactivateTfa(): void
    {
        if (!$this->personDao->fieldsExistsInTable('isys_cats_person_list', ['isys_cats_person_list__tfa_secret'])) {
            throw new Exception($this->language->get('LC__USER_SETTINGS__TFA__TABLE_FIELD_ERROR'));
        }

        $query = "UPDATE isys_cats_person_list
            SET isys_cats_person_list__tfa_secret = NULL
            WHERE isys_cats_person_list__isys_obj__id = {$this->userObjectId}
            LIMIT 1;";

        $this->personDao->update($query);
        $this->personDao->apply_update();
    }

    /**
     * @param int $userId
     *
     * @return void
     * @throws \Exception
     */
    public function deactivateTfaForUser(int $userId): void
    {
        if (!$this->personDao->fieldsExistsInTable('isys_cats_person_list', ['isys_cats_person_list__tfa_secret'])) {
            throw new Exception($this->language->get('LC__USER_SETTINGS__TFA__TABLE_FIELD_ERROR'));
        }

        $query = "UPDATE isys_cats_person_list
            SET isys_cats_person_list__tfa_secret = NULL
            WHERE isys_cats_person_list__isys_obj__id = {$userId}
            LIMIT 1;";

        $this->personDao->update($query);
        $this->personDao->apply_update();
    }

    /**
     * @return string|null
     * @throws TwoFactorAuthException
     */
    public function getQrCode(): ?string
    {
        if (!self::isActive()) {
            return null;
        }

        // @see ID-10882 Use 'BaconQrCode' provider with 'SVG' format, so we do not need any additional extension (imagick).
        return (new TwoFactorAuth(new BaconQrCodeProvider(format: 'svg'), 'i-doit'))->getQRCodeImageAsDataUri(
            $this->session->get_mandator_name(),
            $this->getTfaSecretForUser(),
            150
        );
    }

    /**
     * @param string|null $code
     *
     * @return bool
     */
    public function checkCode(string $code, ?string $secretKey = null): bool
    {
        try {
            $tfa = new TwoFactorAuth(new BaconQrCodeProvider(format: 'svg'));
            return $tfa->verifyCode($secretKey ?? $this->getTfaSecretForUser(), $code);
        } catch (\Throwable $e) {
            return false;
        }
    }

    /**
     * @return array
     * @throws \Exception
     */
    public function getTfaUsers(): array
    {
        if (!$this->personDao->fieldsExistsInTable('isys_cats_person_list', ['isys_cats_person_list__tfa_secret'])) {
            throw new Exception($this->language->get('LC__USER_SETTINGS__TFA__TABLE_FIELD_ERROR'));
        }

        $data = $this->personDao->get_data(null, null, ' AND isys_cats_person_list__tfa_secret IS NOT NULL AND isys_cats_person_list__tfa_secret != \'\'');

        $response = [];
        while ($row = $data->get_row()) {
            $response[] = $row;
        }

        return $response;
    }

    /**
     * @return bool
     */
    public function isVerified(): bool
    {
        return (bool)$this->session->get('tfa-verified');
    }

    /**
     * @return bool
     * @throws \Exception
     */
    public function needsVerification(): bool
    {
        return $this->isActive() && !$this->isVerified();
    }

    /**
     * @return void
     */
    public function setVerified(): void
    {
        $this->session->set('tfa-verified', true);
    }
}
