<?php

use PHPUnit\Framework\TestCase;

class plateTest extends TestCase {

	public int $now;
	public array $projectOwner;
	public array $plateOwner;
	public array $otherUser;
	public array $project1;
	public array $group1;
	public array $plate1;
	public array $plate2;

	/**
	 * @throws NotFoundException
	 * @throws AuthorizationRequiredException
	 * @throws ServerException
	 * @throws ForbiddenException
	 * @throws BadRequestException
	 */
	protected function setUp(): void {
		$now=time();
		$this->now=$now;
		database::connect();
		database::begin();
		session::init(new DummySession());
		session::becomeAdmin();
		$admin=baseuser::getFirstAdmin();
		session::setUserToSession($admin['name']);
		//scoring system
		$scoringSystem=crystalscoringsystem::create([
			'name'=>"score$now"
		])['created'];
		//plate type
		$plateType=platetype::create([
			'projectid'=>baseproject::getSharedProjectId(),
			'name'=>"pt$now",
			'rows'=>12,
			'cols'=>8,
			'subs'=>3,
			'dropmapping'=>'12,3R'
		])['created'];
		//User 1 will own the project
		$this->projectOwner=user::create([
			'name'=>"user1_$now",
			'fullname'=>"user1_$now",
			'email'=>"user1_$now@bogus.bogus"
		])['created'];
		//User 2 will own the plates and have permissions on the project
		$this->plateOwner=user::create([
			'name'=>"user2_$now",
			'fullname'=>"user2_$now",
			'email'=>"user2_$now@bogus.bogus"
		])['created'];
		//User 3 will not have permission to see the project (unless made admin)
		$this->otherUser=user::create([
			'name'=>"user3_$now",
			'fullname'=>"user3_$now",
			'email'=>"user3_$now@bogus.bogus"
		])['created'];
		//Create the project
		$this->project1=baseproject::create([
			'name'=>"project1_$now",
			'description'=>"test $now",
			'owner'=>$this->projectOwner['id']
		])['created'];
		//Create the plates
		$this->plate1=plate::create([
			'name'=>"plate1_$now",
			'platetypeid'=>$plateType['id'],
			'projectid'=>baseproject::getDefaultProjectId(),
			'crystalscoringsystemid'=>$scoringSystem['id'],
			'ownerid'=>$this->plateOwner['id']
		])['created'];
		$this->plate2=plate::create([
			'name'=>"plate2_$now",
			'platetypeid'=>$plateType['id'],
			'projectid'=>$this->project1['id'],
			'crystalscoringsystemid'=>$scoringSystem['id'],
			'ownerid'=>$this->plateOwner['id']
		])['created'];
		//Create the usergroup
		database::query('INSERT INTO usergroup(name) VALUES("group1_'.$now.'")');
		$groupId=database::getLastInsertId();
		$this->group1=baseusergroup::getById($groupId);
		//Add user 1 to the group
		database::query('INSERT INTO groupmembership(userid,usergroupid) VALUES('.$this->plateOwner['id'].','.$groupId.')');
		//Give the group all permissions on the project
		foreach (['read','create','update','delete'] as $type){
			permission::create([
				'projectid'=>$this->project1['id'],
				'usergroupid'=>$groupId,
				'type'=>$type
			]);
		}
		session::revokeAdmin();
	}

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

	/**
	 * @throws NotFoundException
	 * @throws AuthorizationRequiredException
	 * @throws ServerException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetByIdAsNonGroupMember() {
		$plate1Id=$this->plate1['id'];
		$plate2Id=$this->plate2['id'];
		$projectId=$this->project1['id'];

		//Non-group-member cannot see plate, regardless of archiving
		basesession::setUserToSession($this->otherUser['name']);

		$plate=plate::getById($plate1Id);
		self::assertNull($plate,'Unauthorised user could getById plate in default project');
		$plate=plate::getById($plate2Id);
		self::assertNull($plate,'Unauthorised user could getById plate');
		basesession::becomeAdmin();
		baseproject::archive($projectId);
		basesession::revokeAdmin();
		$plate=plate::getById($plate2Id);
		self::assertNull($plate, 'Unauthorised user could getById plate in archived project');
	}

	/**
	 * @throws ServerException
	 * @throws NotFoundException
	 * @throws AuthorizationRequiredException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetByIdAsAdmin() {
		$plate1Id=$this->plate1['id'];
		$plate2Id=$this->plate2['id'];
		$projectId=$this->project1['id'];
		$plate1Name=$this->plate1['name'];
		$plate2Name=$this->plate2['name'];
		basesession::setUserToSession($this->otherUser['name']);
		basesession::becomeAdmin();

		$plate=plate::getById($plate1Id);
		self::assertNotNull($plate,'Admin could not getById plate in default project');
		self::assertEquals($plate1Name,$plate['name']);
		$plate=plate::getById($plate2Id);
		self::assertNotNull($plate,'Admin could not getById plate');
		self::assertEquals($plate2Name,$plate['name']);

		baseproject::archive($projectId);
		basesession::becomeAdmin();
		$plate=plate::getById($plate2Id);
		self::assertNotNull($plate,'Admin could not getById plate in archived project');
		self::assertEquals($plate2Name,$plate['name']);
	}

	/**
	 * @throws ServerException
	 * @throws NotFoundException
	 * @throws AuthorizationRequiredException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetByIdAsPlateOwner() {
		//Plate owner can see both plates, regardless of archiving
		$plate1Id=$this->plate1['id'];
		$plate2Id=$this->plate2['id'];
		$projectId=$this->project1['id'];
		$plate1Name=$this->plate1['name'];
		$plate2Name=$this->plate2['name'];
		basesession::setUserToSession($this->plateOwner['name']);

		$plate=plate::getById($plate1Id);
		self::assertNotNull($plate,'Plate owner could not getById plate in default project');
		self::assertEquals($plate1Name,$plate['name']);
		$plate=plate::getById($plate2Id);
		self::assertNotNull($plate,'Plate owner could not getById plate');
		self::assertEquals($plate2Name,$plate['name']);

		basesession::becomeAdmin();
		baseproject::archive($projectId);
		basesession::revokeAdmin();
		$plate=plate::getById($plate2Id);
		self::assertNotNull($plate,'Plate owner could not getById plate in archived project');
		self::assertEquals($plate2Name,$plate['name']);
	}

	/**
	 * @throws NotFoundException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws AuthorizationRequiredException
	 * @throws ServerException
	 */
	public function testGetByIdAsProjectOwner() {
		//Project owner can see plates in own project but not default, regardless of archiving
		$plate1Id=$this->plate1['id'];
		$plate2Id=$this->plate2['id'];
		$projectId=$this->project1['id'];
		$plate2Name=$this->plate2['name'];
		basesession::setUserToSession($this->projectOwner['name']);

		$plate=plate::getById($plate1Id);
		self::assertNull($plate,'Unauthorised user could getById plate in default project');
		$plate=plate::getById($plate2Id);
		self::assertNotNull($plate,'Project owner could not getById plate');
		self::assertEquals($plate2Name,$plate['name']);

		basesession::becomeAdmin();
		baseproject::archive($projectId);
		basesession::revokeAdmin();
		$plate=plate::getById($plate2Id);
		self::assertNotNull($plate,'Project owner could not getById plate in archived project');
		self::assertEquals($plate2Name,$plate['name']);

	}

	/**
	 * @throws NotFoundException
	 * @throws AuthorizationRequiredException
	 * @throws ServerException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetByNameAsNonGroupMember() {
		$plate1Id=$this->plate1['id'];
		$plate2Id=$this->plate2['id'];
		$projectId=$this->project1['id'];
		$plate1Name=$this->plate1['name'];
		$plate2Name=$this->plate2['name'];

		//Non-group-member cannot see plate, regardless of archiving
		basesession::setUserToSession($this->otherUser['name']);

		$plate=plate::getByName($plate1Name);
		self::assertNull($plate,'Unauthorised user could getByName plate in default project');
		$plate=plate::getByName($plate2Name);
		self::assertNull($plate,'Unauthorised user could getByName plate');
		basesession::becomeAdmin();
		baseproject::archive($projectId);
		basesession::revokeAdmin();
		$plate=plate::getByName($plate2Name);
		self::assertNull($plate, 'Unauthorised user could getByName plate in archived project');
	}

	/**
	 * @throws ServerException
	 * @throws NotFoundException
	 * @throws AuthorizationRequiredException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetByNameAsAdmin() {
		$plate1Id=$this->plate1['id'];
		$plate2Id=$this->plate2['id'];
		$projectId=$this->project1['id'];
		$plate1Name=$this->plate1['name'];
		$plate2Name=$this->plate2['name'];
		basesession::setUserToSession($this->otherUser['name']);
		basesession::becomeAdmin();

		$plate=plate::getByName($plate1Name);
		self::assertNotNull($plate,'Admin could not getByName plate in default project');
		self::assertEquals($plate1Id,$plate['id']);
		$plate=plate::getByName($plate2Name);
		self::assertNotNull($plate,'Admin could not getByName plate');
		self::assertEquals($plate2Name,$plate['name']);

		baseproject::archive($projectId);
		basesession::becomeAdmin();
		$plate=plate::getByName($plate2Name);
		self::assertNotNull($plate,'Admin could not getByName plate in archived project');
		self::assertEquals($plate2Name,$plate['name']);
	}

	/**
	 * @throws ServerException
	 * @throws NotFoundException
	 * @throws AuthorizationRequiredException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetByNameAsPlateOwner() {
		//Plate owner can see both plates, regardless of archiving
		$plate1Id=$this->plate1['id'];
		$plate2Id=$this->plate2['id'];
		$projectId=$this->project1['id'];
		$plate1Name=$this->plate1['name'];
		$plate2Name=$this->plate2['name'];

		basesession::setUserToSession($this->plateOwner['name']);

		$plate=plate::getByName($plate1Name);
		self::assertNotNull($plate,'Plate owner could not getByName plate in default project');
		self::assertEquals($plate1Id,$plate['id']);
		$plate=plate::getByName($plate2Name);
		self::assertNotNull($plate,'Plate owner could not getByName plate');
		self::assertEquals($plate2Name,$plate['name']);

		basesession::becomeAdmin();
		baseproject::archive($projectId);
		basesession::revokeAdmin();
		$plate=plate::getByName($plate2Name);
		self::assertNotNull($plate,'Plate owner could not getByName plate in archived project');
		self::assertEquals($plate2Id,$plate['id']);
	}

	/**
	 * @throws NotFoundException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws AuthorizationRequiredException
	 * @throws ServerException
	 */
	public function testGetByNameAsProjectOwner() {
		//Project owner can see plates in own project but not default, regardless of archiving
		$plate1Id=$this->plate1['id'];
		$plate2Id=$this->plate2['id'];
		$projectId=$this->project1['id'];
		$plate1Name=$this->plate1['name'];
		$plate2Name=$this->plate2['name'];
		basesession::setUserToSession($this->projectOwner['name']);

		$plate=plate::getByName($plate1Name);
		self::assertNull($plate,'Unauthorised user could getByName plate in default project');
		$plate=plate::getByName($plate2Name);
		self::assertNotNull($plate,'Project owner could not getByName plate');
		self::assertEquals($plate2Id,$plate['id']);

		basesession::becomeAdmin();
		baseproject::archive($projectId);
		basesession::revokeAdmin();
		$plate=plate::getByName($plate2Name);
		self::assertNotNull($plate,'Project owner could not getByName plate in archived project');
		self::assertEquals($plate2Id,$plate['id']);

	}

	/**
	 * @throws NotFoundException
	 * @throws AuthorizationRequiredException
	 * @throws ServerException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetByPropertyAsNonGroupMember() {
		$projectId=$this->project1['id'];
		$plate1Name=$this->plate1['name'];
		$plate2Name=$this->plate2['name'];

		//Non-group-member cannot see plate, regardless of archiving
		basesession::setUserToSession($this->otherUser['name']);

		$plate=plate::getByProperty('name',$plate1Name);
		self::assertNull($plate,'Unauthorised user could getByProperty plate in default project');
		$plate=plate::getByProperty('name',$plate2Name);
		self::assertNull($plate,'Unauthorised user could getByProperty plate');
		basesession::becomeAdmin();
		baseproject::archive($projectId);
		basesession::revokeAdmin();
		$plate=plate::getByProperty('name',$plate2Name);
		self::assertNull($plate, 'Unauthorised user could getByProperty plate in archived project');
	}

	/**
	 * @throws ServerException
	 * @throws NotFoundException
	 * @throws AuthorizationRequiredException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetByPropertyAsAdmin() {
		$plate1Id=$this->plate1['id'];
		$plate2Id=$this->plate2['id'];
		$projectId=$this->project1['id'];
		$plate1Name=$this->plate1['name'];
		$plate2Name=$this->plate2['name'];
		basesession::setUserToSession($this->otherUser['name']);
		basesession::becomeAdmin();

		$plate=plate::getByProperty('name',$plate1Name);
		self::assertNotNull($plate,'Admin could not getByProperty plate in default project');
		self::assertArrayHasKey('total',$plate);
		self::assertArrayHasKey('rows',$plate);
		self::assertCount(1, $plate['rows']);
		self::assertEquals(1,$plate['total']);
		self::assertEquals($plate1Id,$plate['rows'][0]['id']);
		$plate=plate::getByProperty('name',$plate2Name);
		self::assertNotNull($plate,'Admin could not getByProperty plate');
		self::assertArrayHasKey('total',$plate);
		self::assertArrayHasKey('rows',$plate);
		self::assertCount(1, $plate['rows']);
		self::assertEquals(1,$plate['total']);
		self::assertEquals($plate2Id,$plate['rows'][0]['id']);

		baseproject::archive($projectId);
		basesession::becomeAdmin();
		$plate=plate::getByProperty('name',$plate2Name);
		self::assertNull($plate,'Admin could getByProperty plate in archived project');
	}

	/**
	 * @throws ServerException
	 * @throws NotFoundException
	 * @throws AuthorizationRequiredException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetByPropertyAsPlateOwner() {
		//Plate owner can see both plates, regardless of archiving
		$plate1Id=$this->plate1['id'];
		$plate2Id=$this->plate2['id'];
		$projectId=$this->project1['id'];
		$plate1Name=$this->plate1['name'];
		$plate2Name=$this->plate2['name'];

		basesession::setUserToSession($this->plateOwner['name']);

		$plate=plate::getByProperty('name',$plate1Name);
		self::assertNotNull($plate,'Plate owner could not getByProperty plate in default project');
		self::assertArrayHasKey('total',$plate);
		self::assertArrayHasKey('rows',$plate);
		self::assertCount(1, $plate['rows']);
		self::assertEquals(1,$plate['total']);
		self::assertEquals($plate1Id,$plate['rows'][0]['id']);
		$plate=plate::getByProperty('name',$plate2Name);
		self::assertNotNull($plate,'Plate owner could not getByProperty plate');
		self::assertArrayHasKey('total',$plate);
		self::assertArrayHasKey('rows',$plate);
		self::assertCount(1, $plate['rows']);
		self::assertEquals(1,$plate['total']);
		self::assertEquals($plate2Id,$plate['rows'][0]['id']);

		basesession::becomeAdmin();
		baseproject::archive($projectId);
		basesession::revokeAdmin();
		$plate=plate::getByProperty('name',$plate2Name);
		self::assertNull($plate,'Plate owner could getByProperty plate in archived project');
	}

	/**
	 * @throws NotFoundException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws AuthorizationRequiredException
	 * @throws ServerException
	 */
	public function testGetByPropertyAsProjectOwner() {
		//Project owner can see plates in own project but not default, regardless of archiving
		$plate2Id=$this->plate2['id'];
		$projectId=$this->project1['id'];
		$plate2Name=$this->plate2['name'];
		basesession::setUserToSession($this->projectOwner['name']);

		$plate=plate::getByProperty('name',$plate2Name);
		self::assertNotNull($plate,'Project owner could not getByProperty plate');
		self::assertArrayHasKey('total',$plate);
		self::assertArrayHasKey('rows',$plate);
		self::assertCount(1, $plate['rows']);
		self::assertEquals(1,$plate['total']);
		self::assertEquals($plate2Id,$plate['rows'][0]['id']);

		basesession::becomeAdmin();
		baseproject::archive($projectId);
		basesession::revokeAdmin();
		$plate=plate::getByProperty('name',$plate2Name);
		self::assertNull($plate,'Project owner could getByProperty plate in archived project');
	}
	/**
	 * @throws NotFoundException
	 * @throws AuthorizationRequiredException
	 * @throws ServerException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetByPropertiesAsNonGroupMember() {
		$projectId=$this->project1['id'];
		$plate1Name=$this->plate1['name'];
		$plate2Name=$this->plate2['name'];

		//Non-group-member cannot see plate, regardless of archiving
		basesession::setUserToSession($this->otherUser['name']);

		$plate=plate::getByProperties(['name'=>$plate1Name]);
		self::assertNull($plate,'Unauthorised user could getByProperties plate in default project');
		$plate=plate::getByProperties(['name'=>$plate2Name]);
		self::assertNull($plate,'Unauthorised user could getByProperties plate');
		basesession::becomeAdmin();
		baseproject::archive($projectId);
		basesession::revokeAdmin();
		$plate=plate::getByProperties(['name'=>$plate2Name]);
		self::assertNull($plate, 'Unauthorised user could getByProperties plate in archived project');
	}

	/**
	 * @throws ServerException
	 * @throws NotFoundException
	 * @throws AuthorizationRequiredException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetByPropertiesAsAdmin() {
		$plate1Id=$this->plate1['id'];
		$plate2Id=$this->plate2['id'];
		$projectId=$this->project1['id'];
		$plate1Name=$this->plate1['name'];
		$plate2Name=$this->plate2['name'];
		basesession::setUserToSession($this->otherUser['name']);
		basesession::becomeAdmin();

		$plate=plate::getByProperties(['name'=>$plate1Name]);
		self::assertNotNull($plate,'Admin could not getByProperties plate in default project');
		self::assertArrayHasKey('total',$plate);
		self::assertArrayHasKey('rows',$plate);
		self::assertCount(1, $plate['rows']);
		self::assertEquals(1,$plate['total']);
		self::assertEquals($plate1Id,$plate['rows'][0]['id']);
		$plate=plate::getByProperties(['name'=>$plate2Name]);
		self::assertNotNull($plate,'Admin could not getByProperties plate');
		self::assertArrayHasKey('total',$plate);
		self::assertArrayHasKey('rows',$plate);
		self::assertCount(1, $plate['rows']);
		self::assertEquals(1,$plate['total']);
		self::assertEquals($plate2Id,$plate['rows'][0]['id']);

		baseproject::archive($projectId);
		basesession::becomeAdmin();
		$plate=plate::getByProperties(['name'=>$plate2Name]);
		self::assertNull($plate,'Admin could getByProperties plate in archived project');
	}

	/**
	 * @throws ServerException
	 * @throws NotFoundException
	 * @throws AuthorizationRequiredException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public function testGetByPropertiesAsPlateOwner() {
		//Plate owner can see both plates, regardless of archiving
		$plate1Id=$this->plate1['id'];
		$plate2Id=$this->plate2['id'];
		$projectId=$this->project1['id'];
		$plate1Name=$this->plate1['name'];
		$plate2Name=$this->plate2['name'];

		basesession::setUserToSession($this->plateOwner['name']);

		$plate=plate::getByProperties(['name'=>$plate1Name]);
		self::assertNotNull($plate,'Plate owner could not getByProperties plate in default project');
		self::assertArrayHasKey('total',$plate);
		self::assertArrayHasKey('rows',$plate);
		self::assertCount(1, $plate['rows']);
		self::assertEquals(1,$plate['total']);
		self::assertEquals($plate1Id,$plate['rows'][0]['id']);
		$plate=plate::getByProperties(['name'=>$plate2Name]);
		self::assertNotNull($plate,'Plate owner could not getByProperties plate');
		self::assertArrayHasKey('total',$plate);
		self::assertArrayHasKey('rows',$plate);
		self::assertCount(1, $plate['rows']);
		self::assertEquals(1,$plate['total']);
		self::assertEquals($plate2Id,$plate['rows'][0]['id']);

		basesession::becomeAdmin();
		baseproject::archive($projectId);
		basesession::revokeAdmin();
		$plate=plate::getByProperties(['name'=>$plate2Name]);
		self::assertNull($plate,'Plate owner could getByProperties plate in archived project');
	}

	/**
	 * @throws NotFoundException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws AuthorizationRequiredException
	 * @throws ServerException
	 */
	public function testGetByPropertiesAsProjectOwner() {
		//Project owner can see plates in own project but not default, regardless of archiving
		$plate2Id=$this->plate2['id'];
		$projectId=$this->project1['id'];
		$plate2Name=$this->plate2['name'];
		basesession::setUserToSession($this->projectOwner['name']);

		$plate=plate::getByProperties(['name'=>$plate2Name]);
		self::assertNotNull($plate,'Project owner could not getByProperties plate');
		self::assertArrayHasKey('total',$plate);
		self::assertArrayHasKey('rows',$plate);
		self::assertCount(1, $plate['rows']);
		self::assertEquals(1,$plate['total']);
		self::assertEquals($plate2Id,$plate['rows'][0]['id']);

		basesession::becomeAdmin();
		baseproject::archive($projectId);
		basesession::revokeAdmin();
		$plate=plate::getByProperties(['name'=>$plate2Name]);
		self::assertNull($plate,'Project owner could getByProperties plate in archived project');
	}


}