<?php
/**
 * This code is licensed under AGPLv3 license or Afterlogic Software License
 * if commercial version of the product was purchased.
 * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
 */

namespace Aurora\Modules\Dav;

use Aurora\Modules\Core\Models\User;
use Aurora\System\Api;
use Aurora\System\Facades\Route;

/**
 * Integrate SabreDav framework into Aurora platform.
 *
 * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
 * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
 * @copyright Copyright (c) 2023, Afterlogic Corp.
 *
 * @property Settings $oModuleSettings
 *
 * @package Modules
 */
class Module extends \Aurora\System\Module\AbstractModule
{
    public $oManager = null;

    protected $Skip2FA = false;

    protected $DavLogin = false;

    /**
     * @return Module
     */
    public static function getInstance()
    {
        return parent::getInstance();
    }

    /**
     * @return Module
     */
    public static function Decorator()
    {
        return parent::Decorator();
    }

    /**
     * @return Settings
     */
    public function getModuleSettings()
    {
        return $this->oModuleSettings;
    }

    /**
     * @return Manager
     */
    public function getManager()
    {
        if ($this->oManager === null) {
            $this->oManager = new Manager($this);
        }

        return $this->oManager;
    }

    public function GetModuleManager()
    {
        return parent::GetModuleManager();
    }

    /***** private functions *****/
    /**
     * Initializes DAV Module.
     *
     * @ignore
     */
    public function init()
    {
        Route::add(
            $this,
            [
                'dav' => 'EntryDav'
            ]
        );

        $this->subscribeEvent('Calendar::GetCalendars::after', array($this, 'onAfterGetCalendars'));
        $this->subscribeEvent('MobileSync::GetInfo', array($this, 'onGetMobileSyncInfo'));
        $this->subscribeEvent('Core::Authenticate::after', array($this, 'onAfterAuthenticate'), 90);
        $this->subscribeEvent(self::GetName() . '::GetDigestHash::after', array($this, 'onAfterGetDigestHash'), 90);
        $this->subscribeEvent('Core::SetAuthDataAndGetAuthToken::after', array($this, 'onAfterSetAuthDataAndGetAuthToken'), 5);

        $this->denyMethodsCallByWebApi([
            'GetDigestHash'
        ]);
    }

    /**
     * Writes in $aParameters DAV server URL.
     *
     * @ignore
     * @param array $aArgs
     */
    public function onAfterGetCalendars(&$aArgs, &$mResult)
    {
        if (isset($mResult) && $mResult !== false) {
            $mResult['ServerUrl'] = $this->GetServerUrl();
        }
    }

    /**
     * Writes in $aData information about DAV server.
     *
     * @ignore
     * @param array $mResult
     */
    public function onGetMobileSyncInfo($aArgs, &$mResult)
    {
        $mResult['EnableDav'] = true;
        $mResult['Dav']['Login'] = $this->GetLogin();
        $mResult['Dav']['Server'] = $this->GetServerUrl();
        $mResult['Dav']['PrincipalUrl'] = $this->GetPrincipalUrl();
    }

    /**
     * Creates tables required for module work. Called by event subscribe.
     *
     * @ignore
     * @param array $aArgs Parameters
     * @param mixed $mResult
     */
    public function onAfterCreateTables($aArgs, &$mResult)
    {
        // if ($mResult)
        // {
        // 	$mResult = self::Decorator()->CreateTables();
        // }
    }

    /**
     * Skips 2FA if the Skip2FA variable is true
     *
     * @param array $aArgs
     * @param array $mResult
     */
    public function onAfterAuthenticate($aArgs, &$mResult)
    {
        if ($this->Skip2FA) {
            return true;
        }
    }

    /**
     * Checks if the user has TwoFactorAuth enabled and skips if the Skip2FA configuration is true
     *
     * @param array $aArgs
     * @param array $mResult
     */
    public function onAfterGetDigestHash($aArgs, &$mResult)
    {
        $module2FA = Api::GetModule('TwoFactorAuth');
        if ($module2FA && method_exists($module2FA, 'GetUserSettings')) {
            $oUser = Api::getUserByPublicId($aArgs['Login']);
            if ($oUser instanceof User && $oUser->isNormalOrTenant()) {
                $prevState = Api::skipCheckUserRole(true);
                $userSettings = $module2FA->GetUserSettings($oUser->Id);
                Api::skipCheckUserRole($prevState);
                $Skip2FA = $this->getConfig('Skip2FA', false);
                if ($userSettings && isset($userSettings['TwoFactorAuthEnabled']) && $userSettings['TwoFactorAuthEnabled'] && !$Skip2FA) {
                    $mResult = null;
                    return true;
                }
            }
        }
    }

    public function onAfterSetAuthDataAndGetAuthToken(&$aArgs, &$mResult)
    {
        // skip used devices functionality for DAV logins
        if ($this->DavLogin) {
            $this->DavLogin = false; // reset variable for future logins
            return true; // skip event handlers with lower priority to avoid storing auth token in cache for DAV login
        }
    }
    /***** private functions *****/

    /***** public functions *****/

    /**
     * @ignore
     * @return void
     */
    public function EntryDav()
    {
        set_error_handler(function ($errno, $errstr, $errfile, $errline) {
            throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
        });

        @set_time_limit(3000);

        $sRequestUri = \Aurora\System\Utils::RequestUri();

        $oServer = \Afterlogic\DAV\Server::getInstance();
        $oServer->setBaseUri($sRequestUri);

        \Afterlogic\DAV\Server::getInstance()->exec();
    }

    /**
     * Returns DAV client.
     *
     * @return \Aurora\Modules\Dav\Client|false
     */
    public function GetDavClient()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        return $this->getManager()->GetDAVClient(\Aurora\System\Api::getAuthenticatedUserId());
    }

    /**
     * Returns VCARD object.
     *
     * @param string|resource $Data
     * @return \Sabre\VObject\Document
     */
    public function GetVCardObject($Data)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        return $this->getManager()->getVCardObject($Data);
    }
    /***** public functions *****/

    /***** public functions might be called with web API *****/
    /**
     * @apiDefine Dav Dav Module
     * Integrate SabreDav framework into Aurora platform
     */

    /**
     * @api {post} ?/Api/ GetSettings
     * @apiName GetSettings
     * @apiGroup Dav
     * @apiDescription Obtains list of module settings for authenticated user.
     *
     * @apiHeader {string} [Authorization] "Bearer " + Authentication token which was received as the result of Core.Login method.
     * @apiHeaderExample {json} Header-Example:
     *	{
     *		"Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
     *	}
     *
     * @apiParam {string=Dav} Module Module name.
     * @apiParam {string=GetSettings} Method Method name.
     *
     * @apiParamExample {json} Request-Example:
     * {
     *	Module: 'Dav',
     *	Method: 'GetSettings'
     * }
     *
     * @apiSuccess {object[]} Result Array of response objects.
     * @apiSuccess {string} Result.Module Module name.
     * @apiSuccess {string} Result.Method Method name.
     * @apiSuccess {mixed} Result.Result Object in case of success, otherwise **false**.
     * @apiSuccess {string} Result.Result.ExternalHostNameOfDAVServer External host name of DAV server.
     * @apiSuccess {int} [Result.ErrorCode] Error code.
     *
     * @apiSuccessExample {json} Success response example:
     * {
     *	Module: 'Dav',
     *	Method: 'GetSettings',
     *	Result: [{ExternalHostNameOfDAVServer: 'host_value'}]
     * }
     *
     * @apiSuccessExample {json} Error response example:
     * {
     *	Module: 'Dav',
     *	Method: 'GetSettings',
     *	Result: false,
     *	ErrorCode: 102
     * }
     */
    /**
     * Obtains list of module settings for authenticated user.
     *
     * @return array
     */
    public function GetSettings()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

        return array(
            'ExternalHostNameOfDAVServer' => $this->GetServerUrl()
        );
    }

    /**
     * @api {post} ?/Api/ UpdateSettings
     * @apiName UpdateSettings
     * @apiGroup Dav
     * @apiDescription Updates module's settings - saves them to config.json file.
     *
     * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
     * @apiHeaderExample {json} Header-Example:
     *	{
     *		"Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
     *	}
     *
     * @apiParam {string=Dav} Module Module name.
     * @apiParam {string=UpdateSettings} Method Method name.
     * @apiParam {string} Parameters JSON.stringified object <br>
     * {<br>
     * &emsp; **ExternalHostNameOfDAVServer** *string* External host name of DAV server.<br>
     * }
     *
     * @apiParamExample {json} Request-Example:
     * {
     *	Module: 'Dav',
     *	Method: 'UpdateSettings',
     *	Parameters: '{ ExternalHostNameOfDAVServer: "host_value" }'
     * }
     *
     * @apiSuccess {object[]} Result Array of response objects.
     * @apiSuccess {string} Result.Module Module name.
     * @apiSuccess {string} Result.Method Method name.
     * @apiSuccess {bool} Result.Result Indicates if settings were updated successfully.
     * @apiSuccess {int} [Result.ErrorCode] Error code.
     *
     * @apiSuccessExample {json} Success response example:
     * {
     *	Module: 'Dav',
     *	Method: 'UpdateSettings',
     *	Result: true
     * }
     *
     * @apiSuccessExample {json} Error response example:
     * {
     *	Module: 'Dav',
     *	Method: 'UpdateSettings',
     *	Result: false,
     *	ErrorCode: 102
     * }
     */
    /**
     * Updates module's settings - saves them to config.json file.
     *
     * @param string $ExternalHostNameOfDAVServer External host name of DAV server.
     * @return bool
     */
    public function UpdateSettings($ExternalHostNameOfDAVServer)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);

        if (!empty($ExternalHostNameOfDAVServer)) {
            $this->setConfig('ExternalHostNameOfDAVServer', $ExternalHostNameOfDAVServer);
            $this->saveModuleConfig();
            return true;
        }

        return false;
    }

    /**
     * @api {post} ?/Api/ GetServerUrl
     * @apiName GetServerUrl
     * @apiGroup Dav
     * @apiDescription Returns DAV server URL.
     *
     * @apiParam {string=Dav} Module Module name.
     * @apiParam {string=GetServerUrl} Method Method name.
     *
     * @apiParamExample {json} Request-Example:
     * {
     *	Module: 'Dav',
     *	Method: 'GetServerUrl'
     * }
     *
     * @apiSuccess {object[]} Result Array of response objects.
     * @apiSuccess {string} Result.Module Module name.
     * @apiSuccess {string} Result.Method Method name.
     * @apiSuccess {mixed} Result.Result DAV server URL in case of success, otherwise **false**.
     * @apiSuccess {int} [Result.ErrorCode] Error code.
     *
     * @apiSuccessExample {json} Success response example:
     * {
     *	Module: 'Dav',
     *	Method: 'GetServerUrl',
     *	Result: 'url_value'
     * }
     *
     * @apiSuccessExample {json} Error response example:
     * {
     *	Module: 'Dav',
     *	Method: 'GetServerUrl',
     *	Result: false,
     *	ErrorCode: 102
     * }
     */
    /**
     * Returns DAV server URL.
     *
     * @return string
     */
    public function GetServerUrl()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

        return $this->getManager()->getServerUrl();
    }

    /**
     * @api {post} ?/Api/ GetServerHost
     * @apiName GetServerHost
     * @apiGroup Dav
     * @apiDescription Returns DAV server host.
     *
     * @apiParam {string=Dav} Module Module name.
     * @apiParam {string=GetServerHost} Method Method name.
     *
     * @apiParamExample {json} Request-Example:
     * {
     *	Module: 'Dav',
     *	Method: 'GetServerHost'
     * }
     *
     * @apiSuccess {object[]} Result Array of response objects.
     * @apiSuccess {string} Result.Module Module name.
     * @apiSuccess {string} Result.Method Method name.
     * @apiSuccess {mixed} Result.Result DAV server host in case of success, otherwise **false**.
     * @apiSuccess {int} [Result.ErrorCode] Error code.
     *
     * @apiSuccessExample {json} Success response example:
     * {
     *	Module: 'Dav',
     *	Method: 'GetServerHost',
     *	Result: 'host_value'
     * }
     *
     * @apiSuccessExample {json} Error response example:
     * {
     *	Module: 'Dav',
     *	Method: 'GetServerHost',
     *	Result: false,
     *	ErrorCode: 102
     * }
     */
    /**
     * Returns DAV server host.
     *
     * @return string
     */
    public function GetServerHost()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

        return $this->getManager()->getServerHost();
    }

    /**
     * @api {post} ?/Api/ GetServerPort
     * @apiName GetServerPort
     * @apiGroup Dav
     * @apiDescription Returns DAV server port.
     *
     * @apiParam {string=Dav} Module Module name.
     * @apiParam {string=GetServerPort} Method Method name.
     *
     * @apiParamExample {json} Request-Example:
     * {
     *	Module: 'Dav',
     *	Method: 'GetServerPort'
     * }
     *
     * @apiSuccess {object[]} Result Array of response objects.
     * @apiSuccess {string} Result.Module Module name.
     * @apiSuccess {string} Result.Method Method name.
     * @apiSuccess {mixed} Result.Result DAV server post in case of success, otherwise **false**.
     * @apiSuccess {int} [Result.ErrorCode] Error code.
     *
     * @apiSuccessExample {json} Success response example:
     * {
     *	Module: 'Dav',
     *	Method: 'GetServerPort',
     *	Result: 'port_value'
     * }
     *
     * @apiSuccessExample {json} Error response example:
     * {
     *	Module: 'Dav',
     *	Method: 'GetServerPort',
     *	Result: false,
     *	ErrorCode: 102
     * }
     */
    /**
     * Returns DAV server port.
     *
     * @return int
     */
    public function GetServerPort()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

        return $this->getManager()->getServerPort();
    }

    /**
     * Returns DAV principal URL.
     *
     * @return string
     */
    public function GetPrincipalUrl()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
        $mResult = null;

        $oUser = \Aurora\System\Api::getAuthenticatedUser();
        if ($oUser) {
            $mResult = $this->getManager()->getPrincipalUrl($oUser->PublicId);
        }
        return $mResult;
    }

    /**
     * Returns **true** if connection to DAV should use SSL.
     *
     * @return bool
     */
    public function IsSsl()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        return $this->getManager()->isSsl();
    }

    /**
     * Returns DAV login.
     *
     * @return string
     */
    public function GetLogin()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
        $mResult = null;
        $oEntity = \Aurora\Modules\Core\Models\User::find(\Aurora\System\Api::getAuthenticatedUserId());
        if ($oEntity) {
            $mResult = $oEntity->PublicId;
        }

        return $mResult;
    }

    /**
     *
     * @param string $Login
     * @param string $Realm
     * @param string $Type
     * @return string|null
     */
    public function GetDigestHash($Login, $Realm, $Type)
    {
        /** This method is restricted to be called by web API (see denyMethodsCallByWebApi method). **/

        return null;
    }

    /**
     * Returns **true** if mobile sync enabled.
     *
     * @return bool
     */
    public function IsMobileSyncEnabled()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        return $this->getManager()->isMobileSyncEnabled();
    }

    /**
     * Sets mobile sync enabled/disabled.
     *
     * @param bool $MobileSyncEnable Indicates if mobile sync should be enabled.
     * @return bool
     */
    public function SetMobileSyncEnable($MobileSyncEnable)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        $oMobileSyncModule = \Aurora\System\Api::GetModule('MobileSync');
        $oMobileSyncModule->setConfig('Disabled', !$MobileSyncEnable);
        return $oMobileSyncModule->saveModuleConfig();
    }

    /**
     * Tests connection and returns **true** if connection was successful.
     *
     * @return bool
     */
    public function TestConnection()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        return $this->getManager()->testConnection(
            \Aurora\System\Api::getAuthenticatedUserId()
        );
    }

    /**
     * Deletes principal.
     *
     * @return bool
     */
    public function DeletePrincipal()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        return $this->getManager()->deletePrincipal(
            \Aurora\System\Api::getAuthenticatedUserId()
        );
    }

    /**
     * Returns public user.
     *
     * @return string
     */
    public function GetPublicUser()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        return \Afterlogic\DAV\Constants::DAV_PUBLIC_PRINCIPAL;
    }

    /**
     *
     */
    public function Login($Login, $Password)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

        if (!$this->oModuleSettings->UseFullEmailForLogin) {
            $Login = $Login . '@' . $this->oModuleSettings->DomainForLoginWithoutEmail;
        }

        $this->Skip2FA = $this->getConfig('Skip2FA', false);
        $this->DavLogin = true;
        $mResult = \Aurora\Modules\Core\Module::Decorator()->Login($Login, $Password, '', false);
        $this->Skip2FA = false;

        if (is_array($mResult) && isset($mResult[\Aurora\System\Application::AUTH_TOKEN_KEY])) {
            $sAuthToken = $mResult[\Aurora\System\Application::AUTH_TOKEN_KEY];

            //this will store user data in static variable of Api class for later usage
            $oUser = \Aurora\System\Api::getAuthenticatedUser($sAuthToken);

            return array(
                \Aurora\System\Application::AUTH_TOKEN_KEY => $sAuthToken
            );
        }

        \Aurora\System\Api::LogEvent('login-failed: ' . $Login, self::GetName());
        if (!is_writable(\Aurora\System\Api::DataPath())) {
            throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::SystemNotConfigured);
        }
        throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AuthError);
    }
    /***** public functions might be called with web API *****/
}
