<?php

namespace Sso\WebserviceBundle\Tests\Services\PasswordPolicy\History;

use Sso\WebserviceBundle\PasswordCrypt\PasswordCryptRepositoryInterface;
use Sso\WebserviceBundle\Tests\Services\ServicesKernelTestCase;
use Sso\WebserviceBundle\Database\Manager as DatabaseManager;
use Sso\WebserviceBundle\Entity\Webservice\Type\User;
use Sso\WebserviceBundle\Entity\Webservice\Type\PasswordPolicy;
use Sso\WebserviceBundle\Database\Webservice\User as DbUser;
use Sso\WebserviceBundle\Database\Webservice\Manager as WebserviceManager;
use Sso\WebserviceBundle\Database\Webservice\PasswordPolicy as DbPasswordPolicy;
use Sso\WebserviceBundle\Api\PasswordPolicy\History\Service;
use Sso\WebserviceBundle\Api\PasswordPolicy\Policy\Service as PolicyService;
use Sso\WebserviceBundle\Database\Webservice\PasswordHistory;
use Sso\WebserviceBundle\Entity\Webservice\Type\PasswordHistory as PasswordHistoryEntity;

/**
 * Class ServiceTest
 * @package Sso\WebserviceBundle\Tests\Services\PasswordPolicy\History
 */
class ServiceTest extends ServicesKernelTestCase
{
    CONST SERVICETEST_TEST_PASSWORD = 'WubbaLubbaDubdub';
    CONST SERVICETEST_TEST_USERGUID = '00000000-0000-0000-0000-000000000000';

    /**
     * @var PasswordHistoryEntity
     */
    public static $cbPasswordHistoryEntity;

    /**
     * @var PasswordHistoryEntity[]
     */
    public static $cbPasswordHistoryEntitys;

    /**
     * @return void
     */
    public function testPasswordAddAndCleanup()
    {
        $mocks = $this->getMocks();

        $policyService = new PolicyService(
            $mocks['DatabaseManager']
        );

        $historyService = new Service(
            $mocks['DatabaseManager'],
            $policyService,
            50,
            $this->getMockBuilder(PasswordCryptRepositoryInterface::class)->getMock()
        );

        // clean slate, for Justin.... Justin Case xD
        self::$cbPasswordHistoryEntitys = [];

        $historyService->setHistorySize(5);
        $historyService->addPassword(
            self::SERVICETEST_TEST_USERGUID,
            self::SERVICETEST_TEST_PASSWORD,
            'default'
        );

        $this->assertEquals(self::SERVICETEST_TEST_PASSWORD, self::$cbPasswordHistoryEntity->getPassword());
        $this->assertEquals(self::SERVICETEST_TEST_USERGUID, self::$cbPasswordHistoryEntity->getUserGuid());
        $this->assertEquals(2, count(self::$cbPasswordHistoryEntitys));
    }

    /**
     * Create all the mocking jays we need for most of the tests
     *
     * @return array
     */
    private function getMocks()
    {
        $passwordPolicy = new PasswordPolicy();
        $passwordPolicy
            ->setPolicyId('00000000-0000-0000-0000-000000000000')
            ->setRegEx('[^A-Za-z0-9]')
            ->setValidityDuration(60)
            ->setHistorySize(5)
            ->setChangeDelay(10)
            ->setEditDistance(4)
            ->setPolicyDescription('Test Policy');

        $mockDbPasswordPolicy = $this->makeMock(
            DbPasswordPolicy::class,
            [
                'getOne' => $passwordPolicy
            ]
        );

        $mockUser = $this->makeMock(
            User::class,
            [
                'getPasswordPolicy' => '00000000-0000-0000-0000-000000000000'
            ]
        );

        $mockDbUser = $this->makeMock(
            DbUser::class,
            [
                'getUserByGuid' => $mockUser
            ]
        );

        $mockDbHistory = $this->makeMock(
            PasswordHistory::class,
            [
                'newPassword' => new PasswordHistoryEntity(),
                'savePassword' => null,
                'getPasswords' => $this->getSomePasswords(7),
                'deletePassword' => null
            ]
        );

        $mockDbWebservice = $this->makeMock(
            WebserviceManager::class,
            [
                'user' => $mockDbUser,
                'passwordPolicy' => $mockDbPasswordPolicy,
                'passwordHistory' => $mockDbHistory
            ]
        );
        $mockDbHistory
            ->expects($this->any())
            ->method('savePassword')
            ->willReturnCallback(self::class . '::mockDbHistorySavePasswordCB');
        $mockDbHistory
            ->expects($this->any())
            ->method('deletePassword')
            ->willReturnCallback(self::class . '::mockDbHistoryDeletePasswordCB');

        $mockDbManager = $this->makeMock(
            DatabaseManager::class,
            [
                'webservice' => $mockDbWebservice
            ]
        );

        return [
            'DatabaseManager' => $mockDbManager
        ];
    }

    /**
     * Callbacks are fun, callbacks are love <3
     *
     * @param PasswordHistoryEntity $passwordEntity
     */
    public static function mockDbHistorySavePasswordCB(PasswordHistoryEntity $passwordEntity)
    {
        self::$cbPasswordHistoryEntity = $passwordEntity;
    }

    /**
     * @param PasswordHistoryEntity $passwordEntity
     */
    public static function mockDbHistoryDeletePasswordCB(PasswordHistoryEntity $passwordEntity)
    {
        self::$cbPasswordHistoryEntitys[] = $passwordEntity;
    }

    /**
     * @param integer $size
     */
    private function getSomePasswords($size)
    {
        $password = password_hash(self::SERVICETEST_TEST_PASSWORD, PASSWORD_DEFAULT);
        $time = new \DateTime();
        $tmp = new PasswordHistoryEntity();
        $passwords = [];
        for ($i = 1; $i <= $size; $i++) {
            $tmp
                ->setPasswordId('TEST.' . $i)
                ->setUserGuid(self::SERVICETEST_TEST_USERGUID)
                ->setPassword($password)
                ->setAddedAt($time);
            $passwords[] = clone $tmp;
        }

        return $passwords;
    }

    /**
     * Helper function to ease the pain of creating 165382 mocks.... ;-)
     *
     * @param string $class
     * @param array $methodReturns
     * @return \PHPUnit_Framework_MockObject_MockObject
     */
    private function makeMock($class, $methodReturns)
    {
        $methods = [];
        foreach ($methodReturns as $method => $returnValue) {
            $methods[] = $method;
        }

        $mock = $this->getMockBuilder($class)
            ->disableOriginalConstructor()
            ->setMethods($methods)
            ->getMock();

        foreach ($methodReturns as $method => $returnValue) {
            $mock->expects($this->any())
                ->method($method)
                ->willReturn($returnValue);
        }

        return $mock;
    }
}
