from __future__ import unicode_literals

import logging

import cfgv
import pytest

import pre_commit.constants as C
from pre_commit.clientlib import check_type_tag
from pre_commit.clientlib import CONFIG_HOOK_DICT
from pre_commit.clientlib import CONFIG_REPO_DICT
from pre_commit.clientlib import CONFIG_SCHEMA
from pre_commit.clientlib import DEFAULT_LANGUAGE_VERSION
from pre_commit.clientlib import MANIFEST_SCHEMA
from pre_commit.clientlib import MigrateShaToRev
from pre_commit.clientlib import validate_config_main
from pre_commit.clientlib import validate_manifest_main
from testing.fixtures import sample_local_config


def is_valid_according_to_schema(obj, obj_schema):
    try:
        cfgv.validate(obj, obj_schema)
        return True
    except cfgv.ValidationError:
        return False


@pytest.mark.parametrize('value', ('definitely-not-a-tag', 'fiel'))
def test_check_type_tag_failures(value):
    with pytest.raises(cfgv.ValidationError):
        check_type_tag(value)


@pytest.mark.parametrize(
    ('config_obj', 'expected'), (
        (
            {
                'repos': [{
                    'repo': 'git@github.com:pre-commit/pre-commit-hooks',
                    'rev': 'cd74dc150c142c3be70b24eaf0b02cae9d235f37',
                    'hooks': [{'id': 'pyflakes', 'files': '\\.py$'}],
                }],
            },
            True,
        ),
        (
            {
                'repos': [{
                    'repo': 'git@github.com:pre-commit/pre-commit-hooks',
                    'rev': 'cd74dc150c142c3be70b24eaf0b02cae9d235f37',
                    'hooks': [
                        {
                            'id': 'pyflakes',
                            'files': '\\.py$',
                            'args': ['foo', 'bar', 'baz'],
                        },
                    ],
                }],
            },
            True,
        ),
        (
            {
                'repos': [{
                    'repo': 'git@github.com:pre-commit/pre-commit-hooks',
                    'rev': 'cd74dc150c142c3be70b24eaf0b02cae9d235f37',
                    'hooks': [
                        {
                            'id': 'pyflakes',
                            'files': '\\.py$',
                            # Exclude pattern must be a string
                            'exclude': 0,
                            'args': ['foo', 'bar', 'baz'],
                        },
                    ],
                }],
            },
            False,
        ),
    ),
)
def test_config_valid(config_obj, expected):
    ret = is_valid_according_to_schema(config_obj, CONFIG_SCHEMA)
    assert ret is expected


def test_local_hooks_with_rev_fails():
    config_obj = {'repos': [dict(sample_local_config(), rev='foo')]}
    with pytest.raises(cfgv.ValidationError):
        cfgv.validate(config_obj, CONFIG_SCHEMA)


def test_config_with_local_hooks_definition_passes():
    config_obj = {'repos': [sample_local_config()]}
    cfgv.validate(config_obj, CONFIG_SCHEMA)


def test_config_schema_does_not_contain_defaults():
    """Due to the way our merging works, if this schema has any defaults they
    will clobber potentially useful values in the backing manifest. #227
    """
    for item in CONFIG_HOOK_DICT.items:
        assert not isinstance(item, cfgv.Optional)


def test_validate_manifest_main_ok():
    assert not validate_manifest_main(('.pre-commit-hooks.yaml',))


def test_validate_config_main_ok():
    assert not validate_config_main(('.pre-commit-config.yaml',))


def test_validate_config_old_list_format_ok(tmpdir):
    f = tmpdir.join('cfg.yaml')
    f.write('-  {repo: meta, hooks: [{id: identity}]}')
    assert not validate_config_main((f.strpath,))


def test_validate_warn_on_unknown_keys_at_repo_level(tmpdir, caplog):
    f = tmpdir.join('cfg.yaml')
    f.write(
        '-   repo: https://gitlab.com/pycqa/flake8\n'
        '    rev: 3.7.7\n'
        '    hooks:\n'
        '    -   id: flake8\n'
        '    args: [--some-args]\n',
    )
    ret_val = validate_config_main((f.strpath,))
    assert not ret_val
    assert caplog.record_tuples == [
        (
            'pre_commit',
            logging.WARNING,
            'Unexpected config key(s): args',
        ),
    ]


def test_validate_warn_on_unknown_keys_at_top_level(tmpdir, caplog):
    f = tmpdir.join('cfg.yaml')
    f.write(
        'repos:\n'
        '-   repo: https://gitlab.com/pycqa/flake8\n'
        '    rev: 3.7.7\n'
        '    hooks:\n'
        '    -   id: flake8\n'
        'foo:\n'
        '    id: 1.0.0\n',
    )
    ret_val = validate_config_main((f.strpath,))
    assert not ret_val
    assert caplog.record_tuples == [
        (
            'pre_commit',
            logging.WARNING,
            'Unexpected config key(s): foo',
        ),
    ]


@pytest.mark.parametrize('fn', (validate_config_main, validate_manifest_main))
def test_mains_not_ok(tmpdir, fn):
    not_yaml = tmpdir.join('f.notyaml')
    not_yaml.write('{')
    not_schema = tmpdir.join('notconfig.yaml')
    not_schema.write('{}')

    assert fn(('does-not-exist',))
    assert fn((not_yaml.strpath,))
    assert fn((not_schema.strpath,))


@pytest.mark.parametrize(
    ('manifest_obj', 'expected'),
    (
        (
            [{
                'id': 'a',
                'name': 'b',
                'entry': 'c',
                'language': 'python',
                'files': r'\.py$',
            }],
            True,
        ),
        (
            [{
                'id': 'a',
                'name': 'b',
                'entry': 'c',
                'language': 'python',
                'language_version': 'python3.4',
                'files': r'\.py$',
            }],
            True,
        ),
        (
            # A regression in 0.13.5: always_run and files are permissible
            [{
                'id': 'a',
                'name': 'b',
                'entry': 'c',
                'language': 'python',
                'files': '',
                'always_run': True,
            }],
            True,
        ),
    ),
)
def test_valid_manifests(manifest_obj, expected):
    ret = is_valid_according_to_schema(manifest_obj, MANIFEST_SCHEMA)
    assert ret is expected


@pytest.mark.parametrize(
    'dct',
    (
        {'repo': 'local'}, {'repo': 'meta'},
        {'repo': 'wat', 'sha': 'wat'}, {'repo': 'wat', 'rev': 'wat'},
    ),
)
def test_migrate_sha_to_rev_ok(dct):
    MigrateShaToRev().check(dct)


def test_migrate_sha_to_rev_dont_specify_both():
    with pytest.raises(cfgv.ValidationError) as excinfo:
        MigrateShaToRev().check({'repo': 'a', 'sha': 'b', 'rev': 'c'})
    msg, = excinfo.value.args
    assert msg == 'Cannot specify both sha and rev'


@pytest.mark.parametrize(
    'dct',
    (
        {'repo': 'a'},
        {'repo': 'meta', 'sha': 'a'}, {'repo': 'meta', 'rev': 'a'},
    ),
)
def test_migrate_sha_to_rev_conditional_check_failures(dct):
    with pytest.raises(cfgv.ValidationError):
        MigrateShaToRev().check(dct)


def test_migrate_to_sha_apply_default():
    dct = {'repo': 'a', 'sha': 'b'}
    MigrateShaToRev().apply_default(dct)
    assert dct == {'repo': 'a', 'rev': 'b'}


def test_migrate_to_sha_ok():
    dct = {'repo': 'a', 'rev': 'b'}
    MigrateShaToRev().apply_default(dct)
    assert dct == {'repo': 'a', 'rev': 'b'}


@pytest.mark.parametrize(
    'config_repo',
    (
        # i-dont-exist isn't a valid hook
        {'repo': 'meta', 'hooks': [{'id': 'i-dont-exist'}]},
        # invalid to set a language for a meta hook
        {'repo': 'meta', 'hooks': [{'id': 'identity', 'language': 'python'}]},
        # name override must be string
        {'repo': 'meta', 'hooks': [{'id': 'identity', 'name': False}]},
    ),
)
def test_meta_hook_invalid(config_repo):
    with pytest.raises(cfgv.ValidationError):
        cfgv.validate(config_repo, CONFIG_REPO_DICT)


@pytest.mark.parametrize(
    'mapping',
    (
        # invalid language key
        {'pony': '1.0'},
        # not a string for version
        {'python': 3},
    ),
)
def test_default_language_version_invalid(mapping):
    with pytest.raises(cfgv.ValidationError):
        cfgv.validate(mapping, DEFAULT_LANGUAGE_VERSION)


def test_minimum_pre_commit_version_failing():
    with pytest.raises(cfgv.ValidationError) as excinfo:
        cfg = {'repos': [], 'minimum_pre_commit_version': '999'}
        cfgv.validate(cfg, CONFIG_SCHEMA)
    assert str(excinfo.value) == (
        '\n'
        '==> At Config()\n'
        '==> At key: minimum_pre_commit_version\n'
        '=====> pre-commit version 999 is required but version {} is '
        'installed.  Perhaps run `pip install --upgrade pre-commit`.'.format(
            C.VERSION,
        )
    )


def test_minimum_pre_commit_version_passing():
    cfg = {'repos': [], 'minimum_pre_commit_version': '0'}
    cfgv.validate(cfg, CONFIG_SCHEMA)


@pytest.mark.parametrize('schema', (CONFIG_SCHEMA, CONFIG_REPO_DICT))
def test_warn_additional(schema):
    allowed_keys = {item.key for item in schema.items if hasattr(item, 'key')}
    warn_additional, = [
        x for x in schema.items if isinstance(x, cfgv.WarnAdditionalKeys)
    ]
    assert allowed_keys == set(warn_additional.keys)

[((29, 1, 29, 67), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(29, 25, 29, 32): '"""value"""', (29, 34, 29, 66): "('definitely-not-a-tag', 'fiel')"}, {}), "('value', ('definitely-not-a-tag', 'fiel'))", False, 'import pytest\n'), ((35, 1, 82, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(36, 4, 36, 30): "('config_obj', 'expected')", (36, 32, 81, 5): "(({'repos': [{'repo': 'git@github.com:pre-commit/pre-commit-hooks', 'rev':\n    'cd74dc150c142c3be70b24eaf0b02cae9d235f37', 'hooks': [{'id': 'pyflakes',\n    'files': '\\\\.py$'}]}]}, True), ({'repos': [{'repo':\n    'git@github.com:pre-commit/pre-commit-hooks', 'rev':\n    'cd74dc150c142c3be70b24eaf0b02cae9d235f37', 'hooks': [{'id': 'pyflakes',\n    'files': '\\\\.py$', 'args': ['foo', 'bar', 'baz']}]}]}, True), ({'repos':\n    [{'repo': 'git@github.com:pre-commit/pre-commit-hooks', 'rev':\n    'cd74dc150c142c3be70b24eaf0b02cae9d235f37', 'hooks': [{'id': 'pyflakes',\n    'files': '\\\\.py$', 'exclude': 0, 'args': ['foo', 'bar', 'baz']}]}]}, False)\n    )"}, {}), "(('config_obj', 'expected'), (({'repos': [{'repo':\n    'git@github.com:pre-commit/pre-commit-hooks', 'rev':\n    'cd74dc150c142c3be70b24eaf0b02cae9d235f37', 'hooks': [{'id': 'pyflakes',\n    'files': '\\\\.py$'}]}]}, True), ({'repos': [{'repo':\n    'git@github.com:pre-commit/pre-commit-hooks', 'rev':\n    'cd74dc150c142c3be70b24eaf0b02cae9d235f37', 'hooks': [{'id': 'pyflakes',\n    'files': '\\\\.py$', 'args': ['foo', 'bar', 'baz']}]}]}, True), ({'repos':\n    [{'repo': 'git@github.com:pre-commit/pre-commit-hooks', 'rev':\n    'cd74dc150c142c3be70b24eaf0b02cae9d235f37', 'hooks': [{'id': 'pyflakes',\n    'files': '\\\\.py$', 'exclude': 0, 'args': ['foo', 'bar', 'baz']}]}]}, \n    False)))", False, 'import pytest\n'), ((163, 1, 163, 78), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(163, 25, 163, 29): '"""fn"""', (163, 31, 163, 77): '(validate_config_main, validate_manifest_main)'}, {}), "('fn', (validate_config_main, validate_manifest_main))", False, 'import pytest\n'), ((175, 1, 212, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(176, 4, 176, 32): "('manifest_obj', 'expected')", (177, 4, 211, 5): "(([{'id': 'a', 'name': 'b', 'entry': 'c', 'language': 'python', 'files':\n    '\\\\.py$'}], True), ([{'id': 'a', 'name': 'b', 'entry': 'c', 'language':\n    'python', 'language_version': 'python3.4', 'files': '\\\\.py$'}], True),\n    ([{'id': 'a', 'name': 'b', 'entry': 'c', 'language': 'python', 'files':\n    '', 'always_run': True}], True))"}, {}), "(('manifest_obj', 'expected'), (([{'id': 'a', 'name':\n    'b', 'entry': 'c', 'language': 'python', 'files': '\\\\.py$'}], True), ([\n    {'id': 'a', 'name': 'b', 'entry': 'c', 'language': 'python',\n    'language_version': 'python3.4', 'files': '\\\\.py$'}], True), ([{'id':\n    'a', 'name': 'b', 'entry': 'c', 'language': 'python', 'files': '',\n    'always_run': True}], True)))", False, 'import pytest\n'), ((218, 1, 224, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(219, 4, 219, 9): '"""dct"""', (220, 4, 223, 5): "({'repo': 'local'}, {'repo': 'meta'}, {'repo': 'wat', 'sha': 'wat'}, {\n    'repo': 'wat', 'rev': 'wat'})"}, {}), "('dct', ({'repo': 'local'}, {'repo': 'meta'}, {\n    'repo': 'wat', 'sha': 'wat'}, {'repo': 'wat', 'rev': 'wat'}))", False, 'import pytest\n'), ((236, 1, 242, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(237, 4, 237, 9): '"""dct"""', (238, 4, 241, 5): "({'repo': 'a'}, {'repo': 'meta', 'sha': 'a'}, {'repo': 'meta', 'rev': 'a'})"}, {}), "('dct', ({'repo': 'a'}, {'repo': 'meta', 'sha': 'a'},\n    {'repo': 'meta', 'rev': 'a'}))", False, 'import pytest\n'), ((260, 1, 270, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(261, 4, 261, 17): '"""config_repo"""', (262, 4, 269, 5): "({'repo': 'meta', 'hooks': [{'id': 'i-dont-exist'}]}, {'repo': 'meta',\n    'hooks': [{'id': 'identity', 'language': 'python'}]}, {'repo': 'meta',\n    'hooks': [{'id': 'identity', 'name': False}]})"}, {}), "('config_repo', ({'repo': 'meta', 'hooks': [{'id':\n    'i-dont-exist'}]}, {'repo': 'meta', 'hooks': [{'id': 'identity',\n    'language': 'python'}]}, {'repo': 'meta', 'hooks': [{'id': 'identity',\n    'name': False}]}))", False, 'import pytest\n'), ((276, 1, 284, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(277, 4, 277, 13): '"""mapping"""', (278, 4, 283, 5): "({'pony': '1.0'}, {'python': 3})"}, {}), "('mapping', ({'pony': '1.0'}, {'python': 3}))", False, 'import pytest\n'), ((310, 1, 310, 69), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(310, 25, 310, 33): '"""schema"""', (310, 35, 310, 68): '(CONFIG_SCHEMA, CONFIG_REPO_DICT)'}, {}), "('schema', (CONFIG_SCHEMA, CONFIG_REPO_DICT))", False, 'import pytest\n'), ((96, 4, 96, 44), 'cfgv.validate', 'cfgv.validate', ({(96, 18, 96, 28): 'config_obj', (96, 30, 96, 43): 'CONFIG_SCHEMA'}, {}), '(config_obj, CONFIG_SCHEMA)', False, 'import cfgv\n'), ((130, 14, 130, 48), 'pre_commit.clientlib.validate_config_main', 'validate_config_main', ({(130, 35, 130, 47): '(f.strpath,)'}, {}), '((f.strpath,))', False, 'from pre_commit.clientlib import validate_config_main\n'), ((152, 14, 152, 48), 'pre_commit.clientlib.validate_config_main', 'validate_config_main', ({(152, 35, 152, 47): '(f.strpath,)'}, {}), '((f.strpath,))', False, 'from pre_commit.clientlib import validate_config_main\n'), ((307, 4, 307, 37), 'cfgv.validate', 'cfgv.validate', ({(307, 18, 307, 21): 'cfg', (307, 23, 307, 36): 'CONFIG_SCHEMA'}, {}), '(cfg, CONFIG_SCHEMA)', False, 'import cfgv\n'), ((23, 8, 23, 38), 'cfgv.validate', 'cfgv.validate', ({(23, 22, 23, 25): 'obj', (23, 27, 23, 37): 'obj_schema'}, {}), '(obj, obj_schema)', False, 'import cfgv\n'), ((31, 9, 31, 44), 'pytest.raises', 'pytest.raises', ({(31, 23, 31, 43): 'cfgv.ValidationError'}, {}), '(cfgv.ValidationError)', False, 'import pytest\n'), ((32, 8, 32, 29), 'pre_commit.clientlib.check_type_tag', 'check_type_tag', ({(32, 23, 32, 28): 'value'}, {}), '(value)', False, 'from pre_commit.clientlib import check_type_tag\n'), ((90, 9, 90, 44), 'pytest.raises', 'pytest.raises', ({(90, 23, 90, 43): 'cfgv.ValidationError'}, {}), '(cfgv.ValidationError)', False, 'import pytest\n'), ((91, 8, 91, 48), 'cfgv.validate', 'cfgv.validate', ({(91, 22, 91, 32): 'config_obj', (91, 34, 91, 47): 'CONFIG_SCHEMA'}, {}), '(config_obj, CONFIG_SCHEMA)', False, 'import cfgv\n'), ((108, 15, 108, 66), 'pre_commit.clientlib.validate_manifest_main', 'validate_manifest_main', ({(108, 38, 108, 65): "('.pre-commit-hooks.yaml',)"}, {}), "(('.pre-commit-hooks.yaml',))", False, 'from pre_commit.clientlib import validate_manifest_main\n'), ((112, 15, 112, 65), 'pre_commit.clientlib.validate_config_main', 'validate_config_main', ({(112, 36, 112, 64): "('.pre-commit-config.yaml',)"}, {}), "(('.pre-commit-config.yaml',))", False, 'from pre_commit.clientlib import validate_config_main\n'), ((118, 15, 118, 49), 'pre_commit.clientlib.validate_config_main', 'validate_config_main', ({(118, 36, 118, 48): '(f.strpath,)'}, {}), '((f.strpath,))', False, 'from pre_commit.clientlib import validate_config_main\n'), ((230, 9, 230, 44), 'pytest.raises', 'pytest.raises', ({(230, 23, 230, 43): 'cfgv.ValidationError'}, {}), '(cfgv.ValidationError)', False, 'import pytest\n'), ((244, 9, 244, 44), 'pytest.raises', 'pytest.raises', ({(244, 23, 244, 43): 'cfgv.ValidationError'}, {}), '(cfgv.ValidationError)', False, 'import pytest\n'), ((272, 9, 272, 44), 'pytest.raises', 'pytest.raises', ({(272, 23, 272, 43): 'cfgv.ValidationError'}, {}), '(cfgv.ValidationError)', False, 'import pytest\n'), ((273, 8, 273, 52), 'cfgv.validate', 'cfgv.validate', ({(273, 22, 273, 33): 'config_repo', (273, 35, 273, 51): 'CONFIG_REPO_DICT'}, {}), '(config_repo, CONFIG_REPO_DICT)', False, 'import cfgv\n'), ((286, 9, 286, 44), 'pytest.raises', 'pytest.raises', ({(286, 23, 286, 43): 'cfgv.ValidationError'}, {}), '(cfgv.ValidationError)', False, 'import pytest\n'), ((287, 8, 287, 56), 'cfgv.validate', 'cfgv.validate', ({(287, 22, 287, 29): 'mapping', (287, 31, 287, 55): 'DEFAULT_LANGUAGE_VERSION'}, {}), '(mapping, DEFAULT_LANGUAGE_VERSION)', False, 'import cfgv\n'), ((291, 9, 291, 44), 'pytest.raises', 'pytest.raises', ({(291, 23, 291, 43): 'cfgv.ValidationError'}, {}), '(cfgv.ValidationError)', False, 'import pytest\n'), ((293, 8, 293, 41), 'cfgv.validate', 'cfgv.validate', ({(293, 22, 293, 25): 'cfg', (293, 27, 293, 40): 'CONFIG_SCHEMA'}, {}), '(cfg, CONFIG_SCHEMA)', False, 'import cfgv\n'), ((95, 28, 95, 49), 'testing.fixtures.sample_local_config', 'sample_local_config', ({}, {}), '()', False, 'from testing.fixtures import sample_local_config\n'), ((226, 4, 226, 21), 'pre_commit.clientlib.MigrateShaToRev', 'MigrateShaToRev', ({}, {}), '()', False, 'from pre_commit.clientlib import MigrateShaToRev\n'), ((250, 4, 250, 21), 'pre_commit.clientlib.MigrateShaToRev', 'MigrateShaToRev', ({}, {}), '()', False, 'from pre_commit.clientlib import MigrateShaToRev\n'), ((256, 4, 256, 21), 'pre_commit.clientlib.MigrateShaToRev', 'MigrateShaToRev', ({}, {}), '()', False, 'from pre_commit.clientlib import MigrateShaToRev\n'), ((89, 33, 89, 54), 'testing.fixtures.sample_local_config', 'sample_local_config', ({}, {}), '()', False, 'from testing.fixtures import sample_local_config\n'), ((231, 8, 231, 25), 'pre_commit.clientlib.MigrateShaToRev', 'MigrateShaToRev', ({}, {}), '()', False, 'from pre_commit.clientlib import MigrateShaToRev\n'), ((245, 8, 245, 25), 'pre_commit.clientlib.MigrateShaToRev', 'MigrateShaToRev', ({}, {}), '()', False, 'from pre_commit.clientlib import MigrateShaToRev\n')]