from __future__ import annotations

from unittest import mock

import pytest
import yaml

import pre_commit.constants as C
from pre_commit.clientlib import InvalidConfigError
from pre_commit.commands.migrate_config import migrate_config
from pre_commit.yaml import yaml_compose


@pytest.fixture(autouse=True, params=['c', 'pure'])
def switch_pyyaml_impl(request):
    if request.param == 'c':
        yield
    else:
        with mock.patch.dict(
                yaml_compose.keywords,
                {'Loader': yaml.SafeLoader},
        ):
            yield


def test_migrate_config_normal_format(tmpdir, capsys):
    cfg = tmpdir.join(C.CONFIG_FILE)
    cfg.write(
        '-   repo: local\n'
        '    hooks:\n'
        '    -   id: foo\n'
        '        name: foo\n'
        '        entry: ./bin/foo.sh\n'
        '        language: script\n',
    )
    with tmpdir.as_cwd():
        assert not migrate_config(C.CONFIG_FILE)
    out, _ = capsys.readouterr()
    assert out == 'Configuration has been migrated.\n'
    contents = cfg.read()
    assert contents == (
        'repos:\n'
        '-   repo: local\n'
        '    hooks:\n'
        '    -   id: foo\n'
        '        name: foo\n'
        '        entry: ./bin/foo.sh\n'
        '        language: script\n'
    )


def test_migrate_config_document_marker(tmpdir):
    cfg = tmpdir.join(C.CONFIG_FILE)
    cfg.write(
        '# comment\n'
        '\n'
        '---\n'
        '-   repo: local\n'
        '    hooks:\n'
        '    -   id: foo\n'
        '        name: foo\n'
        '        entry: ./bin/foo.sh\n'
        '        language: script\n',
    )
    with tmpdir.as_cwd():
        assert not migrate_config(C.CONFIG_FILE)
    contents = cfg.read()
    assert contents == (
        '# comment\n'
        '\n'
        '---\n'
        'repos:\n'
        '-   repo: local\n'
        '    hooks:\n'
        '    -   id: foo\n'
        '        name: foo\n'
        '        entry: ./bin/foo.sh\n'
        '        language: script\n'
    )


def test_migrate_config_list_literal(tmpdir):
    cfg = tmpdir.join(C.CONFIG_FILE)
    cfg.write(
        '[{\n'
        '    repo: local,\n'
        '    hooks: [{\n'
        '        id: foo, name: foo, entry: ./bin/foo.sh,\n'
        '        language: script,\n'
        '    }]\n'
        '}]',
    )
    with tmpdir.as_cwd():
        assert not migrate_config(C.CONFIG_FILE)
    contents = cfg.read()
    assert contents == (
        'repos:\n'
        '    [{\n'
        '        repo: local,\n'
        '        hooks: [{\n'
        '            id: foo, name: foo, entry: ./bin/foo.sh,\n'
        '            language: script,\n'
        '        }]\n'
        '    }]'
    )


def test_already_migrated_configuration_noop(tmpdir, capsys):
    contents = (
        'repos:\n'
        '-   repo: local\n'
        '    hooks:\n'
        '    -   id: foo\n'
        '        name: foo\n'
        '        entry: ./bin/foo.sh\n'
        '        language: script\n'
    )
    cfg = tmpdir.join(C.CONFIG_FILE)
    cfg.write(contents)
    with tmpdir.as_cwd():
        assert not migrate_config(C.CONFIG_FILE)
    out, _ = capsys.readouterr()
    assert out == 'Configuration is already migrated.\n'
    assert cfg.read() == contents


def test_migrate_config_sha_to_rev(tmpdir):
    contents = (
        'repos:\n'
        '-   repo: https://github.com/pre-commit/pre-commit-hooks\n'
        '    sha: v1.2.0\n'
        '    hooks: []\n'
        '-   repo: https://github.com/pre-commit/pre-commit-hooks\n'
        '    sha: v1.2.0\n'
        '    hooks: []\n'
    )
    cfg = tmpdir.join(C.CONFIG_FILE)
    cfg.write(contents)
    with tmpdir.as_cwd():
        assert not migrate_config(C.CONFIG_FILE)
    contents = cfg.read()
    assert contents == (
        'repos:\n'
        '-   repo: https://github.com/pre-commit/pre-commit-hooks\n'
        '    rev: v1.2.0\n'
        '    hooks: []\n'
        '-   repo: https://github.com/pre-commit/pre-commit-hooks\n'
        '    rev: v1.2.0\n'
        '    hooks: []\n'
    )


def test_migrate_config_sha_to_rev_json(tmp_path):
    contents = """\
{"repos": [{
    "repo": "https://github.com/pre-commit/pre-commit-hooks",
    "sha": "v1.2.0",
    "hooks": []
}]}
"""
    expected = """\
{"repos": [{
    "repo": "https://github.com/pre-commit/pre-commit-hooks",
    "rev": "v1.2.0",
    "hooks": []
}]}
"""
    cfg = tmp_path.joinpath('cfg.yaml')
    cfg.write_text(contents)
    assert not migrate_config(str(cfg))
    assert cfg.read_text() == expected


def test_migrate_config_language_python_venv(tmp_path):
    src = '''\
repos:
-   repo: local
    hooks:
    -   id: example
        name: example
        entry: example
        language: python_venv
    -   id: example
        name: example
        entry: example
        language: system
'''
    expected = '''\
repos:
-   repo: local
    hooks:
    -   id: example
        name: example
        entry: example
        language: python
    -   id: example
        name: example
        entry: example
        language: system
'''
    cfg = tmp_path.joinpath('cfg.yaml')
    cfg.write_text(src)
    assert migrate_config(str(cfg)) == 0
    assert cfg.read_text() == expected


def test_migrate_config_quoted_python_venv(tmp_path):
    src = '''\
repos:
-   repo: local
    hooks:
    -   id: example
        name: example
        entry: example
        language: "python_venv"
'''
    expected = '''\
repos:
-   repo: local
    hooks:
    -   id: example
        name: example
        entry: example
        language: "python"
'''
    cfg = tmp_path.joinpath('cfg.yaml')
    cfg.write_text(src)
    assert migrate_config(str(cfg)) == 0
    assert cfg.read_text() == expected


def test_migrate_config_default_stages(tmp_path):
    src = '''\
default_stages: [commit, push, merge-commit, commit-msg]
repos: []
'''
    expected = '''\
default_stages: [pre-commit, pre-push, pre-merge-commit, commit-msg]
repos: []
'''
    cfg = tmp_path.joinpath('cfg.yaml')
    cfg.write_text(src)
    assert migrate_config(str(cfg)) == 0
    assert cfg.read_text() == expected


def test_migrate_config_hook_stages(tmp_path):
    src = '''\
repos:
-   repo: local
    hooks:
    -   id: example
        name: example
        entry: example
        language: system
        stages: ["commit", "push", "merge-commit", "commit-msg"]
'''
    expected = '''\
repos:
-   repo: local
    hooks:
    -   id: example
        name: example
        entry: example
        language: system
        stages: ["pre-commit", "pre-push", "pre-merge-commit", "commit-msg"]
'''
    cfg = tmp_path.joinpath('cfg.yaml')
    cfg.write_text(src)
    assert migrate_config(str(cfg)) == 0
    assert cfg.read_text() == expected


def test_migrate_config_invalid_yaml(tmpdir):
    contents = '['
    cfg = tmpdir.join(C.CONFIG_FILE)
    cfg.write(contents)
    with tmpdir.as_cwd(), pytest.raises(InvalidConfigError) as excinfo:
        migrate_config(C.CONFIG_FILE)
    expected = '\n==> File .pre-commit-config.yaml\n=====> '
    assert str(excinfo.value).startswith(expected)
