#
#  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._remotespec import RemoteSpec, RemoteType
from buildstream._project import Project
from buildstream.utils import _deduplicate
from buildstream import _yaml
from buildstream.exceptions import ErrorDomain, LoadErrorReason

from buildstream._testing import runcli
from buildstream._testing.runcli import cli  # pylint: disable=unused-import

from tests.testutils import dummy_context


DATA_DIR = os.path.dirname(os.path.realpath(__file__))
cache1 = RemoteSpec(RemoteType.ALL, url="https://example.com/cache1", push=True)
cache2 = RemoteSpec(RemoteType.ALL, url="https://example.com/cache2", push=False)
cache3 = RemoteSpec(RemoteType.ALL, url="https://example.com/cache3", push=False)
cache4 = RemoteSpec(RemoteType.ALL, url="https://example.com/cache4", push=False)
cache5 = RemoteSpec(RemoteType.ALL, url="https://example.com/cache5", push=False)
cache6 = RemoteSpec(RemoteType.ALL, url="https://example.com/cache6", push=True)
cache7 = RemoteSpec(RemoteType.INDEX, url="https://index.example.com/cache1", push=True)
cache8 = RemoteSpec(RemoteType.STORAGE, url="https://storage.example.com/cache1", push=True)


# Generate cache configuration fragments for the user config and project config files.
#
def configure_remote_caches(override_caches, project_caches=None, user_caches=None):
    type_strings = {RemoteType.INDEX: "index", RemoteType.STORAGE: "storage", RemoteType.ALL: "all"}

    if project_caches is None:
        project_caches = []

    if user_caches is None:
        user_caches = []

    user_config = {}
    if user_caches:
        user_config["artifacts"] = {
            "servers": [
                {"url": cache.url, "push": cache.push, "type": type_strings[cache.remote_type]}
                for cache in user_caches
            ]
        }

    if override_caches:
        user_config["projects"] = {
            "test": {
                "artifacts": {
                    "servers": [
                        {"url": cache.url, "push": cache.push, "type": type_strings[cache.remote_type]}
                        for cache in override_caches
                    ]
                }
            }
        }

    project_config = {}
    if project_caches:
        project_config.update(
            {
                "artifacts": [
                    {"url": cache.url, "push": cache.push, "type": type_strings[cache.remote_type]}
                    for cache in project_caches
                ]
            }
        )

    return user_config, project_config


# Test that parsing the remote artifact cache locations produces the
# expected results.
@pytest.mark.parametrize(
    "override_caches, project_caches, user_caches",
    [
        # The leftmost cache is the highest priority one in all cases here.
        pytest.param([], [], [], id="empty-config"),
        pytest.param([], [], [cache1, cache2], id="user-config"),
        pytest.param([], [cache1, cache2], [cache3], id="project-config"),
        pytest.param([cache1], [cache2], [cache3], id="project-override-in-user-config"),
        pytest.param([cache1, cache2], [cache3, cache4], [cache5, cache6], id="list-order"),
        pytest.param([cache1, cache2, cache1], [cache2], [cache2, cache1], id="duplicates"),
        pytest.param([cache7, cache8], [], [cache1], id="split-caches"),
    ],
)
def test_artifact_cache_precedence(tmpdir, override_caches, project_caches, user_caches):
    # Produce a fake user and project config with the cache configuration.
    user_config, project_config = configure_remote_caches(override_caches, project_caches, user_caches)
    project_config["name"] = "test"
    project_config["min-version"] = "2.0"

    project_dir = tmpdir.mkdir("project")
    project_config_file = str(project_dir.join("project.conf"))
    _yaml.roundtrip_dump(project_config, file=project_config_file)

    with runcli.configured(str(tmpdir), user_config) as user_config_file, dummy_context(
        config=user_config_file
    ) as context:
        project = Project(str(project_dir), context)
        project.ensure_fully_loaded()

        # Check the specs which the artifact cache thinks are configured
        context.initialize_remotes(True, True, None, None)
        artifactcache = context.artifactcache
        parsed_cache_specs = artifactcache._project_specs[project.name]

        # Verify that it was correctly read.
        expected_cache_specs = list(_deduplicate(override_caches or user_caches))
        expected_cache_specs = list(_deduplicate(expected_cache_specs + project_caches))
        assert parsed_cache_specs == expected_cache_specs


# Assert that if either the client key or client cert is specified
# without specifying its counterpart, we get a comprehensive LoadError
# instead of an unhandled exception.
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("config_key, config_value", [("client-cert", "client.crt"), ("client-key", "client.key")])
def test_missing_certs(cli, datafiles, config_key, config_value):
    project = os.path.join(datafiles, "missing-certs")

    project_conf = {
        "name": "test",
        "min-version": "2.0",
        "artifacts": {"url": "https://cache.example.com:12345", "push": "true", "auth": {config_key: config_value}},
    }
    project_conf_file = os.path.join(project, "project.conf")
    _yaml.roundtrip_dump(project_conf, project_conf_file)

    # Use `pull` here to ensure we try to initialize the remotes, triggering the error
    #
    # This does not happen for a simple `bst show`.
    result = cli.run(project=project, args=["artifact", "pull", "element.bst"])
    result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_DATA)


# Assert that BuildStream complains when someone attempts to define
# only one type of storage.
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
    "override_caches, project_caches, user_caches",
    [
        # The leftmost cache is the highest priority one in all cases here.
        pytest.param([], [], [cache7], id="index-user"),
        pytest.param([], [], [cache8], id="storage-user"),
        pytest.param([], [cache7], [], id="index-project"),
        pytest.param([], [cache8], [], id="storage-project"),
        pytest.param([cache7], [], [], id="index-override"),
        pytest.param([cache8], [], [], id="storage-override"),
    ],
)
def test_only_one(cli, datafiles, override_caches, project_caches, user_caches):
    project = os.path.join(datafiles, "only-one")

    # Produce a fake user and project config with the cache configuration.
    user_config, project_config = configure_remote_caches(override_caches, project_caches, user_caches)
    project_config["name"] = "test"
    project_config["min-version"] = "2.0"

    cli.configure(user_config)

    project_config_file = os.path.join(project, "project.conf")
    _yaml.roundtrip_dump(project_config, file=project_config_file)

    # Use `pull` here to ensure we try to initialize the remotes, triggering the error
    #
    # This does not happen for a simple `bst show`.
    result = cli.run(project=project, args=["artifact", "pull", "element.bst"])
    result.assert_main_error(ErrorDomain.STREAM, None)


@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
    "artifacts_config",
    (
        [
            {
                "url": "http://localhost.test",
                "auth": {
                    "server-cert": "~/server.crt",
                    "client-cert": "~/client.crt",
                    "client-key": "~/client.key",
                },
            }
        ],
        [
            {
                "url": "http://localhost.test",
                "auth": {
                    "server-cert": "~/server.crt",
                    "client-cert": "~/client.crt",
                    "client-key": "~/client.key",
                },
            },
            {
                "url": "http://localhost2.test",
                "auth": {
                    "server-cert": "~/server2.crt",
                    "client-cert": "~/client2.crt",
                    "client-key": "~/client2.key",
                },
            },
        ],
    ),
)
@pytest.mark.parametrize("in_user_config", [True, False])
def test_paths_for_artifact_config_are_expanded(tmpdir, monkeypatch, artifacts_config, in_user_config):
    # Produce a fake user and project config with the cache configuration.
    # user_config, project_config = configure_remote_caches(override_caches, project_caches, user_caches)
    # project_config['name'] = 'test'

    monkeypatch.setenv("HOME", str(tmpdir.join("homedir")))

    project_config = {"name": "test", "min-version": "2.0"}
    user_config = {}
    if in_user_config:
        user_config["artifacts"] = {"servers": artifacts_config}
    else:
        project_config["artifacts"] = artifacts_config

    user_config_file = str(tmpdir.join("buildstream.conf"))
    _yaml.roundtrip_dump(user_config, file=user_config_file)

    project_dir = tmpdir.mkdir("project")
    project_config_file = str(project_dir.join("project.conf"))
    _yaml.roundtrip_dump(project_config, file=project_config_file)

    with dummy_context(config=user_config_file) as context:
        project = Project(str(project_dir), context)
        project.ensure_fully_loaded()

        # Check the specs which the artifact cache thinks are configured
        context.initialize_remotes(True, True, None, None)
        artifactcache = context.artifactcache
        parsed_cache_specs = artifactcache._project_specs[project.name]

    if isinstance(artifacts_config, dict):
        artifacts_config = [artifacts_config]

    # Build expected artifact config
    artifacts_config = [
        RemoteSpec(
            RemoteType.ALL,
            config["url"],
            push=False,
            server_cert=os.path.expanduser(config["auth"]["server-cert"]),
            client_cert=os.path.expanduser(config["auth"]["client-cert"]),
            client_key=os.path.expanduser(config["auth"]["client-key"]),
        )
        for config in artifacts_config
    ]

    assert parsed_cache_specs == artifacts_config
