# gemato: Profile behavior tests
# (c) 2017-2022 Michał Górny
# SPDX-License-Identifier: GPL-2.0-or-later

import itertools
import os
import os.path

import pytest

import gemato.cli
from gemato.profile import (
    EbuildRepositoryProfile,
    BackwardsCompatEbuildRepositoryProfile,
    )
from gemato.recursiveloader import ManifestRecursiveLoader


DIRS = [
    'dev-foo',
    'dev-foo/bar',
    'dev-foo/bar/files',
    'eclass',
    'eclass/tests',
    'licenses',
    'metadata',
    'metadata/dtd',
    'metadata/glsa',
    'metadata/install-qa-check.d',
    'metadata/md5-cache',
    'metadata/md5-cache/dev-foo',
    'metadata/news',
    'metadata/news/2020-01-01-foo',
    'metadata/xml-schema',
    'profiles',
    'profiles/arch',
    'profiles/arch/foo',
    'profiles/desc',
    'profiles/updates',
]
PACKAGE_MANIFESTS = ['dev-foo/bar/Manifest']
EXPECTED_MANIFESTS = [
    'dev-foo/Manifest',
    'eclass/Manifest',
    'licenses/Manifest',
    'metadata/Manifest',
    'metadata/dtd/Manifest',
    'metadata/glsa/Manifest',
    'metadata/md5-cache/Manifest',
    'metadata/md5-cache/dev-foo/Manifest',
    'metadata/news/Manifest',
    'metadata/xml-schema/Manifest',
    'profiles/Manifest',
]
EXPECTED_TYPES = {
    EbuildRepositoryProfile: {
        'header.txt': 'DATA',
        'skel.ebuild': 'DATA',
        'skel.metadata.xml': 'DATA',
        'dev-foo/metadata.xml': 'DATA',
        'dev-foo/bar/bar-1.ebuild': 'DATA',
        'dev-foo/bar/metadata.xml': 'DATA',
        'dev-foo/bar/files/test.patch': 'DATA',
        'eclass/foo.eclass': 'DATA',
        'eclass/tests/foo.sh': 'DATA',
        'licenses/foo': 'DATA',
        'metadata/layout.conf': 'DATA',
        'metadata/projects.xml': 'DATA',
        'metadata/pkg_desc_index': 'DATA',
        'metadata/dtd/foo.dtd': 'DATA',
        'metadata/glsa/glsa-202001-01.xml': 'DATA',
        'metadata/install-qa-check.d/50foo': 'DATA',
        'metadata/md5-cache/dev-foo/bar-1': 'DATA',
        'metadata/news/2020-01-01-foo/2020-01-01-foo.en.txt': 'DATA',
        'metadata/news/2020-01-01-foo/2020-01-01-foo.en.txt.asc': 'DATA',
        'metadata/xml-schema/foo.xsd': 'DATA',
        'profiles/arch.desc': 'DATA',
        'profiles/categories': 'DATA',
        'profiles/eapi': 'DATA',
        'profiles/info_pkgs': 'DATA',
        'profiles/info_vars': 'DATA',
        'profiles/license_groups': 'DATA',
        'profiles/package.mask': 'DATA',
        'profiles/profiles.desc': 'DATA',
        'profiles/repo_name': 'DATA',
        'profiles/thirdpartymirrors': 'DATA',
        'profiles/use.desc': 'DATA',
        'profiles/use.local.desc': 'DATA',
        'profiles/arch/foo/eapi': 'DATA',
        'profiles/arch/foo/parent': 'DATA',
        'profiles/desc/foo.desc': 'DATA',
        'profiles/updates/1Q-2020': 'DATA',
    },
}
FILES = list(EXPECTED_TYPES[EbuildRepositoryProfile]) + [
    'metadata/timestamp',
    'metadata/timestamp.chk',
    'metadata/timestamp.commit',
    'metadata/timestamp.x',
]
EXPECTED_IGNORE = [
    'distfiles',
    'local',
    'lost+found',
    'packages',
    'metadata/timestamp',
    'metadata/timestamp.chk',
    'metadata/timestamp.commit',
    'metadata/timestamp.x',
    'metadata/dtd/timestamp.chk',
    'metadata/dtd/timestamp.commit',
    'metadata/glsa/timestamp.chk',
    'metadata/glsa/timestamp.commit',
    'metadata/news/timestamp.chk',
    'metadata/news/timestamp.commit',
    'metadata/xml-schema/timestamp.chk',
    'metadata/xml-schema/timestamp.commit',
]

EXPECTED_TYPES[BackwardsCompatEbuildRepositoryProfile] = (
    dict(EXPECTED_TYPES[EbuildRepositoryProfile]))
EXPECTED_TYPES[BackwardsCompatEbuildRepositoryProfile].update({
    'dev-foo/bar/bar-1.ebuild': 'EBUILD',
    'dev-foo/bar/metadata.xml': 'MISC',
    'dev-foo/bar/files/test.patch': 'AUX',
})

PKG_MANIFEST_SUFFIX = {
    EbuildRepositoryProfile: '.gz',
    BackwardsCompatEbuildRepositoryProfile: '',
}

ALL_PROFILES = [
    EbuildRepositoryProfile,
    BackwardsCompatEbuildRepositoryProfile,
]


@pytest.fixture
def test_repo(tmp_path_factory):
    tmp_path = tmp_path_factory.mktemp('profile-')
    for d in DIRS:
        os.mkdir(tmp_path / d)
    for f in FILES:
        with open(tmp_path / f, 'w'):
            pass
    yield tmp_path


@pytest.mark.parametrize(
    'profile,path,expected',
    [(profile, path, EXPECTED_TYPES[profile][path])
     for profile in ALL_PROFILES
     for path in EXPECTED_TYPES[profile]])
def test_get_entry_type_for_path(profile, path, expected):
    assert profile().get_entry_type_for_path(path) == expected


def make_entry_match(manifest_loader, path):
    entry = manifest_loader.find_path_entry(path)
    if entry is not None:
        if entry.tag == 'MANIFEST':
            return (entry.tag,
                    os.path.exists(
                        os.path.join(manifest_loader.root_directory,
                                     path)))
        elif entry.tag == 'AUX':
            return (entry.tag, entry.path, entry.aux_path)
        return (entry.tag,)


def make_expect_match(path, entry_type, manifests_exist=True):
    if entry_type == 'MANIFEST':
        return (entry_type, manifests_exist)
    elif entry_type == 'AUX':
        return (entry_type,
                os.path.join('files', os.path.basename(path)),
                os.path.basename(path))
    return (entry_type,)


@pytest.mark.parametrize('profile', ALL_PROFILES)
def test_update_entries_for_directory(test_repo, profile):
    m = ManifestRecursiveLoader(
        test_repo / 'Manifest',
        hashes=['SHA256', 'SHA512'],
        allow_create=True,
        profile=profile())
    m.update_entries_for_directory('')

    expected = dict(
        [(path, make_expect_match(path, entry_type))
         for path, entry_type in EXPECTED_TYPES[profile].items()] +
        [(path, make_expect_match(path, 'MANIFEST', manifests_exist=False))
         for path in EXPECTED_MANIFESTS + PACKAGE_MANIFESTS] +
        [(path, make_expect_match(path, 'IGNORE'))
         for path in EXPECTED_IGNORE])
    found = {path: make_entry_match(m, path) for path in expected}
    assert found == expected

    m.save_manifests()
    manifests_expected = dict(
        itertools.chain(
            ((path + '.gz', make_expect_match(path, 'MANIFEST'))
             for path in EXPECTED_MANIFESTS),
            ((path + PKG_MANIFEST_SUFFIX[profile],
             make_expect_match(path, 'MANIFEST'))
             for path in PACKAGE_MANIFESTS)))
    manifests_found = {path: make_entry_match(m, path)
                       for path in manifests_expected}
    assert manifests_found == manifests_expected

    m.assert_directory_verifies('')


@pytest.mark.parametrize('profile', ALL_PROFILES)
def test_cli_update(test_repo, profile):
    assert gemato.cli.main(['gemato', 'create',
                            '--profile', profile.name,
                            str(test_repo)]) == 0

    m = ManifestRecursiveLoader(test_repo / 'Manifest')
    expected = dict(
        [(path, make_expect_match(path, entry_type))
         for path, entry_type in EXPECTED_TYPES[profile].items()] +
        [(path + '.gz', make_expect_match(path, 'MANIFEST'))
         for path in EXPECTED_MANIFESTS] +
        [(path + PKG_MANIFEST_SUFFIX[profile],
          make_expect_match(path, 'MANIFEST'))
         for path in PACKAGE_MANIFESTS] +
        [(path, make_expect_match(path, 'IGNORE'))
         for path in EXPECTED_IGNORE])
    found = {path: make_entry_match(m, path) for path in expected}
    assert found == expected

    assert gemato.cli.main(['gemato', 'verify', str(test_repo)]) == 0


@pytest.mark.parametrize('profile', ALL_PROFILES)
def test_set_loader_options(test_repo, profile):
    m = ManifestRecursiveLoader(
        test_repo / 'Manifest',
        profile=profile(),
        allow_create=True)
    assert m.hashes is not None
    assert m.sort
    assert m.compress_watermark is not None
    assert m.compress_format is not None


@pytest.mark.parametrize('profile', ALL_PROFILES)
def test_regression_top_level_ignore_in_all_manifests(test_repo, profile):
    """Regression test for IGNORE wrongly applying to all Manifests"""
    m = ManifestRecursiveLoader(
        test_repo / 'Manifest',
        hashes=['SHA256', 'SHA512'],
        allow_create=True,
        profile=profile())
    m.update_entries_for_directory('')

    expected = {
        'distfiles': ('IGNORE',),
        'dev-foo/Manifest': ('MANIFEST', False),
        'dev-foo/distfiles': None,
    }
    found = {path: make_entry_match(m, path) for path in expected}
    assert found == expected
