#
#  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, ALL_REPO_KINDS
from .. import cli  # pylint: disable=unused-import
from .utils import add_plugins_conf

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


def generate_element(repo, element_path, dep_name=None):
    element = {"kind": "import", "sources": [repo.source_config()]}
    if dep_name:
        element["depends"] = [dep_name]

    _yaml.roundtrip_dump(element, element_path)


def generate_import_element(tmpdir, kind, project, name):
    element_name = "import-{}.bst".format(name)
    repo_element_path = os.path.join(project, "elements", element_name)
    files = str(tmpdir.join("imported_files_{}".format(name)))
    os.makedirs(files)

    with open(os.path.join(files, "{}.txt".format(name)), "w", encoding="utf-8") as f:
        f.write(name)

    repo = create_repo(kind, str(tmpdir.join("element_{}_repo".format(name))))
    repo.create(files)

    generate_element(repo, repo_element_path)

    return element_name


def generate_project(tmpdir, name, kind, config=None):
    if config is None:
        config = {}

    project_name = "project-{}".format(name)
    subproject_path = os.path.join(str(tmpdir.join(project_name)))
    os.makedirs(os.path.join(subproject_path, "elements"))

    project_conf = {"name": name, "min-version": "2.0", "element-path": "elements"}
    project_conf.update(config)
    _yaml.roundtrip_dump(project_conf, os.path.join(subproject_path, "project.conf"))
    add_plugins_conf(subproject_path, kind)

    return project_name, subproject_path


def generate_simple_stack(project, name, dependencies):
    element_name = "{}.bst".format(name)
    element_path = os.path.join(project, "elements", element_name)
    element = {"kind": "stack", "depends": dependencies}
    _yaml.roundtrip_dump(element, element_path)

    return element_name


def generate_cross_element(project, subproject_name, import_name):
    basename, _ = os.path.splitext(import_name)
    return generate_simple_stack(
        project,
        "import-{}-{}".format(subproject_name, basename),
        [{"junction": "{}.bst".format(subproject_name), "filename": import_name}],
    )


@pytest.mark.parametrize("kind", ALL_REPO_KINDS.keys())
def test_cross_junction_multiple_projects(cli, tmpdir, kind):
    tmpdir = tmpdir.join(kind)

    # Generate 3 projects: main, a, b
    _, project = generate_project(tmpdir, "main", kind, {"ref-storage": "project.refs"})
    project_a, project_a_path = generate_project(tmpdir, "a", kind)
    project_b, project_b_path = generate_project(tmpdir, "b", kind)

    # Generate an element with a trackable source for each project
    element_a = generate_import_element(tmpdir, kind, project_a_path, "a")
    element_b = generate_import_element(tmpdir, kind, project_b_path, "b")
    element_c = generate_import_element(tmpdir, kind, project, "c")

    # Create some indirections to the elements with dependencies to test --deps
    stack_a = generate_simple_stack(project_a_path, "stack-a", [element_a])
    stack_b = generate_simple_stack(project_b_path, "stack-b", [element_b])

    # Create junctions for projects a and b in main.
    junction_a = "{}.bst".format(project_a)
    junction_a_path = os.path.join(project, "elements", junction_a)
    generate_junction(tmpdir.join("repo_a"), project_a_path, junction_a_path, store_ref=False)

    junction_b = "{}.bst".format(project_b)
    junction_b_path = os.path.join(project, "elements", junction_b)
    generate_junction(tmpdir.join("repo_b"), project_b_path, junction_b_path, store_ref=False)

    # Track the junctions.
    result = cli.run(project=project, args=["source", "track", junction_a, junction_b])
    result.assert_success()

    # Import elements from a and b in to main.
    imported_a = generate_cross_element(project, project_a, stack_a)
    imported_b = generate_cross_element(project, project_b, stack_b)

    # Generate a top level stack depending on everything
    all_bst = generate_simple_stack(project, "all", [imported_a, imported_b, element_c])

    # Track without following junctions. But explicitly also track the elements in project a.
    result = cli.run(
        project=project, args=["source", "track", "--deps", "all", all_bst, "{}:{}".format(junction_a, stack_a)]
    )
    result.assert_success()

    # Elements in project b should not be tracked. But elements in project a and main should.
    expected = [element_c, "{}:{}".format(junction_a, element_a)]
    assert set(result.get_tracked_elements()) == set(expected)


@pytest.mark.parametrize("kind", ALL_REPO_KINDS.keys())
def test_track_exceptions(cli, tmpdir, kind):
    tmpdir = tmpdir.join(kind)

    _, project = generate_project(tmpdir, "main", kind, {"ref-storage": "project.refs"})
    project_a, project_a_path = generate_project(tmpdir, "a", kind)

    element_a = generate_import_element(tmpdir, kind, project_a_path, "a")
    element_b = generate_import_element(tmpdir, kind, project_a_path, "b")

    all_bst = generate_simple_stack(project_a_path, "all", [element_a, element_b])

    junction_a = "{}.bst".format(project_a)
    junction_a_path = os.path.join(project, "elements", junction_a)
    generate_junction(tmpdir.join("repo_a"), project_a_path, junction_a_path, store_ref=False)

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

    imported_b = generate_cross_element(project, project_a, element_b)
    indirection = generate_simple_stack(project, "indirection", [imported_b])

    result = cli.run(
        project=project,
        args=[
            "source",
            "track",
            "--deps",
            "all",
            "--except",
            indirection,
            "{}:{}".format(junction_a, all_bst),
            imported_b,
        ],
    )
    result.assert_success()

    expected = ["{}:{}".format(junction_a, element_a), "{}:{}".format(junction_a, element_b)]
    assert set(result.get_tracked_elements()) == set(expected)
