import copy
from pathlib import Path

import pytest
from rpmlint.lint import Lint
from rpmlint.spellcheck import ENCHANT

from Testing import (
    get_tested_path, HAS_CHECKBASHISMS, HAS_DASH, HAS_ENGLISH_DICTIONARY, HAS_RPMDB,
    TEST_CONFIG
)

TEST_RPMLINTRC = get_tested_path('configs/testing2-rpmlintrc')

options_preset = {
    'config': TEST_CONFIG,
    'verbose': False,
    'strict': False,
    'permissive': False,
    'print_config': False,
    'explain': '',
    'rpmfile': '',
    'rpmlintrc': [],
    'installed': '',
    'time_report': False,
    'profile': False,
    'ignore_unused_rpmlintrc': False,
    'checks': None,
    'mini_mode': False
}

basic_tests = [
    'AlternativesCheck',
    'AppDataCheck',
    'BinariesCheck',
    'BuildRootAndDateCheck',
    'ConfigFilesCheck',
    'DBusPolicyCheck',
    'DuplicatesCheck',
    'DocCheck',
    'ErlangCheck',
    'FHSCheck',
    'FilesCheck',
    'IconSizesCheck',
    'I18NCheck',
    'LibraryDependencyCheck',
    'LogrotateCheck',
    'MenuCheck',
    'MenuXDGCheck',
    'MixedOwnershipCheck',
    'PkgConfigCheck',
    'PostCheck',
    'PythonCheck',
    'SignatureCheck',
    'SourceCheck',
    'SpecCheck',
    'TagsCheck',
    'ZipCheck',
    'ZyppSyntaxCheck',
]


def get_options(additional_options):
    options = copy.deepcopy(options_preset)
    return {**options, **additional_options}


def _remove_except_zip(dictionary):
    """
    In order to not lie in coverage redux the test run on the
    tests to just ZipCheck which has full coverage
    """
    return {'ZipCheck': dictionary['ZipCheck']}


def test_cases_loading():
    linter = Lint(options_preset)
    assert list(linter.checks.keys()) == basic_tests


def test_configoutput(capsys):
    additional_options = {
        'print_config': True,
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()
    assert out
    assert 'Vendor = "Fedora Project"' in out
    assert 're.compile' not in out
    assert not err


def test_time_report(capsys):
    additional_options = {
        'time_report': True,
    }
    options = {**options_preset, **additional_options}
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()
    assert out
    assert 'Duration' in out
    assert 'TOTAL' in out


def test_explain_unknown(capsys):
    message = ['bullcrap']
    additional_options = {
        'explain': message,
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()
    assert 'bullcrap:\nUnknown message' in out
    assert not err


def test_explain_known(capsys):
    message = ['infopage-not-compressed']
    additional_options = {
        'explain': message,
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()
    assert 'This info page is not compressed' in out
    assert 'Unknown message' not in out
    assert not err


@pytest.mark.parametrize('configs', [
    # Message defined in configs/Fedora/warn-on-functions.toml
    (Path('configs/Fedora/warn-on-functions.toml'), False),
    (Path('configs/Fedora/scoring.toml'), True),
])
def test_explain_known_warn_on_function(capsys, configs):
    extraconfig, unknown = configs
    message = ['crypto-policy-non-compliance-openssl']
    additional_options = {
        'explain': message,
        'config': [extraconfig],
    }
    options = {**options_preset, **additional_options}
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()

    assert ('Unknown message' in out) == unknown
    assert not err


def test_explain_with_unknown(capsys):
    message = ['infopage-not-compressed', 'blablablabla']
    additional_options = {
        'explain': message,
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()
    assert 'This info page is not compressed' in out
    assert 'Unknown message' in out
    assert not err


def test_explain_no_binary_from_cfg(capsys):
    """
    Test that 'explain' option can read updated description from configuration.

    Test 'no-binary' error that is defined in CheckBinaries.toml file by
    default and then it's overridden to the custom values defined in
    'descriptions.config' file.
    """
    additional_options = {
        'config': [get_tested_path('configs/descriptions.config')],
        'explain': ['no-binary']
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()

    # the new string is present and the old one is not
    assert 'A new text for no-binary error.' in out
    assert 'The package should be of the noarch architecture' not in out
    assert not err


def test_explain_non_standard_dir_from_cfg(capsys):
    """
    Test that 'explain' option can read updated description from configuration.

    Test 'non-standard-dir-in-usr' error that is special because the original
    description is not defined in FHSCheck.toml but in FHSCheck.py. Then it's
    supposed to be overridden to the custom values defined in
    'descriptions.config' file.
    """
    additional_options = {
        'config': [get_tested_path('configs/descriptions.config')],
        'explain': ['non-standard-dir-in-usr']
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()

    assert 'A new text for non-standard-dir-in-usr error.' in out
    assert 'Your package is creating a non-standard subdirectory in /usr' not in out
    assert not err


@pytest.mark.skipif(not ENCHANT, reason='Optional dependency pyenchant not install')
@pytest.mark.skipif(not HAS_ENGLISH_DICTIONARY, reason='Missing English dictionary')
@pytest.mark.parametrize('packages', [Path('test/binary/non-fhs-0-0.x86_64.rpm')])
def test_descriptions_from_config(capsys, packages):
    """
    Test that rpmlint updates 'parametrized' descriptions from configuration.

    We test that "parametrized" errors (non-standard-dir-in-usr
    and non-standard-dir-in-var) were overridden by values from
    'descriptions.config' file.
    """
    additional_options = {
        'config': [get_tested_path('configs/descriptions.config')],
        'rpmfile': [packages]
    }
    options_preset['verbose'] = True
    options = get_options(additional_options)
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()

    assert 'A new text for non-standard-dir-in-usr error.' in out
    assert 'A new text for non-standard-dir-in-var error.' in out

    assert 'Your package is creating a non-standard subdirectory in /usr' \
           not in out
    assert 'Your package is creating a non-standard subdirectory in /var' \
           not in out
    assert not err


@pytest.mark.parametrize('packages', [Path('test/source/wrongsrc-0-0.src.rpm')])
def test_run_single(capsys, packages):
    additional_options = {
        'rpmfile': [packages],
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.checks = _remove_except_zip(linter.checks)
    linter.run()
    out, err = capsys.readouterr()
    assert '1 packages and 0 specfiles checked' in out
    assert not err


@pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present')
@pytest.mark.parametrize('packages', [Path('test/source/wrongsrc-0-0.src.rpm')])
def test_run_installed(capsys, packages):
    # load up 1 normal path file and 2 installed packages
    additional_options = {
        'rpmfile': [packages],
        'installed': ['binutils', 'rpm'],
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.checks = _remove_except_zip(linter.checks)
    linter.run()
    out, err = capsys.readouterr()
    assert '3 packages and 0 specfiles checked' in out
    assert not err


@pytest.mark.parametrize('packages', [Path('test/binary/ruby2.5-rubygem-rubyzip-testsuite-1.2.1-0.x86_64.rpm')])
def test_run_strict(capsys, packages):
    """
    Test if we convert warning to error
    """
    additional_options = {
        'rpmfile': [packages],
        'strict': True,
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.checks = _remove_except_zip(linter.checks)
    linter.run()
    out, err = capsys.readouterr()
    assert 'W: unable-to-read-zip' not in out
    assert 'E: unable-to-read-zip' in out
    assert not err


@pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present')
def test_run_installed_not_present(capsys):
    additional_options = {
        'rpmfile': [],
        'installed': ['non-existing-package'],
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.checks = _remove_except_zip(linter.checks)
    linter.run()
    out, err = capsys.readouterr()
    assert '0 packages and 0 specfiles checked' in out
    assert 'there is no installed rpm' in err
    assert 'There are no files to process' in err


@pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present')
def test_run_installed_and_no_files(capsys):
    additional_options = {
        'rpmfile': [],
        'installed': ['rpm'],
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.checks = _remove_except_zip(linter.checks)
    linter.run()
    out, err = capsys.readouterr()
    assert '1 packages and 0 specfiles checked' in out
    assert not err


@pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present')
def test_header_information(capsys):
    additional_options = {
        'rpmfile': [],
        'installed': ['python3-rpm'],
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.checks = _remove_except_zip(linter.checks)
    linter.run()
    out, err = capsys.readouterr()
    assert 'packages: 1' in out


@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed')
@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed')
@pytest.mark.parametrize('packages', [list(Path('test').glob('*/*.rpm'))])
@pytest.mark.parametrize('configs', [list(Path('configs').glob('*/*.toml'))])
@pytest.mark.no_cover
def test_run_full_rpm(capsys, packages, configs):
    number_of_pkgs = len(packages)
    additional_options = {
        'rpmfile': packages,
    }
    options_preset['config'] = configs
    options = get_options(additional_options)
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()
    assert f'{number_of_pkgs} packages and 0 specfiles checked' in out
    # we convert the err as we don't care about errors from missing
    # spellchecking dictionaries -> we have to ignore it
    err_reduced = [a for a in err.split('\n') if not a.startswith('(none): W: unable to load spellchecking dictionary for') and a != '']
    # also we can find out signatures are wrong because of the other distros
    # could've signed it
    err_reduced = [a for a in err_reduced if not a.startswith('Error checking signature of')]
    assert not err_reduced


@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed')
@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed')
@pytest.mark.parametrize('packages', [list(Path('test/spec').glob('*.spec'))])
@pytest.mark.parametrize('configs', [list(Path('configs').glob('*/*.toml'))])
@pytest.mark.no_cover
def test_run_full_specs(capsys, packages, configs):
    number_of_pkgs = len(packages)
    additional_options = {
        'rpmfile': packages,
    }
    options_preset['config'] = configs
    options = get_options(additional_options)
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()
    assert f'0 packages and {number_of_pkgs} specfiles checked' in out
    assert not err


@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed')
@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed')
@pytest.mark.parametrize('packages', [Path('test/spec')])
@pytest.mark.no_cover
def test_run_full_directory(capsys, packages):
    assert packages.is_dir()
    file_list = []
    for item in packages.iterdir():
        if item.is_file():
            file_list.append(item)
    number_of_pkgs = len(file_list)
    additional_options = {
        'rpmfile': [packages],
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()
    assert f'0 packages and {number_of_pkgs} specfiles checked' in out
    assert not err


@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed')
@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed')
def test_run_empty(capsys):
    linter = Lint(options_preset)
    linter.run()
    out, err = capsys.readouterr()
    assert err
    assert '0 packages and 0 specfiles checked; 0 errors, 0 warnings' in out


@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed')
@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed')
@pytest.mark.parametrize('packages', [Path('test/rpmlintrc/single')])
def test_run_rpmlintrc_single_dir(capsys, packages):
    additional_options = {
        'rpmfile': [packages],
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()
    assert not err
    assert 'rpmlintrc:' in out


@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed')
@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed')
@pytest.mark.parametrize('packages', [Path('test/rpmlintrc/multiple')])
def test_run_rpmlintrc_multiple(capsys, packages):
    additional_options = {
        'rpmfile': [packages],
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()
    assert 'rpmlintrc:' in out
    assert 'There are multiple items to be loaded' in err


@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed')
@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed')
@pytest.mark.parametrize('packages', [Path('test/rpmlintrc/single/sample.spec')])
def test_run_rpmlintrc_single_file(capsys, packages):
    additional_options = {
        'rpmfile': [packages],
        'rpmlintrc': [TEST_RPMLINTRC]
    }
    options = get_options(additional_options)
    linter = Lint(options)
    linter.run()
    out, err = capsys.readouterr()
    assert not err
    assert 'rpmlintrc:' in out
    assert 'E: unused-rpmlintrc-filter "I am not used"' in out
    assert 'E: unused-rpmlintrc-filter "She is not used"' not in out
    assert 'no-%build-section' not in out


@pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present')
def test_installed_package(capsys):
    additional_options = {
        'installed': ['bzip2'],
        'permissive': True
    }
    options = {**options_preset, **additional_options}
    linter = Lint(options)
    retcode = linter.run()
    out, err = capsys.readouterr()
    assert '1 packages and 0 specfiles checked' in out
    assert retcode == 0
