import os

import pytest

from dvc.main import main
from dvc.utils.fs import remove


def _gen(tmp_dir, struct, name):
    remove(tmp_dir / "data")
    if struct is None:
        (tmp_dir / name).touch()
    else:
        (stage,) = tmp_dir.dvc_gen({"data": struct})
        os.rename(stage.path, name)


@pytest.mark.parametrize(
    "ancestor, our, their, merged",
    [
        (
            {"foo": "foo"},
            {"foo": "foo", "bar": "bar"},
            {"foo": "foo", "baz": "baz"},
            {"foo": "foo", "bar": "bar", "baz": "baz"},
        ),
        (
            {"common": "common", "subdir": {"foo": "foo"}},
            {"common": "common", "subdir": {"foo": "foo", "bar": "bar"}},
            {"common": "common", "subdir": {"foo": "foo", "baz": "baz"}},
            {
                "common": "common",
                "subdir": {"foo": "foo", "bar": "bar", "baz": "baz"},
            },
        ),
        ({}, {"foo": "foo"}, {"bar": "bar"}, {"foo": "foo", "bar": "bar"},),
        ({}, {}, {"bar": "bar"}, {"bar": "bar"},),
        ({}, {"foo": "foo"}, {}, {"foo": "foo"},),
        (None, {"foo": "foo"}, {"bar": "bar"}, {"foo": "foo", "bar": "bar"},),
        (None, None, {"bar": "bar"}, {"bar": "bar"},),
        (None, {"foo": "foo"}, None, {"foo": "foo"},),
    ],
)
def test_merge(tmp_dir, dvc, ancestor, our, their, merged):
    _gen(tmp_dir, ancestor, "ancestor")
    _gen(tmp_dir, our, "our")
    _gen(tmp_dir, their, "their")

    assert (
        main(
            [
                "git-hook",
                "merge-driver",
                "--ancestor",
                "ancestor",
                "--our",
                "our",
                "--their",
                "their",
            ]
        )
        == 0
    )

    _gen(tmp_dir, merged, "merged")

    assert (tmp_dir / "our").read_text() == (tmp_dir / "merged").read_text()


@pytest.mark.parametrize(
    "ancestor, our, their, error",
    [
        (
            {"foo": "foo"},
            {"foo": "bar"},
            {"foo": "baz"},
            (
                "unable to auto-merge directories with "
                "diff that contains 'change'ed files"
            ),
        ),
        (
            {"common": "common", "foo": "foo"},
            {"common": "common", "bar": "bar"},
            {"baz": "baz"},
            (
                "unable to auto-merge directories with "
                "diff that contains 'remove'ed files"
            ),
        ),
    ],
)
def test_merge_conflict(tmp_dir, dvc, ancestor, our, their, error, caplog):
    _gen(tmp_dir, ancestor, "ancestor")
    _gen(tmp_dir, our, "our")
    _gen(tmp_dir, their, "their")

    assert (
        main(
            [
                "git-hook",
                "merge-driver",
                "--ancestor",
                "ancestor",
                "--our",
                "our",
                "--their",
                "their",
            ]
        )
        != 0
    )

    assert error in caplog.text


@pytest.mark.parametrize(
    "workspace", [pytest.lazy_fixture("ssh")], indirect=True
)
def test_merge_different_output_types(tmp_dir, dvc, caplog, workspace):
    (tmp_dir / "ancestor").touch()

    (tmp_dir / "our").write_text(
        "outs:\n- md5: f123456789.dir\n  path: ssh://example.com/path\n"
    )

    (tmp_dir / "their").write_text(
        "outs:\n- md5: f987654321.dir\n  path: path\n"
    )

    assert (
        main(
            [
                "git-hook",
                "merge-driver",
                "--ancestor",
                "ancestor",
                "--our",
                "our",
                "--their",
                "their",
            ]
        )
        != 0
    )

    error = "unable to auto-merge outputs of different types"
    assert error in caplog.text


def test_merge_different_output_options(tmp_dir, dvc, caplog):
    (tmp_dir / "ancestor").touch()

    (tmp_dir / "our").write_text(
        "outs:\n- md5: f123456789.dir\n  path: path\n"
    )

    (tmp_dir / "their").write_text(
        "outs:\n- md5: f987654321.dir\n  path: path\n  cache: false\n"
    )

    assert (
        main(
            [
                "git-hook",
                "merge-driver",
                "--ancestor",
                "ancestor",
                "--our",
                "our",
                "--their",
                "their",
            ]
        )
        != 0
    )

    error = "unable to auto-merge outputs with different options"
    assert error in caplog.text


def test_merge_file(tmp_dir, dvc, caplog):
    (tmp_dir / "ancestor").touch()

    (tmp_dir / "our").write_text(
        "outs:\n- md5: f123456789.dir\n  path: path\n"
    )

    (tmp_dir / "their").write_text("outs:\n- md5: f987654321\n  path: path\n")

    assert (
        main(
            [
                "git-hook",
                "merge-driver",
                "--ancestor",
                "ancestor",
                "--our",
                "our",
                "--their",
                "their",
            ]
        )
        != 0
    )

    err = "unable to auto-merge outputs that are not directories"
    assert err in caplog.text


def test_merge_non_dvc_add(tmp_dir, dvc, caplog):
    (tmp_dir / "ancestor").touch()

    (tmp_dir / "our").write_text(
        "outs:\n"
        "- md5: f123456789.dir\n"
        "  path: path\n"
        "- md5: ff123456789.dir\n"
        "  path: another\n"
    )

    (tmp_dir / "their").write_text("outs:\n- md5: f987654321\n  path: path\n")

    assert (
        main(
            [
                "git-hook",
                "merge-driver",
                "--ancestor",
                "ancestor",
                "--our",
                "our",
                "--their",
                "their",
            ]
        )
        != 0
    )

    error = "unable to auto-merge DVC-files that weren't created by `dvc add`"
    assert error in caplog.text
