import os
import subprocess
import tempfile

import pygit2
from pygit2.enums import ObjectType
import pytest

import gitubuntu.source_builder as target

import gitubuntu.importer as importer
import gitubuntu.git_repository as git_repository
from gitubuntu.test_fixtures import repo


def dsc_path_to_tree(repo, dsc_path, patches_applied=False):
    """Convert a dsc filesystem path to a pygit2.Tree object.

    :param GitUbuntuRepository repo: repository wrapper object
    :param str dsc_path: path to dsc file
    :param bool patches_applied: whether all quilt patches should be
        applied during extraction
    :rtype: pygit2.Tree
    """
    tree_hash = importer.dsc_to_tree_hash(
        repo.raw_repo,
        dsc_path,
        patches_applied,
    )
    return repo.raw_repo.get(tree_hash)


def get_spec_changelog_version(repo, **kwargs):
    """Find the version of a source created with given SourceSpec arguments

    Create a SourceSpec with the given arguments, construct a Source from that
    spec using the source builder, and then extract and return the changelog
    version string from the debian/changelog file created within that source.

    :param GitUbuntuRepository: repository to create it in
    :param **kwargs: arguments to pass to the SourceSpec constructor
    :rtype: str
    :returns: the changelog version string found in debian/changelog
    """
    spec = target.SourceSpec(**kwargs)
    with target.Source(spec) as dsc_path:
        tree_hash = importer.dsc_to_tree_hash(repo.raw_repo, dsc_path)
        changelog = repo.get_changelog_from_treeish(tree_hash)
        return changelog.version


def test_source_is_created():
    with target.Source() as f:
        assert os.path.exists(f)


def test_source_create_with_version(repo):
    version = get_spec_changelog_version(repo, version='3', native=True)
    assert version == '3'


def test_source_create_with_versions(repo):
    source_spec = target.SourceSpec(changelog_versions=['3', '4'], native=True)
    with target.Source(source_spec) as f:
        tree_hash = importer.dsc_to_tree_hash(repo.raw_repo, f)
        changelog = repo.get_changelog_from_treeish(tree_hash)
        assert changelog.all_versions == ['3', '4']
    assert source_spec.version == '3'


@pytest.mark.parametrize('file_contents', [
    {'debian/random': 'Some contents'},
    {
        'debian/random': 'Some contents',
        'debian/subdir/random': 'Some other contents',
    },
])
def test_source_create_with_file_contents(repo, file_contents):
    with target.Source(
        target.SourceSpec(file_contents=file_contents)
    ) as f:
        with tempfile.TemporaryDirectory() as tmpdir:
            subprocess.check_call(
                ['dpkg-source', '-x', f, os.path.join(tmpdir, 'x')],
            )
            for _file in file_contents:
                assert os.path.exists(os.path.join(tmpdir, 'x', _file))
                with open(
                    os.path.join(tmpdir, 'x', _file)
                ) as random_file:
                    assert random_file.read() == file_contents[_file]


@pytest.mark.parametrize('_file', [
    'debian/changelog',
    'debian/control',
    'debian/rules',
])
def test_source_create_fails_with_reserved_file_contents(repo, _file):
    with pytest.raises(ValueError):
        with target.Source(
            target.SourceSpec(file_contents={_file: 'Some contents'})
        ) as f:
            pass


def test_source_create_fails_with_absolute_path(repo):
    with pytest.raises(ValueError):
        with target.Source(
            target.SourceSpec(file_contents={'/root/path': 'Some contents'})
        ) as f:
            pass


def test_source_mutate():
    """mutate kwarg results in a debian/mutate file

    If mutate is used, then a file called 'debian/mutate' should be generated
    with the same contents.
    """
    source = target.Source(target.SourceSpec(mutate='foo'))

    # It is sufficient to just verify spec_files contains what we want here as
    # the spec_files functionality is checked in a separate test already.
    assert source.files.spec_files['debian/mutate'] == 'foo'


@pytest.mark.parametrize('native,expected', [
    (True, b"3.0 (native)\n"),
    (False, b"3.0 (quilt)\n"),
])
def test_source_native_source_format(repo, native, expected):
    with target.Source(target.SourceSpec(native=native)) as dsc_path:
        blob = git_repository.follow_symlinks_to_blob(
            repo.raw_repo,
            dsc_path_to_tree(repo, dsc_path),
            'debian/source/format',
        )
        assert blob.data == expected

def test_source_quilt_no_patches(repo):
    with target.Source(target.SourceSpec()) as dsc_path:
        top = dsc_path_to_tree(repo, dsc_path)
        debian_entry = top['debian']
        assert debian_entry.type in [ObjectType.TREE, 'tree']
        debian = repo.raw_repo.get(debian_entry.id)
        with pytest.raises(KeyError):
            debian['patches']


def test_source_quilt_with_patches(repo):
    spec = target.SourceSpec(has_patches=True)
    with target.Source(spec) as dsc_path:
        top = dsc_path_to_tree(repo, dsc_path)
        expected_files = ['series', 'a', 'b']
        for basename in expected_files:
            assert git_repository.follow_symlinks_to_blob(
                repo.raw_repo,
                top,
                'debian/patches/%s' % basename,
            )
        unexpected_files = ['a', 'b']
        for filename in unexpected_files:
            with pytest.raises(KeyError):
                git_repository.follow_symlinks_to_blob(
                    repo.raw_repo,
                    top,
                    filename,
                )


def test_source_quilt_with_patches_applied(repo):
    spec = target.SourceSpec(has_patches=True)
    with target.Source(spec) as dsc_path:
        top = dsc_path_to_tree(repo, dsc_path, patches_applied=True)
        expected_files = [
            'debian/patches/series',
            'debian/patches/a',
            'debian/patches/b',
            'a',
            'b',
        ]
        for filename in expected_files:
            assert git_repository.follow_symlinks_to_blob(
                repo.raw_repo,
                top,
                filename,
            )


def test_source_version_native_default(repo):
    """The default version string for a native package should not have a
    '-' in it.
    """
    version = get_spec_changelog_version(repo, native=True)
    assert '-' not in version


def test_source_version_non_native_default(repo):
    """The default version string for a non-native package should have a
    '-' in it.
    """
    version = get_spec_changelog_version(repo, native=False)
    assert '-' in version


def test_source_version_native_specific(repo):
    """We should be able to create a native package with a
    native-looking version string.
    """
    version = get_spec_changelog_version(repo, native=True, version='1.0')
    assert version == '1.0'


def test_source_version_non_native_specific(repo):
    """We should be able to create a non-native package with a
    non-native-looking version string.
    """
    version = get_spec_changelog_version(repo, native=False, version='1.0-1')
    assert version == '1.0-1'


def test_source_non_native_version_no_hyphen_raises(repo):
    """Creating a non-native package with a native-looking version
    string should fail.
    """
    with pytest.raises(ValueError):
        get_spec_changelog_version(repo, native=False, version='1')
    with pytest.raises(ValueError):
        get_spec_changelog_version(
            repo,
            native=False,
            changelog_versions=['1-1', '2'],
        )


def test_source_native_version_hyphen_raises(repo):
    """Creating a native package with a non-native-looking version
    string should fail.
    """
    with pytest.raises(ValueError):
        get_spec_changelog_version(repo, native=True, version='1-1')
    with pytest.raises(ValueError):
        get_spec_changelog_version(
            repo,
            native=True,
            changelog_versions=['1', '1-2'],
        )
