<?php

/**
 * Matomo - free/libre analytics platform
 *
 * @link    https://matomo.org
 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 */

namespace Piwik\Plugins\UsersManager\tests\System;

use Piwik\Date;
use Piwik\API\Request;
use Piwik\Piwik;
use Piwik\Plugins\UsersManager\API;
use Piwik\Plugins\UsersManager\Model;
use Piwik\Plugins\UsersManager\tests\Fixtures\ManyUsers;
use Piwik\Tests\Framework\TestCase\SystemTestCase;

/**
 * @group UsersManager
 * @group ApiTest
 * @group Plugins
 */
class ApiTest extends SystemTestCase
{
    /**
     * @var ManyUsers
     */
    public static $fixture = null; // initialized below class definition

    /**
     * @var API
     */
    private $api;

    /**
     * @var Model
     */
    private $model;

    public function setUp(): void
    {
        parent::setUp(); // TODO: Change the autogenerated stub

        $this->api = API::getInstance();
        $this->model = new Model();
    }

    /**
     * @dataProvider getApiForTesting
     */
    public function testApi($api, $params = [])
    {
        $apiId = implode('_', $params);
        $logins = [
            'login1' => 'when_superuseraccess',
            'login2' => 'when_adminaccess',
            'login4' => 'when_viewaccess',
        ];

        // login1 = super user, login2 = some admin access, login4 = only view access
        foreach ($logins as $login => $appendix) {
            $params['token_auth'] = self::$fixture->users[$login]['token'];
            $xmlFieldsToRemove = [
                'date_registered',
                'invite_token',
                'invite_accept_at',
                'invite_expired_at',
                'last_seen',
                'last_seen_ago',
                'password',
                'token_auth',
                'ts_password_modified',
                'idchange_last_viewed',
                'invite_status',
                'ts_changes_shown',
                'ts_last_seen',
            ];

            $this->runAnyApiTest($api, $apiId . '_' . $appendix, $params, array('xmlFieldsToRemove' => $xmlFieldsToRemove));
        }
    }

    public function testGetUserPreferenceLoginIsOptional()
    {
        $response = Request::processRequest('UsersManager.getUserPreference', array(
            'preferenceName' => API::PREFERENCE_DEFAULT_REPORT,
        ));
        $this->assertEquals('1', $response);

        $response = Request::processRequest('UsersManager.getUserPreference', array(
            'preferenceName' => API::PREFERENCE_DEFAULT_REPORT_DATE,
        ));
        $this->assertEquals('yesterday', $response);
    }

    public function testGetUserPreferenceLoginCanBeSet()
    {
        $response = Request::processRequest('UsersManager.getUserPreference', array(
            'userLogin' => Piwik::getCurrentUserLogin(),
            'preferenceName' => API::PREFERENCE_DEFAULT_REPORT_DATE,
        ));
        $this->assertEquals('yesterday', $response);

        // user not exists
        $response = Request::processRequest('UsersManager.getUserPreference', array(
            'userLogin' => 'foo',
            'preferenceName' => API::PREFERENCE_DEFAULT_REPORT_DATE,
        ));
        $this->assertEquals('yesterday', $response);
    }

    public function getApiForTesting()
    {
        $apiToTest = array(
            array('UsersManager.getUsers'),
            array('UsersManager.getUsersLogin'),
            array('UsersManager.getUsersAccessFromSite', array('idSite' => 6)), // admin user has admin access for this
            array('UsersManager.getUsersAccessFromSite', array('idSite' => 3)), // admin user has only view access for this, should not see anything
            array('UsersManager.getUsersSitesFromAccess', array('access' => 'admin')),
            array('UsersManager.getUsersWithSiteAccess', array('idSite' => 3, 'access' => 'admin')),
            array('UsersManager.getUser', array('userLogin' => 'login1')),
            array('UsersManager.getUser', array('userLogin' => 'login2')),
            array('UsersManager.getUser', array('userLogin' => 'login4')),
            array('UsersManager.getUser', array('userLogin' => 'login6')),
        );

        return $apiToTest;
    }

    public function testCreateAppSpecificTokenAuthWithCrypticPassword()
    {
        $password = 'p§$%"@&<~#\'\\/+ >*^!°p';
        API::$UPDATE_USER_REQUIRE_PASSWORD_CONFIRMATION = false;
        $this->api->updateUser('login6', $password);
        API::$UPDATE_USER_REQUIRE_PASSWORD_CONFIRMATION = true;
        $this->model->deleteAllTokensForUser('login6');
        $token = $this->api->createAppSpecificTokenAuth('login6', $password, 'test');
        $this->assertMd5($token);

        $user = $this->model->getUserByTokenAuth($token);
        $this->assertSame('login6', $user['login']);
    }

    public function testCreateAppSpecificTokenAuth()
    {
        $this->model->deleteAllTokensForUser('login1');
        $token = $this->api->createAppSpecificTokenAuth('login1', 'password', 'test');
        $this->assertMd5($token);

        $user = $this->model->getUserByTokenAuth($token);
        $this->assertSame('login1', $user['login']);
    }

    public function testCreateAppSpecificTokenAuthCanLoginByEmail()
    {
        $this->model->deleteAllTokensForUser('login1');
        $token = $this->api->createAppSpecificTokenAuth('login1@example.com', 'password', 'test');
        $this->assertMd5($token);

        $user = $this->model->getUserByTokenAuth($token);
        $this->assertSame('login1', $user['login']);
    }

    public function testCreateAppSpecificTokenAuthFailsWhenPasswordNotValid()
    {
        $this->expectException(\Exception::class);
        $this->expectExceptionMessage('The current password you entered is not correct.');

        $this->model->deleteAllTokensForUser('login1');
        $this->api->createAppSpecificTokenAuth('login1', 'foooooo', 'test');
    }

    public function testCreateAppSpecificTokenAuthWithExpireDate()
    {
        $this->model->deleteAllTokensForUser('login1');

        $expiryDate = (new \DateTime())->modify('+1 day');

        $token = $this->api->createAppSpecificTokenAuth('login1', 'password', 'test', $expiryDate->format('Y-m-d H:i:s'));
        $this->assertMd5($token);

        $tokens = $this->model->getAllNonSystemTokensForLogin('login1');
        $this->assertEquals($this->model->hashTokenAuth($token), $tokens[0]['password']);
        $this->assertEquals('login1', $tokens[0]['login']);
        $this->assertEquals('test', $tokens[0]['description']);
        $this->assertEquals($expiryDate->format('Y-m-d H:i:s'), $tokens[0]['date_expired']);
    }

    public function testCreateAppSpecificTokenAuthWithExpireHours()
    {
        $expireInHours = 48;
        $this->model->deleteAllTokensForUser('login1');
        $token = $this->api->createAppSpecificTokenAuth('login1', 'password', 'test', null, $expireInHours);
        $this->assertMd5($token);

        $tokens = $this->model->getAllNonSystemTokensForLogin('login1');
        $this->assertEquals($this->model->hashTokenAuth($token), $tokens[0]['password']);
        $this->assertEquals('login1', $tokens[0]['login']);
        $this->assertNotEmpty($tokens[0]['date_expired']);

        $dateExpired = Date::factory($tokens[0]['date_expired']);
        $dateExpired->isLater(Date::now()->addHour($expireInHours - 1));
        $dateExpired->isEarlier(Date::now()->addHour($expireInHours + 1));
    }

    private function assertMd5($string)
    {
        $this->assertSame(32, strlen($string));
        $this->assertTrue(ctype_xdigit($string));
    }

    public static function getOutputPrefix()
    {
        return '';
    }

    public static function getPathToTestDirectory()
    {
        return dirname(__FILE__);
    }
}

ApiTest::$fixture = new ManyUsers(1, 1, false);
