<?php

use PHPUnit\Framework\TestCase;

class basesessionTest extends TestCase {

	protected array $testUser;
	protected static string $username='basesessionTestUser';
	protected static string $password='secret123';

	protected function setUp(): void {
		database::connect();
		try {
			database::begin();
			$user=[
				'name'=>static::$username,
				'fullname'=>static::$username,
				'email'=>static::$username.'@bogus.bogus',
				'password'=>static::$password
			];
			$password=$user['password'];
			$user['password']=password_hash($password, PASSWORD_BCRYPT);
			$cols=implode(',', array_keys($user));
			$values='"'.implode('","', array_values($user)).'"';
			database::query("INSERT INTO user($cols) VALUES($values)");
			$userId=database::getLastInsertId();
			$this->testUser=database::queryGetOne('SELECT * FROM user WHERE id='.$userId);
			$this->testUser['password']=$password;
		} catch (Exception){

		}
	}

	protected function tearDown(): void	{
		database::abort();
		session::destroy();
	}

	/**
	 * @throws ServerException
	 * @throws NotFoundException
	 * @throws BadRequestException
	 */
	public function testIsLoggedIn(){
		session::init(new DummySession());
		self::assertFalse(basesession::isLoggedIn());
		session::set('user', $this->testUser);
		self::assertTrue(basesession::isLoggedIn());
	}

	public function getTestUser(){
		return $this->testUser;
	}

	public static function getCredentials(): array {
		$username=static::$username;
		$password=static::$password;
		return [
			[['expectedMessage'=>'', 'credentials'=>[ 'username'=>$username,'password'=>$password ]]],
			[['expectedMessage'=>'Username or password incorrect', 'credentials'=>[ 'username'=>$username,'password'=>$password.'bogus' ]]],
			[['expectedMessage'=>'Username or password incorrect', 'credentials'=>[ 'username'=>$username.'bogus','password'=>$password ]]],
			[['expectedMessage'=>'Username and password are required', 'credentials'=>[ 'username'=>$username,'password'=>'' ]]],
			[['expectedMessage'=>'Username and password are required', 'credentials'=>[ 'username'=>$username ]]],
			[['expectedMessage'=>'Username and password are required', 'credentials'=>[ 'username'=>'','password'=>$password ]]],
			[['expectedMessage'=>'Username and password are required', 'credentials'=>[ 'password'=>$password ]]],
			[['expectedMessage'=>'Username and password are required', 'credentials'=>[  ]]],
		];
	}

	/**
	 * @dataProvider getCredentials
	 * @param $credentials
	 * @throws BadRequestException
	 * @throws NotFoundException
	 * @throws AuthorizationRequiredException
	 * @throws ServerException
	 */
	public function testLogIn($credentials){
		//Try all the credentials in turn
		basesession::init(new DummySession());
		self::assertFalse(basesession::isLoggedIn());
		$shouldSucceed=empty($credentials['expectedMessage']);
		if(!$shouldSucceed){
			self::expectException('AuthorizationRequiredException');
			self::expectExceptionMessage($credentials['expectedMessage']);
		}
		basesession::login($credentials['credentials']);
	}

	/**
	 * @throws BadRequestException
	 * @throws NotFoundException
	 * @throws ServerException
	 * @throws AuthorizationRequiredException
	 */
	public function testMultipleBadLogins(){
		basesession::init(new DummySession());
		self::assertFalse(basesession::isLoggedIn());

		//Try multiple bad logins
		for($i=0;$i<3;$i++){
			try {
				basesession::login(['username'=>'bogus'.time(), 'password'=>'bogus']);
			} catch (AuthorizationRequiredException){
				self::assertFalse(basesession::isLoggedIn());
			}
		}
		self::assertFalse(basesession::isLoggedIn());

		//Immediately try to log in with good credentials - should be locked out
		try {
			basesession::login(['username'=>static::$username, 'password'=>static::$password]);
		} catch (AuthorizationRequiredException $e){
			self::assertMatchesRegularExpression('/^Try again in \d seconds$/', $e->getMessage());
			self::assertFalse(basesession::isLoggedIn());
		}
		self::assertFalse(basesession::isLoggedIn(), 'Login timeout was not enforced');

		//Wait 5 seconds and try again - should work
		sleep(5);
		basesession::login(['username'=>static::$username, 'password'=>static::$password]);
		self::assertTrue(basesession::isLoggedIn(), 'User could not log in after login timeout should have expired');

	}

	/**
	 * @throws ServerException
	 * @throws BadRequestException
	 * @throws NotFoundException
	 */
	public function testSetShowArchivedProjects() {
		session::init(new DummySession());
		session::becomeAdmin();
		$name='project'.time();
		project::create([
			'name'=>$name,
			'description'=>$name,
			'owner'=>user::getFirstAdmin()['id'],
			'isarchived'=>1
		]);
		session::setUserToSession(user::getFirstAdmin()['name']);
		session::revokeAdmin();
		self::assertFalse(session::get('showArchivedProjects'));
		self::assertNull(project::getByName($name));
		basesession::setShowArchivedProjects(true);
		self::assertTrue(session::get('showArchivedProjects'));
		self::assertNotNull(project::getByName($name));
		basesession::setShowArchivedProjects(false);
		session::revokeAdmin();
		self::assertFalse(session::get('showArchivedProjects'));
		self::assertNull(project::getByName($name));
	}

	/**
	 * Inactive users should not appear in searches (getAll/getByProperty/ies) if
	 * session::get('showInactiveUsers') is false. They should be returned by
	 * getById|Name as they may own records that are visible.
	 * @throws ForbiddenException
	 * @throws BadRequestException
	 * @throws ServerException
	 * @throws NotFoundException
	 */
	public function testSetShowInactiveUsers() {
		session::init(new DummySession());
		session::becomeAdmin();
		$name='user_'.time();
		user::create([
			'name'=>$name,
			'fullname'=>$name,
			'email'=>$name.'@bogus.bogus',
			'isactive'=>0
		]);
		self::assertFalse(session::get('showInactiveUsers'));
		self::assertNotNull(user::getByName($name));
		self::assertNull(user::getByProperty('email',$name.'@bogus.bogus'));
		basesession::setShowInactiveUsers(true);
		self::assertTrue(session::get('showInactiveUsers'));
		self::assertNotNull(user::getByName($name));
		self::assertNotNull(user::getByProperty('email',$name.'@bogus.bogus'));
		basesession::setShowInactiveUsers(false);
		self::assertFalse(session::get('showInactiveUsers'));
		self::assertNotNull(user::getByName($name));
		self::assertNull(user::getByProperty('email',$name.'@bogus.bogus'));
	}

	/**
	 * @throws ServerException
	 * @throws BadRequestException
	 * @throws NotFoundException
	 */
	public function testIsAdmin(){
		basesession::init(new DummySession());
		self::assertFalse(basesession::isAdmin());
		basesession::set('isAdmin', true);
		self::assertTrue(basesession::isAdmin());
	}

	/**
	 * @throws NotFoundException
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testBecomeAdmin(){
		basesession::init(new DummySession());
		self::assertFalse(basesession::isAdmin());
		basesession::becomeAdmin();
		self::assertTrue(basesession::isAdmin());
	}

	/**
	 * @throws BadRequestException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
	public function testRevokeAdmin(){
		basesession::init(new DummySession());
		self::assertFalse(basesession::isAdmin());
		basesession::becomeAdmin();
		self::assertTrue(basesession::isAdmin());
		basesession::revokeAdmin();
		self::assertFalse(basesession::isAdmin());
	}

	/**
	 * @throws BadRequestException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
	public function testRevertAdminWasNotAdmin(){
		basesession::init(new DummySession());
		self::assertFalse(basesession::isAdmin());
		basesession::becomeAdmin();
		self::assertTrue(basesession::isAdmin());
		basesession::revertAdmin();
		self::assertFalse(basesession::isAdmin());
	}

	/**
	 * @throws BadRequestException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
	public function testRevertAdminWasAdmin(){
		basesession::init(new DummySession());
		basesession::set('isAdmin',true);
		self::assertTrue(basesession::isAdmin());
		basesession::becomeAdmin();
		self::assertTrue(basesession::isAdmin());
		basesession::revertAdmin();
		self::assertTrue(basesession::isAdmin());
	}

	/**
	 * @throws ServerException
	 * @throws NotFoundException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testRequireAdmin(){
		basesession::init(new DummySession());
		session::set('isAdmin', true);
		basesession::requireAdmin();
		$this->expectException('ForbiddenException');
		session::set('isAdmin', false);
		basesession::requireAdmin();
	}

}

