<?php

declare(strict_types=1);

namespace SimpleSAML\Test\Utils;

use InvalidArgumentException;
use org\bovigo\vfs\vfsStream;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use SimpleSAML\Configuration;
use SimpleSAML\Utils\System;

/**
 * Tests for SimpleSAML\Utils\System.
 */
class SystemTest extends TestCase
{
    const ROOTDIRNAME = 'testdir';

    const DEFAULTTEMPDIR = 'tempdir';

    /** @var \org\bovigo\vfs\vfsStreamDirectory */
    protected $root;

    /** @var string */
    protected $root_directory;


    /**
     * @return void
     */
    public function setUp(): void
    {
        $this->root = vfsStream::setup(
            self::ROOTDIRNAME,
            null,
            [
                self::DEFAULTTEMPDIR => [],
            ]
        );
        $this->root_directory = vfsStream::url(self::ROOTDIRNAME);
    }


    /**
     * @covers \SimpleSAML\Utils\System::getOS
     * @test
     * @return void
     */
    public function testGetOSBasic()
    {
        $res = System::getOS();

        $this->assertIsInt($res);
    }


    /**
     * @covers \SimpleSAML\Utils\System::resolvePath
     * @test
     * @return void
     */
    public function testResolvePathRemoveTrailingSlashes()
    {
        $base = "/base////";
        $path = "test";

        $res = System::resolvePath($path, $base);
        $expected = "/base/test";

        $this->assertEquals($expected, $res);
    }


    /**
     * @covers \SimpleSAML\Utils\System::resolvePath
     * @test
     * @return void
     */
    public function testResolvePathPreferAbsolutePathToBase()
    {
        $base = "/base/";
        $path = "/test";

        $res = System::resolvePath($path, $base);
        $expected = "/test";

        $this->assertEquals($expected, $res);
    }


    /**
     * @covers \SimpleSAML\Utils\System::resolvePath
     * @test
     * @return void
     */
    public function testResolvePathCurDirPath()
    {
        $base = "/base/";
        $path = "/test/.";

        $res = System::resolvePath($path, $base);
        $expected = "/test";

        $this->assertEquals($expected, $res);
    }


    /**
     * @covers \SimpleSAML\Utils\System::resolvePath
     * @test
     * @return void
     */
    public function testResolvePathParentPath()
    {
        $base = "/base/";
        $path = "/test/child/..";

        $res = System::resolvePath($path, $base);
        $expected = "/test";

        $this->assertEquals($expected, $res);
    }


    /**
     * @covers \SimpleSAML\Utils\System::resolvePath
     * @test
     * @return void
     */
    public function testResolvePathAllowsStreamWrappers()
    {
        $base = '/base/';
        $path = 'vfs://simplesaml';

        $res = System::resolvePath($path, $base);
        $expected = $path;

        $this->assertEquals($expected, $res);
    }


    /**
     * @covers \SimpleSAML\Utils\System::resolvePath
     * @test
     * @return void
     */
    public function testResolvePathAllowsAwsS3StreamWrappers()
    {
        $base = '/base/';
        $path = 's3://bucket-name/key-name';

        $res = System::resolvePath($path, $base);
        $expected = $path;

        $this->assertEquals($expected, $res);
    }


    /**
     * @covers \SimpleSAML\Utils\System::writeFile
     * @test
     * @return void
     */
    public function testWriteFileBasic()
    {
        $tempdir = $this->root_directory . DIRECTORY_SEPARATOR . self::DEFAULTTEMPDIR;
        $config = $this->setConfigurationTempDir($tempdir);

        $filename = $this->root_directory . DIRECTORY_SEPARATOR . 'test';

        System::writeFile($filename, '');

        $this->assertFileExists($filename);

        $this->clearInstance($config, Configuration::class);
    }


    /**
     * @covers \SimpleSAML\Utils\System::writeFile
     * @test
     * @return void
     */
    public function testWriteFileContents()
    {
        $tempdir = $this->root_directory . DIRECTORY_SEPARATOR . self::DEFAULTTEMPDIR;
        $config = $this->setConfigurationTempDir($tempdir);

        $filename = $this->root_directory . DIRECTORY_SEPARATOR . 'test';
        $contents = 'TEST';

        System::writeFile($filename, $contents);

        $res = file_get_contents($filename);
        $expected = $contents;

        $this->assertEquals($expected, $res);

        $this->clearInstance($config, Configuration::class);
    }


    /**
     * @covers \SimpleSAML\Utils\System::writeFile
     * @test
     * @return void
     */
    public function testWriteFileMode()
    {
        $tempdir = $this->root_directory . DIRECTORY_SEPARATOR . self::DEFAULTTEMPDIR;
        $config = $this->setConfigurationTempDir($tempdir);

        $filename = $this->root_directory . DIRECTORY_SEPARATOR . 'test';
        $mode = 0666;

        System::writeFile($filename, '', $mode);

        $res = $this->root->getChild('test')->getPermissions();
        $expected = $mode;

        $this->assertEquals($expected, $res);

        $this->clearInstance($config, Configuration::class);
    }


    /**
     * @covers \SimpleSAML\Utils\System::getTempDir
     * @test
     * @return void
     */
    public function testGetTempDirBasic()
    {
        $tempdir = $this->root_directory . DIRECTORY_SEPARATOR . self::DEFAULTTEMPDIR;
        $config = $this->setConfigurationTempDir($tempdir);

        $res = System::getTempDir();
        $expected = $tempdir;

        $this->assertEquals($expected, $res);
        $this->assertFileExists($res);

        $this->clearInstance($config, Configuration::class);
    }


    /**
     * @covers \SimpleSAML\Utils\System::getTempDir
     * @test
     * @return void
     */
    public function testGetTempDirNonExistant()
    {
        $tempdir = $this->root_directory . DIRECTORY_SEPARATOR . 'nonexistant';
        $config = $this->setConfigurationTempDir($tempdir);

        $res = System::getTempDir();
        $expected = $tempdir;

        $this->assertEquals($expected, $res);
        $this->assertFileExists($res);

        $this->clearInstance($config, Configuration::class);
    }


    /**
     * @covers \SimpleSAML\Utils\System::getTempDir
     * @test
     * @return void
     */
    public function testGetTempDirBadPermissions()
    {
        $tempdir = $this->root_directory . DIRECTORY_SEPARATOR . self::DEFAULTTEMPDIR;
        $config = $this->setConfigurationTempDir($tempdir);

        chmod($tempdir, 0440);

        $this->expectException(\SimpleSAML\Error\Exception::class);
        System::getTempDir();

        $this->clearInstance($config, Configuration::class);
    }


    /**
     * @param string $directory
     * @return \SimpleSAML\Configuration
     */
    private function setConfigurationTempDir(string $directory): Configuration
    {
        $config = Configuration::loadFromArray([
            'tempdir' => $directory,
        ], '[ARRAY]', 'simplesaml');

        return $config;
    }


    /**
     * @param \SimpleSAML\Configuration $service
     * @param class-string $className
     * @return void
     */
    protected function clearInstance(Configuration $service, $className)
    {
        $reflectedClass = new ReflectionClass($className);
        $reflectedInstance = $reflectedClass->getProperty('instance');
        $reflectedInstance->setAccessible(true);
        $reflectedInstance->setValue($service, null);
        $reflectedInstance->setAccessible(false);
    }
}