<?php

use PHPUnit\Framework\TestCase;

class apikeyTest extends TestCase {

    protected function setUp():void {
        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();
    }

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

    /**
     * @throws ServerException
     * @throws BadRequestException
     * @throws ForbiddenException
     */
    public function testGetAll(){
        session::becomeAdmin();
        $keys=apikey::getAll();
        self::assertArrayHasKey('rows',$keys);
        self::assertArrayHasKey('total',$keys);
        self::assertArrayHasKey('scopes',$keys);
        $key=$keys['rows'][0];
        self::assertArrayHasKey('scope', $key);
        self::assertArrayHasKey('iplowest', $key);
        self::assertArrayHasKey('iphighest', $key);
        self::assertArrayHasKey('loglevel', $key);
        self::assertTrue(validator::isValid($key['iplowest'], validator::IP4ADDRESS));
        self::assertTrue(validator::isValid($key['iphighest'], validator::IP4ADDRESS));
    }

    /**
     * @throws ServerException
     * @throws BadRequestException
     */
    public function testGetAllAsNonAdmin(){
        self::expectException('ForbiddenException');
        apikey::getAll();
    }

    /**
     * @return void
     */
    public function testGetScopes(){
        $classNames = [];
        $deviceClasses = __DIR__.'/../../../classes/device';
        $dir = dir($deviceClasses);
        $filename = $dir->read();
        while (!empty($filename)) {
            if (preg_match('/.*\.class\.php$/', $filename)) {
                $className = str_replace('.class.php', '', $filename);
                if ('Device' !== $className) {
                    $classNames[] = $className;
                }

            }
            $filename = $dir->read();
        }
        $scopes = apikey::getScopes();
        self::assertSameSize($classNames, $scopes);
        foreach($classNames as $className){
            self::assertContains($className, $scopes);
        }

    }

    /**
     * @throws ServerException
     */
    public function testGetKeyAndHash(){
        $result=apikey::getKeyAndHash();
        self::assertArrayHasKey('key', $result);
        self::assertArrayHasKey('hash', $result);
        self::assertTrue(password_verify($result['key'],$result['hash']));
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws ServerException
     */
    public function testGetByIdAsAdmin(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0],
            'loglevel'=>Log::LOGLEVEL_WARN
        ])['created'];
        $key=apikey::getById($result['id']);
        self::assertArrayHasKey('scope', $key);
        self::assertArrayHasKey('iplowest', $key);
        self::assertArrayHasKey('iphighest', $key);
        self::assertArrayHasKey('loglevel', $key);
        self::assertTrue(validator::isValid($key['iplowest'], validator::IP4ADDRESS));
        self::assertTrue(validator::isValid($key['iphighest'], validator::IP4ADDRESS));
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws ServerException
     */
    public function testGetByIdAsNonAdmin(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0],
            'loglevel'=>Log::LOGLEVEL_WARN
        ])['created'];
        session::revokeAdmin();
        self::expectException('ForbiddenException');
        apikey::getById($result['id']);

    }

    /**
     * @throws ForbiddenException
     */
    public function testGetByNameAsAdmin(){
        session::becomeAdmin();
        self::expectException('ServerException');
        apikey::getByName('test');
    }

    /**
     * @throws ServerException
     */
    public function testGetByNameAsNonAdmin(){
        self::expectException('ForbiddenException');
        apikey::getByName('test');
    }

    /**
     * @throws ForbiddenException
     */
    public function testGetByPropertyAsAdmin(){
        session::becomeAdmin();
        self::expectException('ServerException');
        apikey::getByProperty('test','test');
    }

    /**
     * @throws ServerException
     */
    public function testGetByPropertyAsNonAdmin(){
        self::expectException('ForbiddenException');
        apikey::getByProperty('test','test');
    }

    /**
     * @throws ForbiddenException
     */
    public function testGetByPropertiesAsAdmin(){
        session::becomeAdmin();
        self::expectException('ServerException');
        apikey::getByProperties(['test1'=>'test','test2'=>'test']);
    }

    /**
     * @throws ServerException
     */
    public function testGetByPropertiesAsNonAdmin(){
        self::expectException('ForbiddenException');
        apikey::getByProperties(['test1'=>'test','test2'=>'test']);
    }

    /**
     * @throws ServerException
     * @throws BadRequestException
     */
    public function testCreateAsNonAdmin(){
        self::expectException('ForbiddenException');
        apikey::create();
    }

    /**
     * @throws ServerException
     * @throws BadRequestException
     * @throws ForbiddenException
     */
    public function testCreateBlank(){
        session::becomeAdmin();
        self::expectException('BadRequestException');
        apikey::create();
    }

    /**
     * @throws ServerException
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws AuthorizationRequiredException
     */
    public function testCreateMinimal(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0]
        ])['created'];
        self::assertArrayHasKey('key', $result);
        self::assertArrayHasKey('scope', $result);
        self::assertArrayHasKey('iplowest', $result);
        self::assertArrayHasKey('iphighest', $result);
        self::assertArrayHasKey('loglevel', $result);
        self::assertEquals('0.0.0.0', $result['iplowest']);
        self::assertEquals('255.255.255.255', $result['iphighest']);
        self::assertEquals($scopes[0], $result['scope']);
        self::assertEquals(Log::LOGLEVEL_INFO, $result['loglevel']);
        self::assertTrue(apikey::isValid($result['key'], $result['scope']));
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws AuthorizationRequiredException
     * @throws ServerException
     */
    public function testCreateFull(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0],
            'iplowest'=>'12.34.56.78',
            'iphighest'=>'23.45.67.89',
            'loglevel'=>Log::LOGLEVEL_WARN
        ])['created'];
        self::assertArrayHasKey('key', $result);
        self::assertArrayHasKey('scope', $result);
        self::assertArrayHasKey('iplowest', $result);
        self::assertArrayHasKey('iphighest', $result);
        self::assertEquals('12.34.56.78', $result['iplowest']);
        self::assertEquals('23.45.67.89', $result['iphighest']);
        self::assertEquals($scopes[0], $result['scope']);
        self::assertTrue(apikey::isValid($result['key'], $result['scope']));
    }

    /**
     * @throws ServerException
     * @throws ForbiddenException
     */
    public function testCreateIpsInverted(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        self::expectException('BadRequestException');
        self::expectExceptionMessage(apikey::LOWEST_IP_CANNOT_BE_HIGHER_THAN_HIGHEST_IP);
        apikey::create([
            'scope'=>$scopes[0],
            'iplowest'=>'23.45.67.89',
            'iphighest'=>'12.34.56.78'
        ]);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws ServerException
     */
    public function testIpIsValidForKey(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0],
            'iplowest'=>'12.34.56.78',
            'iphighest'=>'23.45.67.89',
        ])['created'];
        $ips=[
            '0.0.0.0'=>false,
            '12.34.56.77'=>false,
            '12.34.56.78'=>true,
            '12.34.56.79'=>true,
            '23.45.67.88'=>true,
            '23.45.67.89'=>true,
            '23.45.67.90'=>false,
            '255.255.255.255'=>false,

        ];
        foreach ($ips as $ip=>$isValid) {
            self::assertEquals($isValid, apikey::ipIsValidForKey($ip, $result), "Ip/key validation failed for $ip");
        }
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws ServerException
     * @throws AuthorizationRequiredException
     */
    public function testValidate(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0],
            'iplowest'=>'12.34.56.78',
            'iphighest'=>'23.45.67.89',
        ])['created'];
        self::assertTrue(apikey::isValid($result['key'], $scopes[0]));
    }

	/**
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws ServerException
	 * @throws AuthorizationRequiredException
	 */
	public function testValidateBadScope(){
		session::becomeAdmin();
		$scopes=apikey::getScopes();
		$result=apikey::create([
			'scope'=>$scopes[0],
			'iplowest'=>'12.34.56.78',
			'iphighest'=>'23.45.67.89',
		])['created'];
		self::expectException('AuthorizationRequiredException');
		self::expectExceptionMessage(apikey::SCOPE_NOT_AUTHORISED_FOR_THIS_KEY);
		apikey::validate($result['key'], 'BadScope');
	}
	/**
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws ServerException
	 * @throws AuthorizationRequiredException
	 */
	public function testIsValidBadScope(){
		session::becomeAdmin();
		$scopes=apikey::getScopes();
		$result=apikey::create([
			'scope'=>$scopes[0],
			'iplowest'=>'12.34.56.78',
			'iphighest'=>'23.45.67.89',
		])['created'];
		self::assertFalse(apikey::isValid($result['key'], 'BadScope'));
	}

	/**
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws ServerException
	 * @throws AuthorizationRequiredException
	 */
	public function testValidateBadKey(){
		session::becomeAdmin();
		$scopes=apikey::getScopes();
		$result=apikey::create([
			'scope'=>$scopes[0],
			'iplowest'=>'12.34.56.78',
			'iphighest'=>'23.45.67.89',
		])['created'];
		self::expectException('AuthorizationRequiredException');
		self::expectExceptionMessage(apikey::INVALID_API_KEY);
		apikey::validate($result['key'].'BadKey', $scopes[0]);
	}

    /**
     * @throws ForbiddenException
     * @throws BadRequestException
     * @throws AuthorizationRequiredException
     * @throws ServerException
     * @throws NotFoundException
     */
    public function testDelete(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0]
        ])['created'];
        $id=$result['id'];
        self::assertTrue(apikey::isValid($result['key'], $result['scope']));
        apikey::delete($id);
		self::expectException('AuthorizationRequiredException');
		self::expectExceptionMessage(apikey::INVALID_API_KEY);
        apikey::validate($result['key'], $result['scope']);
    }

    /**
     * @throws BadRequestException
     * @throws NotFoundException
     * @throws ForbiddenException
     * @throws ServerException
     */
    public function testDeleteAsNonAdmin(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0]
        ])['created'];
        $id=$result['id'];
        session::revokeAdmin();
        self::expectException('ForbiddenException');
        apikey::delete($id);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws AuthorizationRequiredException
     * @throws ServerException
     */
    public function testGetLogLevel(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0],
            'loglevel'=>Log::LOGLEVEL_WARN
        ])['created'];
        $key=$result['key'];
        $logLevel=apikey::getLogLevel($key);
        self::assertEquals(Log::LOGLEVEL_WARN, $logLevel);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws AuthorizationRequiredException
     * @throws ServerException
     */
    public function testGetLogLevelBadKey(){
        self::expectException('ServerException');
        apikey::getLogLevel('BadKey');
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws AuthorizationRequiredException
     * @throws ServerException
     */
    public function testRegenerateKey(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0],
            'loglevel'=>Log::LOGLEVEL_WARN
        ])['created'];
        $oldKey=$result['key'];
        $newKey=apikey::regenerateKey($result['id'])['key'];
        self::assertFalse(apikey::isValid($oldKey, $scopes[0]));
        self::assertTrue(apikey::isValid($newKey, $scopes[0]));
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws ServerException
     * @throws NotFoundException
     */
    public function testUpdate(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0],
            'loglevel'=>Log::LOGLEVEL_WARN,
            'iplowest'=>'12.34.56.78',
            'iphighest'=>'23.45.67.89'
        ])['created'];
        apikey::update($result['id'],[
            'loglevel'=>Log::LOGLEVEL_ERROR,
            'iplowest'=>'34.56.78.90',
            'iphighest'=>'45.67.89.100'
        ]);
        $key=apikey::getById($result['id']);
        self::assertArrayHasKey('scope',$key);
        self::assertArrayHasKey('loglevel',$key);
        self::assertArrayHasKey('iplowest',$key);
        self::assertArrayHasKey('iphighest',$key);
        self::assertEquals($scopes[0],$key['scope']);
        self::assertEquals(Log::LOGLEVEL_ERROR, $key['loglevel']);
        self::assertEquals('34.56.78.90', $key['iplowest']);
        self::assertEquals('45.67.89.100', $key['iphighest']);
    }

    /**
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws BadRequestException
     * @throws ServerException
     */
    public function testUpdateChangeScope(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0],
            'loglevel'=>Log::LOGLEVEL_WARN,
            'iplowest'=>'12.34.56.78',
            'iphighest'=>'23.45.67.89'
        ])['created'];
        self::expectException('BadRequestException');
        self::expectExceptionMessage(apikey::CANNOT_CHANGE_API_KEY_SCOPE_AFTER_CREATION);
        apikey::update($result['id'], ['scope'=>$scopes[1]]);
    }

    /**
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws BadRequestException
     * @throws ServerException
     * @throws AuthorizationRequiredException
     */
    public function testUpdateChangeKeyHash(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0],
            'loglevel'=>Log::LOGLEVEL_WARN,
            'iplowest'=>'12.34.56.78',
            'iphighest'=>'23.45.67.89'
        ])['created'];
        self::assertTrue(apikey::isValid($result['key'],$scopes[0]));
        $updated=apikey::update($result['id'],['keyhash'=>'Test'])['updated'];
        self::assertFalse(apikey::isValid($result['key'],$scopes[0]));
        self::assertTrue(apikey::isValid($updated['key'],$scopes[0]));
    }

    /**
     * @throws ForbiddenException
     * @throws BadRequestException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testUpdateChangeKeyHashAndOtherFields(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0],
            'loglevel'=>Log::LOGLEVEL_WARN,
            'iplowest'=>'12.34.56.78',
            'iphighest'=>'23.45.67.89'
        ])['created'];
        self::expectException('BadRequestException');
        self::expectExceptionMessage(apikey::CANNOT_CHANGE_OTHER_FIELDS_WHILE_REGENERATING_THE_KEY);
        apikey::update($result['id'],['keyhash'=>'Test','iplowest'=>'0.0.0.0'])['updated'];
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testUpdateAsNonAdmin(){
        session::becomeAdmin();
        $scopes=apikey::getScopes();
        $result=apikey::create([
            'scope'=>$scopes[0],
            'loglevel'=>Log::LOGLEVEL_WARN,
            'iplowest'=>'12.34.56.78',
            'iphighest'=>'23.45.67.89'
        ])['created'];
        session::revokeAdmin();
        self::expectException('ForbiddenException');
        apikey::update($result['id'],[
            'loglevel'=>Log::LOGLEVEL_ERROR,
            'iplowest'=>'34.56.78.90',
            'iphighest'=>'45.67.89.100'
        ]);
    }

}