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, 'pytest.mark.parametrize', 'pytest.mark.parametrize', False, 'import pytest\n'), (35, 'pytest.mark.parametrize', 'pytest.mark.parametrize', False, 'import pytest\n'), (163, 'pytest.mark.parametrize', 'pytest.mark.parametrize', False, 'import pytest\n'), (175, 'pytest.mark.parametrize', 'pytest.mark.parametrize', False, 'import pytest\n'), (218, 'pytest.mark.parametrize', 'pytest.mark.parametrize', False, 'import pytest\n'), (236, 'pytest.mark.parametrize', 'pytest.mark.parametrize', False, 'import pytest\n'), (260, 'pytest.mark.parametrize', 'pytest.mark.parametrize', False, 'import pytest\n'), (276, 'pytest.mark.parametrize', 'pytest.mark.parametrize', False, 'import pytest\n'), (310, 'pytest.mark.parametrize', 'pytest.mark.parametrize', False, 'import pytest\n'), (96, 'cfgv.validate', 'cfgv.validate', False, 'import cfgv\n'), (130, 'pre_commit.clientlib.validate_config_main', 'validate_config_main', False, 'from pre_commit.clientlib import validate_config_main\n'), (152, 'pre_commit.clientlib.validate_config_main', 'validate_config_main', False, 'from pre_commit.clientlib import validate_config_main\n'), (307, 'cfgv.validate', 'cfgv.validate', False, 'import cfgv\n'), (23, 'cfgv.validate', 'cfgv.validate', False, 'import cfgv\n'), (31, 'pytest.raises', 'pytest.raises', False, 'import pytest\n'), (32, 'pre_commit.clientlib.check_type_tag', 'check_type_tag', False, 'from pre_commit.clientlib import check_type_tag\n'), (90, 'pytest.raises', 'pytest.raises', False, 'import pytest\n'), (91, 'cfgv.validate', 'cfgv.validate', False, 'import cfgv\n'), (108, 'pre_commit.clientlib.validate_manifest_main', 'validate_manifest_main', False, 'from pre_commit.clientlib import validate_manifest_main\n'), (112, 'pre_commit.clientlib.validate_config_main', 'validate_config_main', False, 'from pre_commit.clientlib import validate_config_main\n'), (118, 'pre_commit.clientlib.validate_config_main', 'validate_config_main', False, 'from pre_commit.clientlib import validate_config_main\n'), (230, 'pytest.raises', 'pytest.raises', False, 'import pytest\n'), (244, 'pytest.raises', 'pytest.raises', False, 'import pytest\n'), (272, 'pytest.raises', 'pytest.raises', False, 'import pytest\n'), (273, 'cfgv.validate', 'cfgv.validate', False, 'import cfgv\n'), (286, 'pytest.raises', 'pytest.raises', False, 'import pytest\n'), (287, 'cfgv.validate', 'cfgv.validate', False, 'import cfgv\n'), (291, 'pytest.raises', 'pytest.raises', False, 'import pytest\n'), (293, 'cfgv.validate', 'cfgv.validate', False, 'import cfgv\n'), (95, 'testing.fixtures.sample_local_config', 'sample_local_config', False, 'from testing.fixtures import sample_local_config\n'), (226, 'pre_commit.clientlib.MigrateShaToRev', 'MigrateShaToRev', False, 'from pre_commit.clientlib import MigrateShaToRev\n'), (250, 'pre_commit.clientlib.MigrateShaToRev', 'MigrateShaToRev', False, 'from pre_commit.clientlib import MigrateShaToRev\n'), (256, 'pre_commit.clientlib.MigrateShaToRev', 'MigrateShaToRev', False, 'from pre_commit.clientlib import MigrateShaToRev\n'), (89, 'testing.fixtures.sample_local_config', 'sample_local_config', False, 'from testing.fixtures import sample_local_config\n'), (231, 'pre_commit.clientlib.MigrateShaToRev', 'MigrateShaToRev', False, 'from pre_commit.clientlib import MigrateShaToRev\n'), (245, 'pre_commit.clientlib.MigrateShaToRev', 'MigrateShaToRev', False, 'from pre_commit.clientlib import MigrateShaToRev\n')]