<?php

use PHPUnit\Framework\TestCase;

class userTest extends TestCase {

	static $now;

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    protected function setUp():void {
		error_reporting(E_ALL);
		static::$now=time();
        database::connect();
        try {
            session::init(new DummySession());
        } catch (BadRequestException) {
            $this->fail('BadRequestException on session::init');
        } catch (NotFoundException) {
            $this->fail('NotFoundException on session::init');
        } catch (ServerException) {
            $this->fail('ServerException on session::init');
        }
        database::begin();
		session::becomeAdmin();
		/*
		 * Make a supervision hierarchy as follows:
		 * alice > bob > charles > dave > elaine > fred
		 *                                       > gertrude > harry
		 */
		$supervisionChain=['alice','bob','charles','dave','elaine','fred','gertrude','harry'];
		$previousUser=null;
		foreach($supervisionChain as $name){
			$user=user::create([
				'name'=>$name.static::$now,
				'fullname'=>$name,
				'email'=>"$name@bogus.bogus",
			])['created'];
			if($previousUser){
				user::update($user['id'],['supervisorid'=>$previousUser['id']]);
			}
			$previousUser=$user;
		}
		$elaine=user::getByName('elaine'.static::$now);
		$gertrude=user::getByName('gertrude'.static::$now);
		user::update($gertrude['id'], ['supervisorid'=>$elaine['id']]);
		session::revokeAdmin();
    }

	/**
	 * @return void
	 */
	protected function tearDown():void {
        database::abort();
        session::destroy();
    }

	/**
	 * @return void
	 */
	public function testCanCreateAsAdmin(){
        session::becomeAdmin();
        $this->assertTrue(user::canCreate());
    }

	/**
	 * @return void
	 */
    public function testCanCreateAsNonAdmin(){
        $this->assertFalse(user::canCreate());
    }

	/**
	 * @return void
	 */
    public function testCreateAsAdmin(){
        session::becomeAdmin();
        try {
            $created=user::create(array(
                'name'=>'test'.time(),
                'fullname'=>'Test '.time(),
                'email'=>time().'@test.user',
                'password'=>'USELESSPASSWORD'
            ));
            $this->assertNotEmpty($created);
            $this->assertNotEmpty(user::getById($created['created']['id']));
        } catch (ForbiddenException){
            $this->fail('Could not create user as admin (ForbiddenException)');
        } catch (Exception){
            $this->fail('Could not create user as admin (not ForbiddenException)');
        }

    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreateAsNonAdmin(){
        $this->expectException(ForbiddenException::class);
        user::create(array(
                'name'=>'test'.time(),
                'fullname'=>'Test '.time(),
                'email'=>time().'@test.user'
            ));
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testGetById(){
        session::becomeAdmin();
        $username='test'.time();
        $created=user::create(array(
            'name'=>$username,
            'fullname'=>'Test '.time(),
            'email'=>time().'@test.user',
            'password'=>'USELESSPASSWORD'
        ));
        $id=$created['created']['id'];
        //Get by ID as admin
        $user=user::getById($id);
        $this->assertArrayHasKey('fullname', $user);
        $this->assertArrayHasKey('email', $user);
        $this->assertArrayNotHasKey('password', $user);
        //getById as non-admin
        session::revokeAdmin();
        $user=user::getById($id);
        $this->assertArrayHasKey('fullname', $user);
        $this->assertArrayNotHasKey('name', $user);
        $this->assertArrayNotHasKey('email', $user);
        $this->assertArrayNotHasKey('password', $user);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testGetByName(){
        session::becomeAdmin();
        $username='test'.time();
        user::create(array(
            'name'=>$username,
            'fullname'=>'Test '.time(),
            'email'=>time().'@test.user',
            'password'=>'USELESSPASSWORD'
        ));
        //Get by ID as admin
        $user=user::getByName($username);
        $this->assertArrayHasKey('fullname', $user);
        $this->assertArrayHasKey('email', $user);
        $this->assertArrayNotHasKey('password', $user);
        //getById as non-admin
        session::revokeAdmin();
        $user=user::getByName($username);
        $this->assertArrayHasKey('fullname', $user);
        $this->assertArrayNotHasKey('name', $user);
        $this->assertArrayNotHasKey('email', $user);
        $this->assertArrayNotHasKey('password', $user);
    }

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testIsInSupervisionLoopTrue(){
		static::forceSupervisionLoop();
		$dave=user::getByName('dave'.static::$now);
		$isInLoop=user::isInSupervisionLoop($dave['id']);
		self::assertTrue($isInLoop);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testIsInSupervisionLoopFalse(){
		$dave=user::getByName('dave'.static::$now);
		$isInLoop=user::isInSupervisionLoop($dave['id']);
		self::assertFalse($isInLoop);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testGetDirectSupervisor(){
		$alice=user::getByName('alice'.static::$now);
		$bob=user::getByName('bob'.static::$now);
		self::assertNotEmpty($alice);
		self::assertNotEmpty($bob);
		self::assertEmpty($alice['supervisorid']);
		self::assertNotEmpty($bob['supervisorid']);
		$supervisor=user::getDirectSupervisor($bob['id']);
		self::assertNotEmpty($supervisor);
		self::assertEquals($supervisor['id'], $alice['id']);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testGetDirectSupervisorNoneSet(){
		$alice=user::getByName('alice'.static::$now);
		self::assertNotEmpty($alice);
		self::assertEmpty($alice['supervisorid']);
		$supervisor=user::getDirectSupervisor($alice['id']);
		self::assertEmpty($supervisor);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testGetDirectSupervisorIsOwnSupervisor(){
		$bob=user::getByName('bob'.static::$now);
		self::assertNotEmpty($bob);
		self::forceUserIsOwnSupervisor($bob);
		self::expectException('ServerException');
		self::expectExceptionMessageMatches('/^'.baseuser::SUPERVISION_LOOP_DETECTED.'/');
		user::getDirectSupervisor($bob['id']);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testGetDirectSupervisorId(){
		$alice=user::getByName('alice'.static::$now);
		$bob=user::getByName('bob'.static::$now);
		self::assertNotEmpty($alice);
		self::assertNotEmpty($bob);
		self::assertEmpty($alice['supervisorid']);
		self::assertNotEmpty($bob['supervisorid']);
		$supervisorId=user::getDirectSupervisorId($bob['id']);
		self::assertNotEmpty($supervisorId);
		self::assertEquals($supervisorId, $alice['id']);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testGetDirectSupervisorIdNoneSet(){
		$alice=user::getByName('alice'.static::$now);
		self::assertNotEmpty($alice);
		self::assertEmpty($alice['supervisorid']);
		$supervisorId=user::getDirectSupervisorId($alice['id']);
		self::assertNull($supervisorId);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testGetDirectSupervisorIdIsOwnSupervisor(){
		$bob=user::getByName('bob'.static::$now);
		self::assertNotEmpty($bob);
		self::forceUserIsOwnSupervisor($bob);
		self::expectException('ServerException');
		self::expectExceptionMessageMatches('/^'.baseuser::SUPERVISION_LOOP_DETECTED.'/');
		user::getDirectSupervisorId($bob['id']);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testGetAllSupervisors(){
		$gertrude=user::getByName('gertrude'.static::$now);
		$supervisors=user::getAllSupervisors($gertrude['id']);
		self::assertIsArray($supervisors);
		self::assertArrayHasKey('total', $supervisors);
		self::assertArrayHasKey('rows', $supervisors);
		self::assertEquals(5, $supervisors['total']);
		self::assertEquals('elaine|dave|charles|bob|alice', implode('|', array_column($supervisors['rows'], 'fullname')));
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testGetAllSupervisorsNoneSet(){
		$alice=user::getByName('alice'.static::$now);
		$supervisors=user::getAllSupervisors($alice['id']);
		self::assertNull($supervisors);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testGetAllSupervisorsSupervisionLoop(){
		static::forceSupervisionLoop();
		$alice=user::getByName('alice'.static::$now);
		self::expectException('ServerException');
		self::expectExceptionMessageMatches('/^'.baseuser::SUPERVISION_LOOP_DETECTED.'/');
		user::getAllSupervisors($alice['id']);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testGetAllSupervisorIds(){
		$gertrude=user::getByName('gertrude'.static::$now);
		$elaine=user::getByName('elaine'.static::$now);
		$alice=user::getByName('alice'.static::$now);
		$supervisorIds=user::getAllSupervisorIds($gertrude['id']);
		self::assertIsArray($supervisorIds);
		self::assertCount(5, $supervisorIds);
		self::assertEquals($elaine['id'], $supervisorIds[0]);
		self::assertEquals($alice['id'], $supervisorIds[4]);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testGetAllSupervisorIdsNoneSet(){
		$alice=user::getByName('alice'.static::$now);
		$supervisorId=user::getAllSupervisorIds($alice['id']);
		self::assertNull($supervisorId);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public function testGetAllSupervisorIdsSupervisionLoop(){
		static::forceSupervisionLoop();
		$alice=user::getByName('alice'.static::$now);
		self::expectException('ServerException');
		self::expectExceptionMessageMatches('/^'. baseuser::SUPERVISION_LOOP_DETECTED.'/');
		user::getAllSupervisorIds($alice['id']);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 * @throws ForbiddenException
	 */
	public function testGetDirectSupervisees(){
		$elaine=user::getByName('elaine'.static::$now);
		self::assertIsArray($elaine);
		$supervisees=user::getDirectSupervisees($elaine['id']);
		self::assertIsArray($supervisees);
		self::assertArrayHasKey('total', $supervisees);
		self::assertArrayHasKey('rows', $supervisees);
		self::assertEquals(2, $supervisees['total']);
		self::assertCount(2, $supervisees['rows']);
		$names=array_column($supervisees['rows'],'fullname');
		self::assertContains('fred',$names);
		self::assertContains('gertrude',$names);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 * @throws ForbiddenException
	 */
	public function testGetDirectSuperviseesNoneSet(){
		$harry=user::getByName('harry'.static::$now);
		self::assertIsArray($harry);
		$supervisees=user::getDirectSupervisees($harry['id']);
		self::assertNull($supervisees);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 * @throws ForbiddenException
	 */
	public function testGetDirectSuperviseesIsOwnSupervisee(){
		$dave=user::getByName('dave'.static::$now);
		self::assertIsArray($dave);
		self::forceUserIsOwnSupervisor($dave);
		self::expectException('ServerException');
		self::expectExceptionMessageMatches('/^'.baseuser::SUPERVISION_LOOP_DETECTED.'/');
		user::getDirectSupervisees($dave['id']);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 * @throws ForbiddenException
	 */
	public function testGetDirectSuperviseeIds(){
		$elaine=user::getByName('elaine'.static::$now);
		$fred=user::getByName('fred'.static::$now);
		$gertrude=user::getByName('gertrude'.static::$now);
		self::assertIsArray($elaine);
		self::assertIsArray($fred);
		self::assertIsArray($gertrude);
		$superviseeIds=user::getDirectSuperviseeIds($elaine['id']);
		self::assertIsArray($superviseeIds);
		self::assertCount(2, $superviseeIds);
		self::assertContains($fred['id'],$superviseeIds);
		self::assertContains($gertrude['id'],$superviseeIds);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 * @throws ForbiddenException
	 */
	public function testGetDirectSuperviseeIdsNoneSet(){
		$harry=user::getByName('harry'.static::$now);
		self::assertIsArray($harry);
		$supervisees=user::getDirectSupervisees($harry['id']);
		self::assertNull($supervisees);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 * @throws ForbiddenException
	 *
	 */
	public function testGetDirectSuperviseeIdsIsOwnSupervisee(){
		$dave=user::getByName('dave'.static::$now);
		self::assertIsArray($dave);
		self::forceUserIsOwnSupervisor($dave);
		self::expectException('ServerException');
		self::expectExceptionMessageMatches('/^'.baseuser::SUPERVISION_LOOP_DETECTED.'/');
		user::getDirectSuperviseeIds($dave['id']);
	}

	/**
	 * @throws ServerException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetAllSupervisees(){
		$charles=user::getByName('charles'.static::$now);
		$supervisees=user::getAllSupervisees($charles['id']);
		self::assertIsArray($supervisees);
		self::assertArrayHasKey('total', $supervisees);
		self::assertArrayHasKey('rows', $supervisees);
		self::assertCount(1, $supervisees['rows']);
		self::assertEquals('dave', $supervisees['rows'][0]['fullname']);
		$supervisees=$supervisees['rows']['0']['supervisees'];
		self::assertArrayHasKey('total', $supervisees);
		self::assertArrayHasKey('rows', $supervisees);
		self::assertCount(1, $supervisees['rows']);
		self::assertEquals('elaine', $supervisees['rows'][0]['fullname']);
		$supervisees=$supervisees['rows']['0']['supervisees'];
		self::assertArrayHasKey('total', $supervisees);
		self::assertArrayHasKey('rows', $supervisees);
		self::assertCount(2, $supervisees['rows']);
		$superviseeNames=array_column($supervisees['rows'],'fullname');
		self::assertContains('fred',$superviseeNames);
		self::assertContains('gertrude',$superviseeNames);
		$fredIndex=array_search('fred', $superviseeNames);
		$gertrudeIndex=array_search('gertrude', $superviseeNames);
		$fred=$supervisees['rows'][$fredIndex];
		$gertrude=$supervisees['rows'][$gertrudeIndex];
		self::assertArrayNotHasKey('supervisees',$fred);
		$supervisees=$gertrude['supervisees'];
		self::assertIsArray($supervisees);
		self::assertArrayHasKey('total', $supervisees);
		self::assertArrayHasKey('rows', $supervisees);
		self::assertCount(1, $supervisees['rows']);
		$harry=$supervisees['rows'][0];
		self::assertEquals('harry', $harry['fullname']);
		self::assertArrayNotHasKey('supervisees',$harry);
	}

	/**
	 * @throws ServerException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetAllSuperviseesNoneSet(){
		$fred=user::getByName('fred'.static::$now);
		$supervisees=user::getAllSupervisees($fred['id']);
		self::assertNull($supervisees);
	}

	/**
	 * @throws ServerException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetAllSuperviseesSupervisionLoop(){
		self::forceSupervisionLoop();
		$dave=user::getByName('dave'.static::$now);
		self::expectException('ServerException');
		self::expectExceptionMessageMatches('/^'.baseuser::SUPERVISION_LOOP_DETECTED.'/');
		user::getAllSupervisees($dave['id']);
	}

	/**
	 * @throws ServerException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetAllSuperviseeIds(){
		$charles=user::getByName('charles'.static::$now);
		$superviseeIds=user::getAllSuperviseeIds($charles['id']);
		$superviseeNames=[];
		foreach ($superviseeIds as $superviseeId){
			$user=user::getById($superviseeId);
			$superviseeNames[]=$user['fullname'];
		}
		self::assertCount(5, $superviseeIds);
		foreach(['dave','elaine','fred','gertrude','harry'] as $name){
			self::assertContains($name, $superviseeNames);
		}
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 * @throws ForbiddenException
	 */
	public function testGetAllSuperviseeIdsNoneSet(){
		$fred=user::getByName('fred'.static::$now);
		$supervisees=user::getAllSuperviseeIds($fred['id']);
		self::assertNull($supervisees);
	}

	/**
	 * @return void
	 * @throws BadRequestException
	 * @throws ServerException
	 * @throws ForbiddenException
	 */
	public function testGetAllSuperviseeIdsSupervisionLoop(){
		self::forceSupervisionLoop();
		self::expectException('ServerException');
		self::expectExceptionMessageMatches('/^'.baseuser::SUPERVISION_LOOP_DETECTED.'/');
		$dave=user::getByName('dave'.static::$now);
		user::getAllSuperviseeIds($dave['id']);
	}

	/**
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	private function forceSupervisionLoop(): void {
		$alice=user::getByName('alice'.static::$now);
		$harry=user::getByName('harry'.static::$now);
		database::query(
			'UPDATE user SET supervisorid=:harry WHERE id=:alice',
			[':alice'=>$alice['id'], ':harry'=>$harry['id']]
		);
	}

	/**
	 * @throws ServerException
	 * @throws BadRequestException
	 */
	private function forceUserIsOwnSupervisor($user): void {
		self::assertNotEmpty($user);
		self::assertArrayHasKey('id',$user);
		database::query(
			'UPDATE user set supervisorid=:id1 WHERE id=:id2',
			[':id1'=>$user['id'], ':id2'=>$user['id']]
		);
	}

}