<?php
/********************************************************************************
 * (c)1337 aheadware.com - All rights reserved
 ********************************************************************************/


class TB_Server_Control_Auth
{

    /**
     * Logs user in and creates appropriate session
     *
     * @param array $params ( 'email' => (string), 'pass' => (string), 'is_hashed' => true (1) | false (0) )
     *
     * @return TB_Server_Core_Response
     */
    public static function login( TB_Server_Core_RequestData $params ) {
        // Return value
        $resp = new TB_Server_Core_Response();

        // Validate params
        $email = $params->get( 'email' );
        $pass = $params->get( 'password' );
        $isHashed = (bool)$params->get( 'is_hashed', false );
        if ( !$isHashed )
        {
            $pass = TB_Shared_Utils_Security::getSaltedPassword( $pass );
        }

        // Get account
        $account = TB_Shared_Ent_Core_Account::getByCredentials( $email, $pass );
        $profile = NULL;

        if ( is_null( $account ) )
        {
            $resp->setCode( TB_Server_Core_Response::CODE_NOT_FOUND );
            return $resp;
        }

        if ( false == $account->is_validated )
        {
            // Send validation mail
            $ad = TB_Shared_Ent_Core_ActionData::createNew(
                TB_Shared_Ent_Core_ActionData::ACTION_VALIDATE_EMAIL,
                NULL,
                $account->id
            );
            TB_Server_Utils_Mail::sendEmailValidation( array(
                'email' => $account->email,
                'url' => TB_Server_Utils_Config::get( 'url.client' ) . '/#/auth/validate-email/' . $ad->token
            ));
            $resp->addData( 'error', 'not_validated' );
            return $resp;
        }

        // Login successful
        if ( $account instanceof TB_Shared_Ent_Core_Account )
        {
            /*
            // Get latest profile for account
            $profiles = TB_Shared_Ent_TeamData_Profile::getByAccountId( $account->id );
            $teams = array();
            if ( !is_array( $profiles ) || 0 === count( $profiles ) ) {
                throw new Exception( "Account Id: " . $account->id . " does not have any profiles." );
            } else if ( 1 === count( $profiles )) {
                $profile = $profiles[0];
                array_push( $teams, TB_Shared_Ent_TeamData_Team::get( $profile->team_id ) );
            } else {
                // Multiple profiles
                $lastActive = 0;
                foreach( $profiles as $p ) {
                    $pt = TB_Shared_Ent_TeamData_ProfileTimestamp::getByProfileId( $p->id );
                    array_push( $teams, TB_Shared_Ent_TeamData_Team::get( $p->team_id ) );
                    if ( $lastActive < $pt->last_online ) {
                        $profile = $p;
                        $lastActive = $pt->last_online;
                    }
                }
            }
            */

            // Load profile
            /** @var TB_Shared_Ent_TeamData_Profile $profile */
            $profile = TB_Shared_Ent_TeamData_Profile::getByAccountId( $account->id );
            if ( is_null( $profile ) )
            {
                throw new Exception( 'No profile found for account id: ' . $account->id );
            }

            // Create session
            /** @var TB_Shared_Ent_Core_Session $session */
            $session = new TB_Shared_Ent_Core_Session();
            $session->account_id = $account->id;
            $session->profile_id = $profile->id;
            $session->token = TB_Server_Core_Session::generateToken();
            $session->save();

            // Set session
            TB_Server_Core_Session::get()->init( $session );

            // Return entities to client
            $resp->addData( 'userProfile', $profile );
            $resp->addData( 'session', $session );
            $resp->addData( 'teams', TB_Shared_Ent_TeamData_Team::getByProfile( $profile) );
        }
        else
        {
            $resp->setCode( TB_Server_Core_Response::CODE_NOT_FOUND );
        }

        return $resp;
    }

    /**
     * @param TB_Server_Core_RequestData $params
     * @return TB_Server_Core_Response
     */
    public static function getTeamForJoin( TB_Server_Core_RequestData $params )
    {
        // Return value
        $resp = new TB_Server_Core_Response();
        $publicTeamId = $params->get( 'publicTeamId' );

        $team = TB_Shared_Ent_TeamData_Team::getByPublicId( $publicTeamId );
        $resp->addData( 'team', $team );

        return $resp;
    }


    public static function getTeamsForJoin( TB_Server_Core_RequestData $params )
    {
        // Return value
        $resp = new TB_Server_Core_Response();

        $teams = TB_Shared_Ent_TeamData_Team::getAllForJoin();
        $resp->addRowsToData( 'teams', $teams );

        return $resp;
    }

    /**
     * @param TB_Server_Core_RequestData $params
     * @return TB_Server_Core_Response
     * @throws Exception
     */
    public static function joinTeam( TB_Server_Core_RequestData $params )
    {
        // Return value
        $resp = new TB_Server_Core_Response();
        $email = $params->get( 'email' );
        $email = TB_Shared_Utils_Validator::isValidEmail( $email ) ? $email : NULL;

        if ( is_null( $email ) )
        {
            throw new Exception( 'Invalid email' );
        }

        // $firstname = TB_Server_Utils_Helper::getFirstnameFromEmail( $email );
        // $lastname = TB_Server_Utils_Helper::getLastnameFromEmail( $email );

        $firstname = $params->get( 'firstname' );
        $lastname = $params->get( 'lastname' );


        $plainPass = $params->get( 'password' );
        $publicTeamId = $params->get( 'publicTeamId' );
        $isNewAccount = false;

        $team = TB_Shared_Ent_TeamData_Team::getByPublicId( $publicTeamId );
        if ( is_null( $team ) )
        {
            $resp->setCode( TB_Server_Core_Response::CODE_NOT_FOUND);
            return $resp;
        }

        // @todo Check if joining publically is enabled

        // Get account
        $pass = TB_Shared_Utils_Security::getSaltedPassword( $plainPass );
        $account = TB_Shared_Ent_Core_Account::getByCredentials( $email, $pass );
        $profile = NULL;

        if ( is_null( $account ) )
        {
            $account = TB_Shared_Ent_Core_Account::getByEmail( $email );
            if ( isset( $account ) )
            {
                $resp = new TB_Server_Core_Response();
                $resp->addData( 'error', 'email_exists_wrong_password' );
                return $resp;
            }
        }

        try
        {
            /** @var PDO $dbh */
            $dbh = TB_Shared_Db_Core::get();
            $dbh2 = TB_Shared_Db_TeamData::get();
            $dbh->beginTransaction();
            $dbh2->beginTransaction();

            if ( is_null( $account ) )
            {
                $account = new TB_Shared_Ent_Core_Account();
                $account->email = $email;
                $account->pass = $pass;
                $account->is_validated = 0;
                $account->subscribed_newsletter = 0;
                $account->save();

                // Create profile to account
                $profile = new TB_Shared_Ent_TeamData_Profile();
                $profile->account_id = $account->id;
                $profile->public_id = TB_Server_Utils_Helper::getToken(64);
                $profile->birthday_dt = NULL;
                $profile->city = NULL;
                $profile->first_name = $firstname;
                $profile->last_name = $lastname;
                $profile->misc = NULL;
                $profile->mobile = NULL;
                $profile->phone = NULL;
                $profile->pic_cloud_id = NULL;
                $profile->pic_url = NULL;
                $profile->street = NULL;
                $profile->zip_code = NULL;
                $profile->save();

                // Create profile timestamp entity
                $profileTimestamp = new TB_Shared_Ent_TeamData_ProfileTimestamp();
                $profileTimestamp->profile_id = $profile->id;
                $profileTimestamp->save();

                $isNewAccount = true;
            }
            else
            {
                /** @var TB_Shared_Ent_TeamData_Profile $profile */
                $profile = TB_Shared_Ent_TeamData_Profile::getByAccountId( $account->id );
                if ( $profile->isInTeam( $team->id ) )
                {
                    $dbh->rollBack();
                    $dbh2->rollBack();

                    if ( 1 === $account->is_validated )
                    {
                        $resp = self::login( $params );
                        $resp->addData( 'account_validated', true );
                        return $resp;
                    }
                    else
                    {
                        return $resp;
                    }

                }
            }

            // Create team data
            $profile->joinTeam( $team->id );
            $profile->save();

            if ( 0 == $account->is_validated  )
            {
                // Send validation mail
                $ad = TB_Shared_Ent_Core_ActionData::createNew(
                    TB_Shared_Ent_Core_ActionData::ACTION_VALIDATE_EMAIL,
                    NULL,
                    $account->id
                );
                TB_Server_Utils_Mail::sendEmailValidation( array(
                    'email' => $account->email,
                    'url' => TB_Server_Utils_Config::get( 'url.client' ) . '/#/auth/validate-email/' . $ad->token
                ));
            }

            $dbh->commit();
            $dbh2->commit();

        }
        catch ( Exception $e )
        {
            $dbh->rollBack();
            $dbh2->rollBack();

            throw $e;
        }

        // Send notification to admins
        try
        {
            TB_Server_Core_Notification::notifyNewMemberJoined( $team, $profile );
        }
        catch( Exception $e )
        {
            // Fail silently
        }

        if ( 1 == $account->is_validated )
        {
            $resp = self::login( $params );
            $resp->addData( 'account_validated', true );
        }

        return $resp;
    }

    /**
     * @param TB_Server_Core_RequestData $params
     * @return TB_Server_Core_Response
     */
    public static function getSession( TB_Server_Core_RequestData $params )
    {
        // Return value
        $resp = new TB_Server_Core_Response();
        $includeConfig = $params->get( 'includeConfig' );

        // Return entities to client
        $session = TB_Server_Core_Session::get();
        if ( $session->isInitialized() )
        {
            $resp->addData( 'session', $session->getEntCoreSession() );
            $account = TB_Server_Core_Session::get()->getAccount();
            if ( $account )
            {
                $profile = TB_Shared_Ent_TeamData_Profile::getByAccountId( $account->id );
                $resp->addData( 'userProfile', $profile );
                $resp->addData( 'teams', TB_Shared_Ent_TeamData_Team::getByProfile( $profile) );
            }
            else
            {
                $resp->addData( 'userProfile', null );
                $resp->addData( 'teams', [] );
            }
        }
        else
        {
            $resp->addData( 'userProfile', null );
            $resp->addData( 'teams', [] );
        }


        if ( true == $includeConfig )
        {
            $config = array(
                'url' => Francis_Utils_Config::get( 'url.client' ),
                'onesignal.appId' => Francis_Utils_Config::get( 'onesignal.appId' ),
                'environment' => Francis_Utils_Config::get( 'environment' ),
                'version' => Francis_Utils_Config::get( 'version' ),
                'manager.url' => Francis_Utils_Config::get( 'url.manager' ),
            );
            $resp->addData( 'config', $config );
        }

        return $resp;
    }

    public static function sso( TB_Server_Core_RequestData $params )
    {
        // Return value
        $resp = new TB_Server_Core_Response();

        // Return entities to client
        /** @var TB_Shared_Ent_Core_Session $entSession */
        $entSession = TB_Shared_Ent_Core_Session::getByToken( $params->get( 'token' ) );
        $entAccount = NULL;
        $entProfile = NULL;
        $entTeams = [];
        if ( $entSession )
        {
            $entAccount = TB_Shared_Ent_Core_Account::get( $entSession->account_id );
            if ( $entAccount )
            {
                $entProfile = TB_Shared_Ent_TeamData_Profile::get( $entSession->profile_id );
                if ( $entProfile )
                {
                    $entTeams = TB_Shared_Ent_TeamData_Team::getByProfile( $entProfile );
                }
            }
        }

        $resp->addData( 'session', $entSession );
        $resp->addData( 'userProfile', $entProfile->asArray() );
        $resp->addData( 'teams', $entTeams );

        return $resp;
    }

    public static function getSessionManager( TB_Server_Core_RequestData $params )
    {
        // Return value
        $resp = new TB_Server_Core_Response();

        // Return entities to client
        $session = TB_Server_Core_Session::get();
        if ( $session->isInitialized() )
        {
            $resp->addData( 'sessionToken', $session->getToken() );
            $account = TB_Server_Core_Session::get()->getAccount();
            if ( $account )
            {
                /** @var TB_Shared_Ent_TeamData_Profile $profile */
                $profile = TB_Shared_Ent_TeamData_Profile::getByAccountId( $account->id );
                $resp->addData( 'userProfile', $profile );

                $teams = [];
                $zContracts = [];
                $teamIds = $profile->getTeamIds();
                foreach ($teamIds as $teamId )
                {
                    if ( false === $profile->isManagerAccessAllowed( $teamId ) )
                    {
                        continue;
                    }

                    array_push( $teams, TB_Shared_Ent_TeamData_Team::get( $teamId ) );


                    $zcs = TB_Shared_Ent_TeamData_ZContracts::getByTeamId( $teamId );
                    if ( $zcs && count( $zcs ) > 0 )
                    {
                        /** @var TB_Shared_Ent_TeamData_ZContracts $zc */
                        foreach ( $zcs as $zc )
                        {
                            array_push( $zContracts, $zc->asArray() );
                        }
                    }

                }
                $resp->addData( 'teams', $teams );
                $resp->addData( 'zContracts', $zContracts );
            }
            else
            {
                $resp->addData( 'sessionToken', null );
                $resp->addData( 'userProfile', null );
                $resp->addData( 'teams', [] );
                $resp->addData( 'zContracts', [] );
            }
        }
        else
        {
            $resp->addData( 'sessionToken', null );
            $resp->addData( 'userProfile', null );
            $resp->addData( 'teams', [] );
            $resp->addData( 'zContracts', [] );
        }

        return $resp;
    }

    public static function loginManager( TB_Server_Core_RequestData $params )
    {
        // Return value
        $resp = new TB_Server_Core_Response();

        // Validate params
        $email = $params->get( 'email' );
        $pass = $params->get( 'password' );
        $pass = TB_Shared_Utils_Security::getSaltedPassword( $pass );

        // Get account
        $account = TB_Shared_Ent_Core_Account::getByCredentials( $email, $pass );
        $profile = NULL;

        if ( is_null( $account ) )
        {
            $resp->addData( 'err', 'not_found' );
            return $resp;
        }

        if ( false == $account->is_validated )
        {
            $resp->addData( 'error', 'not_validated' );
            return $resp;
        }

        // Login successful
        if ( $account instanceof TB_Shared_Ent_Core_Account )
        {
            // Load profile
            /** @var TB_Shared_Ent_TeamData_Profile $profile */
            $profile = TB_Shared_Ent_TeamData_Profile::getByAccountId( $account->id );
            if ( is_null( $profile ) )
            {
                throw new Exception( 'No profile found for account id: ' . $account->id );
            }

            $adminTeams = [];
            $zContracts = [];
            $teams = TB_Shared_Ent_TeamData_Team::getByProfile( $profile );

            /** @var TB_Shared_Ent_TeamData_Team $team */
            foreach ( $teams as $team )
            {
                if ( false === $profile->isManagerAccessAllowed( $team->id ) )
                {
                    continue;
                }

                array_push( $adminTeams, $team );

                $zcs = TB_Shared_Ent_TeamData_ZContracts::getByTeamId( $team->id );
                if ( $zcs && count( $zcs ) > 0 )
                {
                    /** @var TB_Shared_Ent_TeamData_ZContracts $zc */
                    foreach ( $zcs as $zc )
                    {
                        array_push( $zContracts, $zc->asArray() );
                    }
                }
            }

            if ( 0 === count( $adminTeams ) )
            {
                $resp->addData( 'err', 'no_admin_teams' );
            }
            else
            {
                // Create session
                /** @var TB_Shared_Ent_Core_Session $session */
                $session = new TB_Shared_Ent_Core_Session();
                $session->account_id = $account->id;
                $session->profile_id = $profile->id;
                $session->token = TB_Server_Core_Session::generateToken();
                $session->save();

                // Set session
                TB_Server_Core_Session::get()->init( $session );

                // Return entities to client
                $resp->addData( 'userProfile', $profile );
                $resp->addData( 'session', $session );
                $resp->addData( 'teams', $adminTeams );
                $resp->addData( 'zContracts', $zContracts );
            }
        }
        else
        {
            $resp->addData( 'err', 'not_found' );
            //$resp->setCode( TB_Server_Core_Response::CODE_NOT_FOUND );
        }

        return $resp;
    }

    /**
     * Register a new account
     *
     * Trainer is required to validate email first. This method just prepares everything in the DB
     *
     * @param TB_Server_Core_RequestData $params ( 'email', 'pass', 'role' )
     * @return TB_Server_Core_Response
     */
    public static function registerTrainer( TB_Server_Core_RequestData $params )
    {
        // Return value
        $resp = new TB_Server_Core_Response();

        // Default Params
        $teamName = $params->get( 'teamname' );
        $teamName = TB_Shared_Utils_Validator::isValidName( $teamName ) ? $teamName : NULL;
        $teamCategory = $params->get( 'industry' );
        $teamCategory = TB_Shared_Utils_Validator::isValidName( $teamCategory ) ? $teamCategory : NULL;
        $affiliateId = $params->get( 'affiliateId' );
        $email = $params->get( 'email' );
        $email = TB_Shared_Utils_Validator::isValidEmail( $email ) ? $email : NULL;
        $pass = $params->get( 'password' );
        if ( is_null( $pass ) ) throw new Exception( 'Password cannot be null during registration.' );
        $pass = TB_Shared_Utils_Validator::isValidPassword( $pass ) ? $pass : NULL;
        $plainPass = $pass;
        $pass = TB_Shared_Utils_Security::getSaltedPassword( $pass );

        if ( is_null( $email ) )
        {
            throw new Exception( 'Invalid email' );
        }

        /*
        $gender = $params->get( 'gender' );
        $gender = TB_Shared_Utils_Validator::isValidValue( TB_Shared_Ent_TeamData_Profile::getValidGender(), $gender ) ? $gender : TB_Shared_Ent_TeamData_Profile::GENDER_MALE;
        $firstName = $params->get( 'first_name' );
        $firstName = TB_Shared_Utils_Validator::isValidName( $firstName) ? $firstName: NULL;
        $lastName = $params->get( 'last_name' );
        $lastName = TB_Shared_Utils_Validator::isValidName( $lastName) ? $lastName: NULL;
        */
        $firstname = $params->get( 'firstname' );
        $lastname = $params->get( 'lastname' );

        $subscribedNews = $params->get( 'subscribedNews', 0 );
        $account = NULL;

        // If params seems to be valid
        if ( isset( $email ) && isset( $pass ) && isset( $teamName ) )
        {
            try
            {
                /** @var PDO $dbh */
                $dbh = TB_Shared_Db_Core::get();
                $dbh->beginTransaction();
                $dbh2 = TB_Shared_Db_TeamData::get();
                $dbh2->beginTransaction();

                // Try to load existing account
                $account = TB_Shared_Ent_Core_Account::getByCredentials( $email, $pass );

                // If not
                if ( null == $account )
                {
                    // Create a new one
                    $account = new TB_Shared_Ent_Core_Account();
                    $account->is_validated = $affiliateId === 'retrust' ? 1 : 0;
                    $account->email = $email;
                    $account->pass = $pass;
                    $account->created_dt = new DateTime();
                    $account->subscribed_newsletter = $subscribedNews;
                    $account->save();
                }

                $profile = TB_Shared_Ent_TeamData_Profile::getByAccountId( $account->id );

                if ( null == $profile )
                {
                    // Create profile to account
                    $profile = new TB_Shared_Ent_TeamData_Profile();
                    $profile->account_id = $account->id;
                    $profile->public_id = TB_Server_Utils_Helper::getToken(64);
                    $profile->birthday_dt = NULL;
                    $profile->city = NULL;
                    $profile->first_name = $firstname;
                    $profile->last_name = $lastname;
                    $profile->misc = NULL;
                    $profile->mobile = NULL;
                    $profile->phone = NULL;
                    $profile->pic_cloud_id = NULL;
                    $profile->pic_url = NULL;
                    $profile->street = NULL;
                    $profile->zip_code = NULL;
                    $profile->save();

                    // Create profile timestamp entity
                    $profileTimestamp = new TB_Shared_Ent_TeamData_ProfileTimestamp();
                    $profileTimestamp->profile_id = $profile->id;
                    $profileTimestamp->save();
                }

                // Create team if role is TRAINER
                $team = new TB_Shared_Ent_TeamData_Team();
                $team->public_id = TB_Server_Utils_Helper::getPublicTeamToken( TB_Shared_Ent_TeamData_Team::getAllPublicIds() );
                $team->display_name = _xss( $teamName );
                $team->category = _xss( $teamCategory );
                $team->affiliate_id = is_string( $affiliateId ) ? _xss( $affiliateId ) : NULL;
                $team->save();

                // Set default course categories
                $team->setDefaultCourseCategory();
                $team->save();

                // Create team data
                $teamsJson = $profile->teams_js;
                if ( !is_array( $teamsJson ) )
                {
                    $teamsJson = array();
                }
                array_push( $teamsJson, array(
                    'team_id' => $team->id,
                    'role' => TB_Shared_Ent_TeamData_Profile::ROLE_TRAINER,
                    'is_anonymous' => 0,
                    'join_dt' => TB_Server_Utils_Helper::getUTCNowDateTime()
                ));
                $profile->teams_js = $teamsJson;
                $profile->save();

                // Create session
                /** @var TB_Shared_Ent_Core_Session $session */
                $session = NULL;
                if ( 0 != $account->is_validated )
                {
                    $session = new TB_Shared_Ent_Core_Session();
                    $session->account_id = $account->id;
                    $session->profile_id = $profile->id;
                    $session->token = TB_Server_Core_Session::generateToken();
                    $session->save();
                }
                else
                {
                    // Send validation mail
                    $ad = TB_Shared_Ent_Core_ActionData::createNew(
                        TB_Shared_Ent_Core_ActionData::ACTION_VALIDATE_EMAIL,
                        NULL,
                        $account->id
                    );
                    TB_Server_Utils_Mail::sendEmailValidationTrainer( array(
                        'email' => $account->email,
                        'teamname' => $team->display_name,
                        'url' => TB_Server_Utils_Config::get( 'url.client' ) . '/#/auth/validate-email/' . $ad->token
                    ));
                }

                // Set data to response
                $resp->addData( 'session', $session );

                // Note:
                // It is important to add the profile as array here - to avoid the ResponseProecessor being called
                $resp->addData( 'userProfile', $profile->asArray() );
                $resp->addData( 'teams', [ $team ] );

                // New additional setup
                $courseCategories = $params->get( 'courseCategories' );
                if ( $courseCategories && is_array( $courseCategories ) && 0 < count( $courseCategories ) )
                {
                    foreach( $courseCategories as $courseCategoryName )
                    {
                        if ( strlen( $courseCategoryName ) > 0 )
                        {
                            $team->addCourseCategory( [ 'name' => $courseCategoryName ] );
                        }
                    }

                    $team->save();
                }

                $createDemoAppointments = $params->get( 'createDemoAppointments', 0 );
                if ( $createDemoAppointments === 1 )
                {
                    TB_Shared_Ent_TeamData_Appointment::createTeamAppointmentsForTeam( $team );
                }

                // Special case for JULIA HAMMERSCHMIDT
                if ( $affiliateId === 'hammerschmidt' )
                {
                    $package = $params->get( 'package' );
                    $invoiceName = $params->get( 'invoiceName' );
                    $invoiceStreet = $params->get( 'invoiceStreet' );
                    $invoiceZipCity = $params->get( 'invoiceZipCity' );
                    $invoiceCountry = $params->get( 'invoiceCountry' );

                    $body  = "TEAM INFOS:
                    
                        ID: $team->id
                        NAME: $team->display_name
                        
                        KONTAKT PERSON:
                        
                        ACCOUNT ID: $account->id
                        PROFILE ID:  $profile->id
                        NAME: $profile->first_name $profile->last_name
                        
                        GEBUCHTES PAKET:
                        
                        $package
                        
                        RECHNUNGSANSCHRIFT:
                        
                        $invoiceName
                        $invoiceStreet
                        $invoiceZipCity
                        $invoiceCountry                        
                    ";

                    TB_Server_Utils_Mail::sendToSupport( "HAMMERSCHMIDT Bestellung", $body );
                }

                // Create ZContracts entry
                $zContract = new TB_Shared_Ent_TeamData_ZContracts();
                $zContract->fk_publicTeamID = $team->public_id;
                $zContract->fk_TeamID = $team->id;
                $zContract->displayName = $team->display_name;
                $zContract->status = 'Test Vollversion';
                $zContract->status_visible = 'Lead';
                $zContract->product = 'ProBuddy';
                $zContract->product_volume = 50;
                $zContract->product_type = 'Basis';
                $zContract->create_dt = TB_Server_Utils_Helper::getUTCNowDateTime();
                $zContract->valid_from = '2021-01-01';
                $zContract->valid_to = '2021-12-31';
                $zContract->contracted_period = 12;
                $zContract->price_regularMonth = 19.90;
                $zContract->Invoice_firstName = $profile->first_name;
                $zContract->Invoice_lastName = $profile->last_name;
                $zContract->Invoice_eMail = $account->email;
                $zContract->save();

                // Success
                $dbh->commit();
                $dbh2->commit();
            }
            catch ( Exception $e )
            {
                // Error
                $dbh->rollBack();
                $dbh2->rollBack();

                // EMail already exists
                if ( $e instanceof PDOException && 23000 == $e->getCode() ) {
                    $resp->setCode( TB_Server_Core_Response::CODE_NOT_FOUND );
                    $resp->addData( 'gen_err', 'email_already_exists' );
                }
                else
                {
                    $resp->setCode( TB_Server_Core_Response::CODE_GENERAL_ERROR );
                    TB_Server_Utils_Log::get()->log( $e );
                }
            }
        }
        else
        {
            $resp->setCode( TB_Server_Core_Response::CODE_INVALID_DATA );
        }

        return $resp;
    }

    /**
     * Register a new account
     *
     * Use Case:
     * User actively creates an account (e.g. via landing page - mostly manager|trainer).
     *
     * @param TB_Server_Core_RequestData $params ( 'token', 'pass'  )
     * @return TB_Server_Core_Response
     */
    public static function registerPlayer( TB_Server_Core_RequestData $params ) {
        // Return value
        $resp = new TB_Server_Core_Response();

        // Default Params
        $pass = $params->get( 'pass' );
        $token = $params->get( 'token' );
        $subscribedNews = ( 'on' === $params->get( 'subscribed_news' ) ) ? 1 : 0;
        $invitation = is_null( $token ) ? NULL : TB_Shared_Ent_Core_Invitation::getByToken( $token );
        $team = is_null( $invitation ) ? NULL : TB_Shared_Ent_TeamData_Team::get( $invitation->team_id );

        // Invitation could not be found
        if ( is_null( $invitation ) ) {
            $resp->addData( 'error', 'invalid-token' );
            return $resp;
        }

        $account = TB_Shared_Ent_Core_Account::getByEmail( $invitation->email );

        if ( is_null( $account ) ) {
            $pass = TB_Shared_Utils_Validator::isValidPassword( $pass ) ? $pass : NULL;
            $pass = TB_Shared_Utils_Security::getSaltedPassword( $pass );
        }

        // If params seems to be valid
        if ( isset( $invitation ) && ( isset( $account ) || isset( $pass ) ) && isset( $team )) {
            try {
                /** @var PDO $dbh */
                $dbh = TB_Shared_Db_Core::get();
                $dbh->beginTransaction();

                /** @var PDO $dbhTD */
                $dbhTD = TB_Shared_Db_TeamData::get();
                $dbhTD->beginTransaction();

                // If not
                if ( is_null( $account ) ) {
                    // Create a new one
                    $account = new TB_Shared_Ent_Core_Account();
                    $account->email = $invitation->email;
                    // Since player always comes through email invitation - his email is automatically validated
                    $account->is_validated = true;
                    $account->pass = $pass;
                    $account->created_dt = new DateTime();
                    $account->subscribed_news = $subscribedNews;
                    $account->save();
                }

                // Create profile to account
                $profile = new TB_Shared_Ent_TeamData_Profile();
                $profile->team_id = $team->id;
                $profile->account_id = $account->id;
                $profile->role = $invitation->role;
                $profile->first_name = _xss( $invitation->first_name );
                $profile->last_name = _xss( $invitation->last_name );
                $profile->save();

                // Create profile timestamp entity
                $profileTimestamp = new TB_Shared_Ent_TeamData_ProfileTimestamp();
                $profileTimestamp->profile_id = $profile->id;
                $profileTimestamp->registered = time();
                $profileTimestamp->last_online = time();
                $profileTimestamp->last_news_update = time();
                $profileTimestamp->save();

                // Automatically create a valid session
                $session = new TB_Shared_Ent_Core_Session();
                $session->account_id = $account->id;
                $session->profile_id = $profile->id;
                $session->token = TB_Server_Core_Session::generateToken();
                $session->save();

                // Set session
                TB_Server_Core_Session::get()->init( $session );

                $profiles = TB_Shared_Ent_TeamData_Profile::getByAccountId( $account->id );
                $teams = array();
                foreach( $profiles as $p ) {
                    array_push( $teams, TB_Shared_Ent_TeamData_Team::get( $p->team_id ) );
                }


                // Set data to response
                $resp->addData( 'account', $account );
                $resp->addData( 'userProfile', $profile );
                $resp->addData( 'session', $session );
                $resp->addData( 'profiles', $profiles );
                $resp->addData( 'teams', $teams );

                // Mark invitation as done
                $invitation->state = TB_Shared_Ent_Core_Invitation::STATE_ACCEPTED;
                $invitation->save();

                // Success
                $dbh->commit();
                $dbhTD->commit();
            } catch ( Exception $e ) {
                // Error
                $dbh->rollBack();
                $dbhTD->rollBack();

                // EMail already exists
                if ( $e instanceof PDOException && 23000 == $e->getCode() ) {
                    $resp->setCode( TB_Server_Core_Response::CODE_GENERAL_ERROR );
                    $resp->addNotification( new TB_Server_Utils_Notification( _lc( 'EMail existiert bereits.' ), TB_Server_Utils_Notification::TYPE_WARNING ) );
                }
                else {
                    $resp->setCode( TB_Server_Core_Response::CODE_GENERAL_ERROR );
                    TB_Server_Utils_Log::get()->log( $e );
                }
            }
        } else {
            $resp->setCode( TB_Server_Core_Response::CODE_INVALID_DATA );
        }

        return $resp;
    }

    public static function getInvitation( TB_Server_Core_RequestData $params ) {
        $resp = new TB_Server_Core_Response();

        $token = $params->get( 'token' );
        $invitation = is_null( $token ) ? NULL : TB_Shared_Ent_Core_Invitation::getByToken( $token );
        /** @var TB_Shared_Ent_TeamData_Team $team */
        $team = isset( $invitation ) ? TB_Shared_Ent_TeamData_Team::get( $invitation->team_id ) : NULL;
        if ( is_null( $invitation ) || is_null( $team ) ) {
            $resp->addData( 'token', NULL );
        } else {
            $account = TB_Shared_Ent_Core_Account::getByEmail( $invitation->email );
            // If account exists
            if ( isset( $account ) ) {
                // Hide password to client
                $account->pass = '';
            }
            $resp->addData( 'token', $token );
            $resp->addData( 'invitation', $invitation );
            $resp->addData( 'inviter', TB_Shared_Ent_TeamData_Profile::get( $invitation->inviter_profile_id ) );
            $resp->addData( 'team', $team );
            $resp->addData( 'account', $account );
        }

        return $resp;
    }

    /**
     * @param TB_Server_Core_RequestData $params
     * @return TB_Server_Core_Response
     */
    public static function requestPasswordReset( TB_Server_Core_RequestData $params )
    {
        $resp = new TB_Server_Core_Response();

        $email = $params->get( 'email' );
        $account = TB_Shared_Ent_Core_Account::getByEmail( $email );
        if ( isset( $account ) )
        {
            $oldToken = TB_Shared_Ent_Core_ActionData::getByActionAndAccountId(
                TB_Shared_Ent_Core_ActionData::ACTION_RESET_PASSWORD,
                $account->id
            );
            if ( $oldToken )
            {
                $oldToken->delete();
            }
            $entToken = TB_Shared_Ent_Core_ActionData::createNew(
                TB_Shared_Ent_Core_ActionData::ACTION_RESET_PASSWORD,
                NULL,
                $account->id
            );

            TB_Server_Utils_Mail::sendPasswordReset(array(
                'email' => $account->email,
                'url' => TB_Server_Utils_Config::get( 'url.client' ) . '/#/auth/reset-password/' . $entToken->token
            ));

            $resp->addData( 'res', 'success' );
        }
        else
        {
            $resp->addData( 'res', 'account_not_found' );
        }

        return $resp;
    }

    /**
     *
     * @param TB_Server_Core_RequestData $params
     * @return TB_Server_Core_Response
     */
    public static function resetPassword( TB_Server_Core_RequestData $params )
    {
        $resp = new TB_Server_Core_Response();

        /** @var TB_Shared_Ent_Core_ActionData $entToken */
        $entToken = TB_Shared_Ent_Core_ActionData::getByToken( $params->get( 'token' ) );
        if ( !isset( $entToken ) || $entToken->action !== TB_Shared_Ent_Core_ActionData::ACTION_RESET_PASSWORD )
        {
            throw new \Exception( 'Invalid token / token action.' );
        }

        /** @var TB_Shared_Ent_Core_Account $account */
        $account = TB_Shared_Ent_Core_Account::get( $entToken->related_account_id );
        if ( !isset( $account ) )
        {
            throw new \Exception( 'Invalid account.' );
        }

        // Validation
        $password1 = $params->get( 'newPassword' );

        if ( false === TB_Shared_Utils_Validator::isValidPassword( $password1 ) )
        {
            throw new \Exception( 'New password is invalid.' );
        }

        // Update account with new password
        $account->pass = TB_Shared_Utils_Security::getSaltedPassword( $password1 );
        $account->save();

        $resp->addData( 'res', 'ok' );

        $entToken->delete();

        return $resp;
    }


    /**
     *
     * @param TB_Server_Core_RequestData $params
     * @return TB_Server_Core_Response
     */
    public static function validateEmail( TB_Server_Core_RequestData $params )
    {
        $resp = new TB_Server_Core_Response();

        /** @var TB_Shared_Ent_Core_ActionData $entToken */
        $entToken = TB_Shared_Ent_Core_ActionData::getByToken( $params->get( 'token' ) );
        if ( !isset( $entToken ) || $entToken->action !== TB_Shared_Ent_Core_ActionData::ACTION_VALIDATE_EMAIL )
        {
            throw new \Exception( 'Invalid token / token action.' );
        }

        /** @var TB_Shared_Ent_Core_Account $account */
        $account = TB_Shared_Ent_Core_Account::get( $entToken->related_account_id );
        if ( !isset( $account ) )
        {
            throw new \Exception( 'Invalid account.' );
        }

        // Update account
        $account->is_validated = 1;
        $account->save();

        // Load profile
        /** @var TB_Shared_Ent_TeamData_Profile $profile */
        $profile = TB_Shared_Ent_TeamData_Profile::getByAccountId( $account->id );
        if ( is_null( $profile ) )
        {
            throw new Exception( 'No profile found for account id: ' . $account->id );
        }

        // Create session
        /** @var TB_Shared_Ent_Core_Session $session */
        $session = new TB_Shared_Ent_Core_Session();
        $session->account_id = $account->id;
        $session->profile_id = $profile->id;
        $session->token = TB_Server_Core_Session::generateToken();
        $session->save();

        // Set session
        TB_Server_Core_Session::get()->init( $session );

        // Return entities to client
        $resp->addData( 'userProfile', $profile );
        $resp->addData( 'session', $session );
        $resp->addData( 'teams', TB_Shared_Ent_TeamData_Team::getByProfile( $profile) );

        $resp->addData( 'result', 'ok' );

        $entToken->delete();

        return $resp;
    }

    /**
     * @param TB_Server_Core_RequestData $params
     * @return TB_Server_Core_Response
     */
    public static function getToken( TB_Server_Core_RequestData $params )
    {
        $resp = new TB_Server_Core_Response();

        $entToken = TB_Shared_Ent_Core_ActionData::getByToken( $params->get( 'token' ) );

        $resp->addData( 'token', $entToken );

        return $resp;
    }

    /**
     * @param TB_Server_Core_RequestData $params
     * @return TB_Server_Core_Response
     * @throws Exception
     */
    public static function confirmEmailChange( TB_Server_Core_RequestData $params ) {
        $resp = new TB_Server_Core_Response();
        $result = 'nok';

        $token = $params->get( 'token' );

        /** @var TB_Shared_Ent_Core_EmailChange $ec */
        $ec = TB_Shared_Ent_Core_EmailChange::getByToken( $token );
        if ( isset( $ec ) ) {

            $now = time();
            $until = $ec->valid_until_dt->getTimestamp();

            // If token is still valid
            if ( $now < $until ) {

                /** @var TB_Shared_Ent_Core_Account $account */
                $account = TB_Shared_Ent_Core_Account::get( $ec->account_id );
                $duplicateAccount = TB_Shared_Ent_Core_Account::getByEmail( $ec->new_email );
                if ( isset( $account ) && is_null( $duplicateAccount ) ) {
                    $account->email = $ec->new_email;
                    $account->save();

                    $result = 'ok';

                    $ec->delete();
                }
            } else {
                $result = 'timeup';
            }
        }

        $resp->addData( 'email_change_result', $result );

        return $resp;
    }

    /**
     * Logs user out of current session
     *
     * @return TB_Server_Core_Response
     */
    public static function logout( TB_Server_Core_RequestData $params ) {
        // Return value
        $resp = new TB_Server_Core_Response();

        // Remove device registration
        $account = TB_Server_Core_Session::get()->getAccount();
        $deviceId = $params->get( 'device_id' );
        if ( !is_null( $account ) && !is_null( $deviceId ) )
        {
            /** @var TB_Shared_Ent_Core_DeviceReg $dr */
            $dr = TB_Shared_Ent_Core_DeviceReg::getByAccountIdAndRegId( $account->id, $deviceId );
            if ( isset( $dr ) )
            {
                $dr->delete();
            }
        }

        // Destory session
        TB_Server_Core_Session::get()->destory();

        return $resp;
    }
}