<?php

/**
 * This file is part of Gitonomy.
 *
 * (c) Alexandre Salomé <alexandre.salome@gmail.com>
 * (c) Julien DIDIER <genzo.wm@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Gitonomy\Git\Tests;

use Gitonomy\Git\Exception\InvalidArgumentException;
use Gitonomy\Git\Exception\LogicException;

class HooksTest extends AbstractTest
{
    private static $symlinkOnWindows = null;

    /**
     * @beforeClass
     */
    public static function setUpWindows()
    {
        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
            self::$symlinkOnWindows = true;
            $originDir = tempnam(sys_get_temp_dir(), 'sl');
            $targetDir = tempnam(sys_get_temp_dir(), 'sl');
            if (true !== @symlink($originDir, $targetDir)) {
                $report = error_get_last();
                if (is_array($report) && false !== strpos($report['message'], 'error code(1314)')) {
                    self::$symlinkOnWindows = false;
                }
            }
        }
    }

    public function hookPath($repository, $hook)
    {
        return $repository->getGitDir().'/hooks/'.$hook;
    }

    public function touchHook($repository, $hook, $content = '')
    {
        $path = $this->hookPath($repository, $hook);
        file_put_contents($path, $content);

        return $path;
    }

    public function assertHasHook($repository, $hook)
    {
        $file = $this->hookPath($repository, $hook);

        $this->assertTrue($repository->getHooks()->has($hook), "hook $hook in repository");

        $this->assertFileExists($file, "Hook $hook is present");
    }

    public function assertNoHook($repository, $hook)
    {
        $file = $this->hookPath($repository, $hook);

        $this->assertFalse($repository->getHooks()->has($hook), "No hook $hook in repository");

        if (method_exists($this, 'assertFileDoesNotExist')) {
            $this->assertFileDoesNotExist($file, "Hook $hook is not present");
        } else {
            $this->assertFileNotExists($file, "Hook $hook is not present");
        }
    }

    /**
     * @dataProvider provideFoobar
     */
    public function testHas($repository)
    {
        $this->assertNoHook($repository, 'foo');
        $this->touchHook($repository, 'foo');
        $this->assertHasHook($repository, 'foo');
    }

    /**
     * @dataProvider provideFoobar
     */
    public function testGet_InvalidName_ThrowsException($repository)
    {
        $this->expectException(InvalidArgumentException::class);

        $repository->getHooks()->get('foo');
    }

    /**
     * @dataProvider provideFoobar
     */
    public function testGet($repository)
    {
        $this->touchHook($repository, 'foo', 'foobar');

        $this->assertEquals('foobar', $repository->getHooks()->get('foo'));
    }

    /**
     * @dataProvider provideFoobar
     */
    public function testSymlink($repository)
    {
        $this->markAsSkippedIfSymlinkIsMissing();

        $file = $this->touchHook($repository, 'bar', 'barbarbar');
        $repository->getHooks()->setSymlink('foo', $file);

        $this->assertTrue(is_link($this->hookPath($repository, 'foo')), 'foo hook is a symlink');

        $this->assertEquals(
            str_replace('\\', '/', $file),
            str_replace('\\', '/', readlink($this->hookPath($repository, 'foo'))),
            'target of symlink is correct'
        );
    }

    /**
     * @dataProvider provideFoobar
     */
    public function testSymlink_WithExisting_ThrowsLogicException($repository)
    {
        $this->expectException(LogicException::class);

        $this->markAsSkippedIfSymlinkIsMissing();

        $file = $this->hookPath($repository, 'target-symlink');
        $fooFile = $this->hookPath($repository, 'foo');

        file_put_contents($file, 'foobar');
        touch($fooFile);

        $repository->getHooks()->setSymlink('foo', $file);
    }

    /**
     * @dataProvider provideFoobar
     */
    public function testSet($repository)
    {
        $file = $this->hookPath($repository, 'foo');
        $repository->getHooks()->set('foo', 'bar');

        $this->assertEquals('bar', file_get_contents($file), 'Hook content is correct');

        $perms = fileperms($file);
        $this->assertEquals(defined('PHP_WINDOWS_VERSION_BUILD') ? 0666 : 0777, $perms & 0777, 'Hook permissions are correct');
    }

    /**
     * @dataProvider provideFoobar
     */
    public function testSet_Existing_ThrowsLogicException($repository)
    {
        $repository->getHooks()->set('foo', 'bar');

        $this->expectException(LogicException::class);

        $repository->getHooks()->set('foo', 'bar');
    }

    /**
     * @dataProvider provideFoobar
     */
    public function testRemove($repository)
    {
        $file = $this->hookPath($repository, 'foo');
        touch($file);

        $repository->getHooks()->remove('foo');

        if (method_exists($this, 'assertFileDoesNotExist')) {
            $this->assertFileDoesNotExist($file);
        } else {
            $this->assertFileNotExists($file);
        }
    }

    /**
     * @dataProvider provideFoobar
     */
    public function testRemove_NotExisting_ThrowsLogicException($repository)
    {
        $this->expectException(LogicException::class);

        $repository->getHooks()->remove('foo');
    }

    private function markAsSkippedIfSymlinkIsMissing()
    {
        if (!function_exists('symlink')) {
            $this->markTestSkipped('symlink is not supported');
        }

        if (defined('PHP_WINDOWS_VERSION_MAJOR') && false === self::$symlinkOnWindows) {
            $this->markTestSkipped('symlink requires "Create symbolic links" privilege on windows');
        }
    }
}
