#
#  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.exceptions import ErrorDomain
from buildstream._testing import cli  # pylint: disable=unused-import
from tests.testutils import create_artifact_share
from . import configure_project

# Project directory
DATA_DIR = os.path.join(
    os.path.dirname(os.path.realpath(__file__)),
    "artifact-show",
)
SIMPLE_DIR = os.path.join(
    os.path.dirname(os.path.realpath(__file__)),
    "simple",
)


# Test artifact show
@pytest.mark.datafiles(DATA_DIR)
def test_artifact_show_element_name(cli, tmpdir, datafiles):
    project = str(datafiles)
    element = "target.bst"

    result = cli.run(project=project, args=["artifact", "show", element])
    result.assert_success()
    assert "not cached {}".format(element) in result.output

    result = cli.run(project=project, args=["build", element])
    result.assert_success()

    result = cli.run(project=project, args=["artifact", "show", element])
    result.assert_success()
    assert "cached {}".format(element) in result.output


# Test artifact show on a failed element
@pytest.mark.datafiles(DATA_DIR)
def test_artifact_show_failed_element(cli, tmpdir, datafiles):
    project = str(datafiles)
    element = "manual.bst"

    result = cli.run(project=project, args=["artifact", "show", element])
    result.assert_success()
    assert "not cached {}".format(element) in result.output

    result = cli.run(project=project, args=["build", element])
    result.assert_task_error(ErrorDomain.SANDBOX, "missing-command")

    result = cli.run(project=project, args=["artifact", "show", element])
    result.assert_success()
    assert "failed {}".format(element) in result.output


# Test artifact show with a deleted dependency
@pytest.mark.datafiles(DATA_DIR)
def test_artifact_show_element_missing_deps(cli, tmpdir, datafiles):
    project = str(datafiles)
    element = "target.bst"
    dependency = "import-bin.bst"

    result = cli.run(project=project, args=["build", element])
    result.assert_success()

    result = cli.run(project=project, args=["artifact", "delete", dependency])
    result.assert_success()

    result = cli.run(project=project, args=["artifact", "show", "--deps", "all", element])
    result.assert_success()
    assert "not cached {}".format(dependency) in result.output
    assert "cached {}".format(element) in result.output


# Test artifact show with artifact ref
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("with_project", [True, False], ids=["with-project", "without-project"])
def test_artifact_show_artifact_name(cli, tmpdir, datafiles, with_project):
    project = str(datafiles)
    element = "target.bst"

    result = cli.run(project=project, args=["build", element])
    result.assert_success()

    cache_key = cli.get_element_key(project, element)
    artifact_ref = "test/target/" + cache_key

    # Delete the project.conf if we're going to try this without a project
    if not with_project:
        os.remove(os.path.join(project, "project.conf"))

    result = cli.run(project=project, args=["artifact", "show", artifact_ref])
    result.assert_success()
    assert "cached {}".format(artifact_ref) in result.output


# Test artifact show glob behaviors
@pytest.mark.datafiles(SIMPLE_DIR)
@pytest.mark.parametrize(
    "pattern,expected_prefixes",
    [
        # List only artifact results in the test/project
        #
        ("test/**", ["test/target/", "test/compose-all/", "test/import-bin", "test/import-dev"]),
        # List only artifact results by their .bst element names
        #
        ("**.bst", ["import-bin.bst", "import-dev.bst", "compose-all.bst", "target.bst", "subdir/target.bst"]),
        # List only the import artifact results
        #
        ("import*.bst", ["import-bin.bst", "import-dev.bst"]),
    ],
    ids=["test/**", "**.bst", "import*.bst"],
)
def test_artifact_show_glob(cli, tmpdir, datafiles, pattern, expected_prefixes):
    project = str(datafiles)

    result = cli.run(project=project, args=["build", "target.bst"])
    result.assert_success()

    result = cli.run(project=project, args=["artifact", "show", pattern])
    result.assert_success()

    output = result.output.strip().splitlines()

    # Assert that the number of results match the number of expected results
    assert len(output) == len(expected_prefixes)

    # Assert that each expected result was found.
    for expected_prefix in expected_prefixes:
        found = False
        for result_line in output:
            result_split = result_line.split()
            if result_split[-1].startswith(expected_prefix):
                found = True
                break
        assert found, "Expected result {} not found".format(expected_prefix)


# Test artifact show artifact in remote
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("config", ["config-project", "config-user", "config-cli"])
@pytest.mark.parametrize("by_element_name", [True, False], ids=["by-element-name", "by-artifact-name"])
def test_artifact_show_available_remotely(cli, tmpdir, datafiles, config, by_element_name):
    project = str(datafiles)
    element = "target.bst"

    #
    # Skip this configuration, BuildStream intentionally ignores the local project.conf
    # if an artifact name is specified.
    #
    if config == "config-project" and not by_element_name:
        pytest.skip("No project.conf in context")

    # Set up remote and local shares
    local_cache = os.path.join(str(tmpdir), "artifacts")
    cli.configure(
        {
            "cachedir": local_cache,
        }
    )

    with create_artifact_share(os.path.join(str(tmpdir), "remote")) as remote:
        extra_cli_args = []
        if config == "config-project":
            configure_project(
                project,
                {
                    "artifacts": [
                        {
                            "url": remote.repo,
                            "push": True,
                        }
                    ]
                },
            )
        elif config == "config-user":
            cli.configure(
                {
                    "artifacts": {"servers": [{"url": remote.repo, "push": True}]},
                }
            )
        else:
            extra_cli_args = ["--artifact-remote", remote.repo]

        # Build the element
        result = cli.run(project=project, args=["build"] + extra_cli_args + [element])
        result.assert_success()

        artifact_name = cli.get_artifact_name(project, "test", element)

        # Make sure it's in the share
        assert remote.get_artifact(artifact_name)

        # Delete the artifact from the local cache
        result = cli.run(project=project, args=["artifact", "delete", element])
        result.assert_success()
        assert cli.get_element_state(project, element) != "cached"

        # Do the artifact show and assert
        element_or_artifact = element if by_element_name else artifact_name
        result = cli.run(project=project, args=["artifact", "show"] + extra_cli_args + [element_or_artifact])
        result.assert_success()
        assert "available {}".format(element_or_artifact) in result.output


# Test out --ignore-project-artifact-remotes
@pytest.mark.datafiles(DATA_DIR)
def test_artifact_show_ignore_project_remotes(cli, tmpdir, datafiles):
    project = str(datafiles)
    element = "target.bst"

    # Set up remote and local shares
    local_cache = os.path.join(str(tmpdir), "artifacts")
    cli.configure(
        {
            "cachedir": local_cache,
        }
    )

    with create_artifact_share(os.path.join(str(tmpdir), "remote")) as remote:
        configure_project(
            project,
            {
                "artifacts": [
                    {
                        "url": remote.repo,
                        "push": True,
                    }
                ]
            },
        )

        # Build the element
        result = cli.run(project=project, args=["build", element])
        result.assert_success()

        # Make sure it's in the share
        assert remote.get_artifact(cli.get_artifact_name(project, "test", element))

        # Delete the artifact from the local cache
        result = cli.run(project=project, args=["artifact", "delete", element])
        result.assert_success()
        assert cli.get_element_state(project, element) != "cached"

        # It is available remotely with the project configured remote
        result = cli.run(project=project, args=["artifact", "show", element])
        result.assert_success()
        assert "available {}".format(element) in result.output

        # Ignoring project remotes, it is not found in any remote
        result = cli.run(project=project, args=["artifact", "show", "--ignore-project-artifact-remotes", element])
        result.assert_success()
        assert "not cached {}".format(element) in result.output
