#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#

# Pylint doesn't play well with fixtures and dependency injection from pytest
# pylint: disable=redefined-outer-name

import os
import pytest

from buildstream import _yaml
from .._utils import generate_junction
from .. import create_repo
from .. import cli  # pylint: disable=unused-import
from .utils import kind  # pylint: disable=unused-import

# Project directory
TOP_DIR = os.path.dirname(os.path.realpath(__file__))
DATA_DIR = os.path.join(TOP_DIR, "project")


def _set_project_mirrors_and_aliases(project_path, mirrors, aliases):
    project_conf_path = os.path.join(project_path, "project.conf")
    project_conf = _yaml.roundtrip_load(project_conf_path)

    project_conf["mirrors"] = mirrors
    project_conf["aliases"].update(aliases)

    _yaml.roundtrip_dump(project_conf, project_conf_path)


def _set_project_includes_and_aliases(project_path, includes, aliases):
    project_conf_path = os.path.join(project_path, "project.conf")
    project_conf = _yaml.roundtrip_load(project_conf_path)

    project_conf["aliases"].update(aliases)
    project_conf["(@)"] = includes

    _yaml.roundtrip_dump(project_conf, project_conf_path)


@pytest.mark.datafiles(DATA_DIR)
def test_mirror_fetch(cli, tmpdir, datafiles, kind):
    project_dir = str(datafiles)
    bin_files_path = os.path.join(project_dir, "files", "bin-files", "usr")
    dev_files_path = os.path.join(project_dir, "files", "dev-files", "usr")
    upstream_repodir = os.path.join(str(tmpdir), "upstream")
    mirror_repodir = os.path.join(str(tmpdir), "mirror")
    element_dir = os.path.join(project_dir, "elements")

    # Create repo objects of the upstream and mirror
    upstream_repo = create_repo(kind, upstream_repodir)
    upstream_repo.create(bin_files_path)
    mirror_repo = upstream_repo.copy(mirror_repodir)
    upstream_ref = upstream_repo.create(dev_files_path)

    element = {"kind": "import", "sources": [upstream_repo.source_config(ref=upstream_ref)]}
    element_name = "test.bst"
    element_path = os.path.join(element_dir, element_name)
    full_repo = element["sources"][0]["url"]
    upstream_map, repo_name = os.path.split(full_repo)
    alias = "foo-" + kind
    aliased_repo = alias + ":" + repo_name
    element["sources"][0]["url"] = aliased_repo
    full_mirror = mirror_repo.source_config()["url"]
    mirror_map, _ = os.path.split(full_mirror)
    _yaml.roundtrip_dump(element, element_path)

    _set_project_mirrors_and_aliases(
        project_dir,
        [
            {
                "name": "middle-earth",
                "aliases": {
                    alias: [mirror_map + "/"],
                },
            },
        ],
        {alias: upstream_map + "/"},
    )

    # No obvious ways of checking that the mirror has been fetched
    # But at least we can be sure it succeeds
    result = cli.run(project=project_dir, args=["source", "fetch", element_name])
    result.assert_success()


@pytest.mark.datafiles(DATA_DIR)
def test_mirror_fetch_upstream_absent(cli, tmpdir, datafiles, kind):
    project_dir = str(datafiles)
    dev_files_path = os.path.join(project_dir, "files", "dev-files", "usr")
    upstream_repodir = os.path.join(project_dir, "upstream")
    mirror_repodir = os.path.join(str(tmpdir), "mirror")
    element_dir = os.path.join(project_dir, "elements")

    # Create repo objects of the upstream and mirror
    upstream_repo = create_repo(kind, upstream_repodir)
    ref = upstream_repo.create(dev_files_path)
    mirror_repo = upstream_repo.copy(mirror_repodir)

    element = {"kind": "import", "sources": [upstream_repo.source_config(ref=ref)]}

    element_name = "test.bst"
    element_path = os.path.join(element_dir, element_name)
    full_repo = element["sources"][0]["url"]
    _, repo_name = os.path.split(full_repo)
    alias = "foo-" + kind
    aliased_repo = alias + ":" + repo_name
    element["sources"][0]["url"] = aliased_repo
    full_mirror = mirror_repo.source_config()["url"]
    mirror_map, _ = os.path.split(full_mirror)
    _yaml.roundtrip_dump(element, element_path)

    _set_project_mirrors_and_aliases(
        project_dir,
        [
            {
                "name": "middle-earth",
                "aliases": {alias: [mirror_map + "/"]},
            },
        ],
        {alias: "http://www.example.com"},
    )

    result = cli.run(project=project_dir, args=["source", "fetch", element_name])
    result.assert_success()


@pytest.mark.datafiles(DATA_DIR)
def test_mirror_from_includes(cli, tmpdir, datafiles, kind):
    project_dir = str(datafiles)
    bin_files_path = os.path.join(project_dir, "files", "bin-files", "usr")
    upstream_repodir = os.path.join(str(tmpdir), "upstream")
    mirror_repodir = os.path.join(str(tmpdir), "mirror")
    element_dir = os.path.join(project_dir, "elements")

    # Create repo objects of the upstream and mirror
    upstream_repo = create_repo(kind, upstream_repodir)
    upstream_ref = upstream_repo.create(bin_files_path)
    mirror_repo = upstream_repo.copy(mirror_repodir)

    element = {"kind": "import", "sources": [upstream_repo.source_config(ref=upstream_ref)]}
    element_name = "test.bst"
    element_path = os.path.join(element_dir, element_name)
    full_repo = element["sources"][0]["url"]
    upstream_map, repo_name = os.path.split(full_repo)
    alias = "foo-" + kind
    aliased_repo = alias + ":" + repo_name
    element["sources"][0]["url"] = aliased_repo
    full_mirror = mirror_repo.source_config()["url"]
    mirror_map, _ = os.path.split(full_mirror)
    _yaml.roundtrip_dump(element, element_path)

    config_project_dir = str(tmpdir.join("config"))
    os.makedirs(config_project_dir, exist_ok=True)
    config_project = {"name": "config", "min-version": "2.0"}
    _yaml.roundtrip_dump(config_project, os.path.join(config_project_dir, "project.conf"))
    extra_mirrors = {
        "mirrors": [
            {
                "name": "middle-earth",
                "aliases": {
                    alias: [mirror_map + "/"],
                },
            }
        ]
    }
    _yaml.roundtrip_dump(extra_mirrors, os.path.join(config_project_dir, "mirrors.yml"))
    generate_junction(str(tmpdir.join("config_repo")), config_project_dir, os.path.join(element_dir, "config.bst"))

    _set_project_includes_and_aliases(
        project_dir,
        ["config.bst:mirrors.yml"],
        {alias: upstream_map + "/"},
    )

    # Now make the upstream unavailable.
    os.rename(upstream_repo.repo, "{}.bak".format(upstream_repo.repo))
    result = cli.run(project=project_dir, args=["source", "fetch", element_name])
    result.assert_success()


@pytest.mark.datafiles(DATA_DIR)
def test_mirror_track_upstream_present(cli, tmpdir, datafiles, kind):
    project_dir = str(datafiles)
    bin_files_path = os.path.join(project_dir, "files", "bin-files", "usr")
    dev_files_path = os.path.join(project_dir, "files", "dev-files", "usr")
    upstream_repodir = os.path.join(str(tmpdir), "upstream")
    mirror_repodir = os.path.join(str(tmpdir), "mirror")
    element_dir = os.path.join(project_dir, "elements")

    # Create repo objects of the upstream and mirror
    upstream_repo = create_repo(kind, upstream_repodir)
    upstream_repo.create(bin_files_path)
    mirror_repo = upstream_repo.copy(mirror_repodir)
    upstream_ref = upstream_repo.create(dev_files_path)

    element = {"kind": "import", "sources": [upstream_repo.source_config(ref=upstream_ref)]}

    element_name = "test.bst"
    element_path = os.path.join(element_dir, element_name)
    full_repo = element["sources"][0]["url"]
    upstream_map, repo_name = os.path.split(full_repo)
    alias = "foo-" + kind
    aliased_repo = alias + ":" + repo_name
    element["sources"][0]["url"] = aliased_repo
    full_mirror = mirror_repo.source_config()["url"]
    mirror_map, _ = os.path.split(full_mirror)
    _yaml.roundtrip_dump(element, element_path)

    _set_project_mirrors_and_aliases(
        project_dir,
        [
            {
                "name": "middle-earth",
                "aliases": {
                    alias: [mirror_map + "/"],
                },
            },
        ],
        {alias: upstream_map + "/"},
    )

    result = cli.run(project=project_dir, args=["source", "track", element_name])
    result.assert_success()

    # Tracking tries upstream first. Check the ref is from upstream.
    new_element = _yaml.load(element_path, shortname=element_name)
    source = new_element.get_sequence("sources").mapping_at(0)
    if "ref" in source:
        assert source.get_str("ref") == upstream_ref


@pytest.mark.datafiles(DATA_DIR)
def test_mirror_track_upstream_absent(cli, tmpdir, datafiles, kind):
    project_dir = str(datafiles)
    bin_files_path = os.path.join(project_dir, "files", "bin-files", "usr")
    dev_files_path = os.path.join(project_dir, "files", "dev-files", "usr")
    upstream_repodir = os.path.join(str(tmpdir), "upstream")
    mirror_repodir = os.path.join(str(tmpdir), "mirror")
    element_dir = os.path.join(project_dir, "elements")

    # Create repo objects of the upstream and mirror
    upstream_repo = create_repo(kind, upstream_repodir)
    upstream_ref = upstream_repo.create(bin_files_path)
    mirror_repo = upstream_repo.copy(mirror_repodir)
    mirror_ref = upstream_ref
    upstream_ref = upstream_repo.create(dev_files_path)

    # Configure to allow tracking mirrors as well as upstream
    cli.configure({"track": {"source": "all"}})

    element = {"kind": "import", "sources": [upstream_repo.source_config(ref=upstream_ref)]}

    element_name = "test.bst"
    element_path = os.path.join(element_dir, element_name)
    full_repo = element["sources"][0]["url"]
    _, repo_name = os.path.split(full_repo)
    alias = "foo-" + kind
    aliased_repo = alias + ":" + repo_name
    element["sources"][0]["url"] = aliased_repo
    full_mirror = mirror_repo.source_config()["url"]
    mirror_map, _ = os.path.split(full_mirror)
    _yaml.roundtrip_dump(element, element_path)

    _set_project_mirrors_and_aliases(
        project_dir,
        [
            {
                "name": "middle-earth",
                "aliases": {
                    alias: [mirror_map + "/"],
                },
            },
        ],
        {alias: "http://www.example.com"},
    )

    result = cli.run(project=project_dir, args=["source", "track", element_name])
    result.assert_success()

    # Check that tracking fell back to the mirror
    new_element = _yaml.load(element_path, shortname=element_name)
    source = new_element.get_sequence("sources").mapping_at(0)
    if "ref" in source:
        assert source.get_str("ref") == mirror_ref
