#
#  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 buildstream._testing import cli  # pylint: disable=unused-import
from buildstream.exceptions import ErrorDomain, LoadErrorReason


DATA_DIR = os.path.join(
    os.path.dirname(os.path.realpath(__file__)),
    "link",
)

#
# Test links to elements, this tests both specifying the link as
# the main target, and also as a dependency of the main target.
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("target", ["target.bst", "hello-link.bst"])
def test_simple_link(cli, tmpdir, datafiles, target):
    project = os.path.join(str(datafiles), "simple")
    checkoutdir = os.path.join(str(tmpdir), "checkout")

    # Build, checkout
    result = cli.run(project=project, args=["build", target])
    result.assert_success()
    result = cli.run(project=project, args=["artifact", "checkout", target, "--directory", checkoutdir])
    result.assert_success()

    # Check that the checkout contains the expected files from sub-sub-project
    assert os.path.exists(os.path.join(checkoutdir, "hello.txt"))


#
# Test links to elements, this tests both specifying the link as
# the main target, and also as a dependency of the main target, while
# also using a conditional statement in the link
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("target", ["target.bst", "target-link.bst"])
@pytest.mark.parametrize("greeting,expected_file", [("hello", "hello.txt"), ("goodbye", "goodbye.txt")])
def test_conditional_link(cli, tmpdir, datafiles, target, greeting, expected_file):
    project = os.path.join(str(datafiles), "conditional")
    checkoutdir = os.path.join(str(tmpdir), "checkout")

    # Build, checkout
    result = cli.run(project=project, args=["-o", "greeting", greeting, "build", target])
    result.assert_success()
    result = cli.run(
        project=project, args=["-o", "greeting", greeting, "artifact", "checkout", target, "--directory", checkoutdir]
    )
    result.assert_success()

    # Check that the checkout contains the expected files from sub-sub-project
    assert os.path.exists(os.path.join(checkoutdir, expected_file))


#
# Test links to junctions from local projects and subprojects
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
    "target", ["target-local.bst", "target-nested.bst", "full-path-link.bst", "target-full-path.bst"]
)
def test_simple_junctions(cli, tmpdir, datafiles, target):
    project = os.path.join(str(datafiles), "simple-junctions")
    checkoutdir = os.path.join(str(tmpdir), "checkout")

    # Build, checkout
    result = cli.run(project=project, args=["build", target])
    result.assert_success()
    result = cli.run(project=project, args=["artifact", "checkout", target, "--directory", checkoutdir])
    result.assert_success()

    # Check that the checkout contains the expected files from sub-sub-project
    assert os.path.exists(os.path.join(checkoutdir, "hello.txt"))


#
# Test links which resolve junction targets conditionally
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("greeting,expected_file", [("hello", "hello.txt"), ("goodbye", "goodbye.txt")])
def test_conditional_junctions(cli, tmpdir, datafiles, greeting, expected_file):
    project = os.path.join(str(datafiles), "conditional-junctions")
    checkoutdir = os.path.join(str(tmpdir), "checkout")

    # Build, checkout
    result = cli.run(project=project, args=["-o", "greeting", greeting, "build", "target.bst"])
    result.assert_success()
    result = cli.run(
        project=project,
        args=["-o", "greeting", greeting, "artifact", "checkout", "target.bst", "--directory", checkoutdir],
    )
    result.assert_success()

    # Check that the checkout contains the expected files from sub-sub-project
    assert os.path.exists(os.path.join(checkoutdir, expected_file))


#
# Tests links which refer to non-existing elements or junctions
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
    "target,provenance",
    [
        # Target is a link to a non-existing local element
        ("link-target.bst", "link-target.bst [line 4 column 10]"),
        # Target is a stack depending on a link to a non-existing local element
        (
            "depends-on-link-target.bst",
            "link-target.bst [line 4 column 10]",
        ),
        # Depends on non-existing subproject element, via a local link
        (
            "linked-local-junction-target.bst",
            "linked-local-junction-target.bst [line 4 column 2]",
        ),
        # Depends on non-existing subsubproject element, via a local link
        (
            "linked-nested-junction-target.bst",
            "linked-nested-junction-target.bst [line 4 column 2]",
        ),
        # Depends on an element via a link to a non-existing local junction
        (
            "linked-local-junction.bst",
            "subproject-link-notfound.bst [line 4 column 10]",
        ),
        # Depends on an element via a link to a non-existing subproject junction
        (
            "linked-nested-junction.bst",
            "subsubproject-link-notfound.bst [line 4 column 10]",
        ),
        # Target is a link to a non-existing nested element referred to with a full path
        ("link-full-path.bst", "link-full-path.bst [line 4 column 10]"),
        # Target depends on a link to a non-existing nested element referred to with a full path
        ("target-full-path.bst", "link-full-path.bst [line 4 column 10]"),
    ],
)
def test_link_not_found(cli, tmpdir, datafiles, target, provenance):
    project = os.path.join(str(datafiles), "notfound")
    result = cli.run(project=project, args=["build", target])

    result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE)
    assert provenance in result.stderr


#
# Tests links with invalid configurations
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
    "target,expected_error,expected_reason",
    [
        # Test link which declares sources, either directly of via a dependency
        ("link-with-sources.bst", ErrorDomain.ELEMENT, "element-forbidden-sources"),
        ("target-link-with-sources.bst", ErrorDomain.ELEMENT, "element-forbidden-sources"),
        # Test link which declares dependencies, either directly of via a dependency
        ("link-with-dependencies.bst", ErrorDomain.LOAD, LoadErrorReason.LINK_FORBIDDEN_DEPENDENCIES),
    ],
)
def test_link_invalid_config(cli, tmpdir, datafiles, target, expected_error, expected_reason):
    project = os.path.join(str(datafiles), "invalid")
    result = cli.run(project=project, args=["show", target])
    result.assert_main_error(expected_error, expected_reason)


#
# Test including files across the boundry a link to a subproject's junction
#
@pytest.mark.datafiles(DATA_DIR)
def test_cross_link_junction_include(cli, tmpdir, datafiles):
    project = os.path.join(str(datafiles), "cross-link-junction-include")

    # Show the variables and parse our test variable from the subsubproject
    result = cli.run(project=project, args=["show", "--deps", "none", "--format", "%{vars}", "target.bst"])
    result.assert_success()

    # Read back some of our project defaults from the env
    variables = _yaml.load_data(result.output)
    assert variables.get_str("test") == "the test"


#
# Test two links to the same element, both links are dependencies of the build target.
#
@pytest.mark.datafiles(DATA_DIR)
def test_multiple_links_same_target(cli, tmpdir, datafiles):
    project = os.path.join(str(datafiles), "multiple-links-same-target")
    checkoutdir = os.path.join(str(tmpdir), "checkout")

    target = "target.bst"

    # Build, checkout
    result = cli.run(project=project, args=["build", target])
    result.assert_success()
    result = cli.run(project=project, args=["artifact", "checkout", target, "--directory", checkoutdir])
    result.assert_success()

    # Check that the checkout contains the expected files from sub-sub-project
    assert os.path.exists(os.path.join(checkoutdir, "hello.txt"))
