# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
#
#   Copyright 2018-2019 Fetch.AI Limited
#
#   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.
#
# ------------------------------------------------------------------------------

"""This test module contains the tests for the `aea search` sub-command."""

import json
import os
import shutil
import tempfile
from pathlib import Path
from unittest import TestCase, mock

import jsonschema
from jsonschema import Draft4Validator

from aea.cli import cli

from packages.fetchai.skills.echo import PUBLIC_ID as ECHO_SKILL_PUBLIC_ID
from packages.fetchai.skills.error import PUBLIC_ID as ERROR_SKILL_PUBLIC_ID

from tests.conftest import (
    AGENT_CONFIGURATION_SCHEMA,
    AUTHOR,
    CLI_LOG_OPTION,
    CONFIGURATION_SCHEMA_DIR,
    CliRunner,
    ROOT_DIR,
)
from tests.test_cli.constants import FORMAT_ITEMS_SAMPLE_OUTPUT


class TestSearchProtocolsLocal:
    """Test that the command 'aea search protocols' works as expected."""

    @classmethod
    def setup_class(cls):
        """Set the test up."""
        cls.cwd = os.getcwd()
        cls.runner = CliRunner()

    @mock.patch("aea.cli.search.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT)
    def test_correct_output_default_registry(self, _):
        """Test that the command has printed the correct output when using the default registry."""
        self.result = self.runner.invoke(
            cli,
            [*CLI_LOG_OPTION, "search", "--local", "protocols"],
            standalone_mode=False,
        )
        assert self.result.output == (
            'Searching for ""...\n'
            "Protocols found:\n\n"
            "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT)
        )

    @classmethod
    def teardown_class(cls):
        """Tear the test down."""
        os.chdir(cls.cwd)


class TestSearchContractsLocal(TestCase):
    """Test that the command 'aea search contracts' works as expected."""

    def setUp(self):
        """Set the test up."""
        self.runner = CliRunner()

    @mock.patch("aea.cli.search.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT)
    @mock.patch("aea.cli.search._search_items_locally", return_value=["item1"])
    def test_search_contracts_positive(self, *mocks):
        """Test search contracts command positive result."""
        result = self.runner.invoke(
            cli,
            [*CLI_LOG_OPTION, "search", "--local", "contracts"],
            standalone_mode=False,
        )
        assert result.output == (
            'Searching for ""...\n'
            "Contracts found:\n\n"
            "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT)
        )

    @mock.patch("aea.cli.search.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT)
    @mock.patch(
        "aea.cli.search.request_api", return_value={"results": ["item1"], "count": 1}
    )
    def test_search_contracts_registry_positive(self, *mocks):
        """Test search contracts in registry command positive result."""
        result = self.runner.invoke(
            cli, [*CLI_LOG_OPTION, "search", "contracts"], standalone_mode=False,
        )
        assert result.output == (
            'Searching for ""...\n'
            "Contracts found:\n\n"
            "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT)
        )


class TestSearchConnectionsLocal:
    """Test that the command 'aea search connections' works as expected."""

    @classmethod
    def setup_class(cls):
        """Set the test up."""
        cls.cwd = os.getcwd()
        cls.runner = CliRunner()

    @mock.patch("aea.cli.search.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT)
    def test_correct_output_default_registry(self, _):
        """Test that the command has printed the correct output when using the default registry."""
        self.result = self.runner.invoke(
            cli,
            [*CLI_LOG_OPTION, "search", "--local", "connections"],
            standalone_mode=False,
        )
        assert self.result.output == (
            'Searching for ""...\n'
            "Connections found:\n\n"
            "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT)
        )

    @classmethod
    def teardown_class(cls):
        """Tear the test down."""
        os.chdir(cls.cwd)


class TestSearchSkillsLocal:
    """Test that the command 'aea search skills' works as expected."""

    @classmethod
    def setup_class(cls):
        """Set the test up."""
        cls.cwd = os.getcwd()
        cls.runner = CliRunner()

    @mock.patch("aea.cli.search.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT)
    def test_correct_output_default_registry(self, _):
        """Test that the command has printed the correct output when using the default registry."""
        self.result = self.runner.invoke(
            cli, [*CLI_LOG_OPTION, "search", "--local", "skills"], standalone_mode=False
        )
        assert self.result.output == (
            'Searching for ""...\n'
            "Skills found:\n\n"
            "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT)
        )

    @classmethod
    def teardown_class(cls):
        """Tear the test down."""
        os.chdir(cls.cwd)


class TestSearchAgentsLocal:
    """Test that the command 'aea search agents' works as expected."""

    @classmethod
    def setup_class(cls):
        """Set the test up."""
        cls.schema = json.load(open(AGENT_CONFIGURATION_SCHEMA))
        cls.resolver = jsonschema.RefResolver(
            "file://{}/".format(Path(CONFIGURATION_SCHEMA_DIR).absolute()), cls.schema
        )
        cls.validator = Draft4Validator(cls.schema, resolver=cls.resolver)

        cls.cwd = os.getcwd()
        cls.runner = CliRunner()

        cls.t = tempfile.mkdtemp()
        dir_path = Path("packages")
        tmp_dir = cls.t / dir_path
        src_dir = cls.cwd / Path(ROOT_DIR, dir_path)
        shutil.copytree(str(src_dir), str(tmp_dir))
        os.chdir(cls.t)
        cls.cli_config_file = f"{cls.t}/cli_config.yaml"
        cls.cli_config_patch = mock.patch(
            "aea.cli.utils.config.CLI_CONFIG_PATH", cls.cli_config_file
        )
        cls.cli_config_patch.start()
        result = cls.runner.invoke(
            cli, [*CLI_LOG_OPTION, "init", "--local", "--author", AUTHOR]
        )
        assert result.exit_code == 0
        result = cls.runner.invoke(
            cli,
            [*CLI_LOG_OPTION, "create", "--local", "myagent"],
            standalone_mode=False,
        )
        assert result.exit_code == 0

        os.chdir(Path(cls.t, "myagent"))
        result = cls.runner.invoke(
            cli,
            [
                *CLI_LOG_OPTION,
                "config",
                "set",
                "agent.description",
                "Some description.",
            ],
            standalone_mode=False,
            catch_exceptions=False,
        )
        assert result.exit_code == 0
        result = cls.runner.invoke(
            cli, [*CLI_LOG_OPTION, "publish", "--local"], standalone_mode=False
        )
        assert result.exit_code == 0
        cls.result = cls.runner.invoke(
            cli, [*CLI_LOG_OPTION, "search", "--local", "agents"], standalone_mode=False
        )

    def test_correct_output_default_registry(self):
        """Test that the command has printed the correct output when using the default registry."""
        expected = [
            ('Searching for ""...\n' "Agents found:\n\n"),
            (
                "------------------------------\n"
                "Public ID: default_author/myagent:0.1.0\n"
                "Name: myagent\n"
                "Description: Some description.\n"
                "Author: default_author\n"
                "Version: 0.1.0\n"
                "------------------------------\n\n"
            ),
        ]
        assert [strings in self.result.output for strings in expected]

    @classmethod
    def teardown_class(cls):
        """Tear the test down."""
        cls.cli_config_patch.stop()
        os.chdir(cls.cwd)
        try:
            shutil.rmtree(cls.t)
        except (OSError, IOError):
            pass


@mock.patch(
    "aea.cli.search.request_api",
    return_value={"results": ["correct", "results"], "count": 2},
)
@mock.patch("aea.cli.search.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT)
class RegistrySearchTestCase(TestCase):
    """Test case for search --registry CLI command."""

    def setUp(self):
        """Set it up."""
        self.runner = CliRunner()

    def test_search_connections_positive(self, format_items_mock, request_api_mock):
        """Test for CLI search --registry connections positive result."""
        result = self.runner.invoke(
            cli,
            [*CLI_LOG_OPTION, "search", "connections", "--query=some"],
            standalone_mode=False,
        )
        expected_output = (
            'Searching for "some"...\n'
            "Connections found:\n\n"
            "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT)
        )
        self.assertEqual(result.output, expected_output)
        request_api_mock.assert_called_once_with(
            "GET", "/connections", params={"search": "some", "page": 1}
        )
        format_items_mock.assert_called_once_with(["correct", "results"])

    def test_search_agents_positive(self, format_items_mock, request_api_mock):
        """Test for CLI search --registry agents positive result."""
        result = self.runner.invoke(
            cli,
            [*CLI_LOG_OPTION, "search", "agents", "--query=some"],
            standalone_mode=False,
        )
        expected_output = (
            'Searching for "some"...\n'
            "Agents found:\n\n"
            "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT)
        )
        self.assertEqual(result.output, expected_output)
        request_api_mock.assert_called_once_with(
            "GET", "/agents", params={"search": "some", "page": 1}
        )
        format_items_mock.assert_called_once_with(["correct", "results"])

    def test_search_protocols_positive(self, format_items_mock, request_api_mock):
        """Test for CLI search --registry protocols positive result."""
        result = self.runner.invoke(
            cli,
            [*CLI_LOG_OPTION, "search", "protocols", "--query=some"],
            standalone_mode=False,
        )
        expected_output = (
            'Searching for "some"...\n'
            "Protocols found:\n\n"
            "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT)
        )
        self.assertEqual(result.output, expected_output)
        request_api_mock.assert_called_once_with(
            "GET", "/protocols", params={"search": "some", "page": 1}
        )
        format_items_mock.assert_called_once_with(["correct", "results"])

    def test_search_skills_positive(self, format_items_mock, request_api_mock):
        """Test for CLI search --registry skills positive result."""
        result = self.runner.invoke(
            cli,
            [*CLI_LOG_OPTION, "search", "skills", "--query=some"],
            standalone_mode=False,
        )
        expected_output = (
            'Searching for "some"...\n'
            "Skills found:\n\n"
            "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT)
        )
        self.assertEqual(result.output, expected_output)
        request_api_mock.assert_called_once_with(
            "GET", "/skills", params={"search": "some", "page": 1}
        )
        format_items_mock.assert_called_once_with(["correct", "results"])


class TestSearchWithRegistryInSubfolderLocal:
    """Test the search when the registry directory is a subfolder of the current path."""

    @classmethod
    def setup_class(cls):
        """Set the test up."""
        cls.cwd = os.getcwd()
        cls.runner = CliRunner()
        cls.t = tempfile.mkdtemp()
        os.chdir(cls.t)

        # copy the packages directory in the temporary test directory.
        shutil.copytree(Path(ROOT_DIR, "packages"), Path(cls.t, "packages"))

        # remove all the skills except the echo and error skill (to make testing easier).
        for p in Path(cls.t, "packages", "fetchai", "skills").iterdir():
            if p.name not in ["echo", "error"] and p.is_dir():
                shutil.rmtree(p)

        cls.result = cls.runner.invoke(
            cli, [*CLI_LOG_OPTION, "search", "--local", "skills"], standalone_mode=False
        )

    def test_exit_code_equal_to_zero(self):
        """Test that the exit code is equal to 0 (i.e. success)."""
        assert self.result.exit_code == 0

    def test_correct_output(self,):
        """Test that the command has printed the correct output.."""
        public_id_echo = ECHO_SKILL_PUBLIC_ID
        public_id_error = ERROR_SKILL_PUBLIC_ID
        expected = [
            ('Searching for ""...\n' "Skills found:\n\n"),
            (
                "------------------------------\n"
                "Public ID: {}\n"
                "Name: echo\n"
                "Description: The echo skill implements simple echo functionality.\n"
                "Author: fetchai\n"
                "Version: {}\n"
                "------------------------------\n"
            ).format(str(public_id_echo), str(public_id_echo.version)),
            (
                "------------------------------\n"
                "Public ID: {}\n"
                "Name: error\n"
                "Description: The error skill implements basic error handling required by all AEAs.\n"
                "Author: fetchai\n"
                "Version: {}\n"
                "------------------------------\n\n"
            ).format(str(public_id_error), str(public_id_error.version),),
        ]
        assert [strings in self.result.output for strings in expected]

    @classmethod
    def teardown_class(cls):
        """Tear the test down."""
        os.chdir(cls.cwd)
        try:
            shutil.rmtree(cls.t)
        except (OSError, IOError):
            pass


class TestSearchInAgentDirectoryLocal:
    """Test the search when we are in the agent directory."""

    @classmethod
    def setup_class(cls):
        """Set the test up."""
        cls.cwd = os.getcwd()
        cls.runner = CliRunner()
        cls.t = tempfile.mkdtemp()
        os.chdir(cls.t)

        # copy the packages directory in the temporary test directory.
        shutil.copytree(Path(ROOT_DIR, "packages"), Path(cls.t, "packages"))

        # remove all the skills except the echo and error skill (to make testing easier).
        for p in Path(cls.t, "packages", "fetchai", "skills").iterdir():
            if p.name not in ["echo", "error"] and p.is_dir():
                shutil.rmtree(p)

        result = cls.runner.invoke(
            cli, [*CLI_LOG_OPTION, "init", "--local", "--author", AUTHOR]
        )
        assert result.exit_code == 0

        # create an AEA proejct and enter into it.
        result = cls.runner.invoke(
            cli,
            [*CLI_LOG_OPTION, "create", "--local", "myagent"],
            standalone_mode=False,
        )
        assert result.exit_code == 0
        os.chdir(Path(cls.t, "myagent"))

        cls.result = cls.runner.invoke(
            cli, [*CLI_LOG_OPTION, "search", "--local", "skills"], standalone_mode=False
        )

    def test_exit_code_equal_to_zero(self):
        """Test that the exit code is equal to 0 (i.e. success)."""
        assert self.result.exit_code == 0

    def test_correct_output(self,):
        """Test that the command has printed the correct output.."""
        public_id_echo = ECHO_SKILL_PUBLIC_ID
        public_id_error = ERROR_SKILL_PUBLIC_ID
        expected = [
            ('Searching for ""...\n' "Skills found:\n\n"),
            (
                "------------------------------\n"
                "Public ID: {}\n"
                "Name: echo\n"
                "Description: The echo skill implements simple echo functionality.\n"
                "Author: fetchai\n"
                "Version: {}\n"
                "------------------------------\n"
            ).format(str(public_id_echo), str(public_id_echo.version)),
            (
                "------------------------------\n"
                "Public ID: {}\n"
                "Name: error\n"
                "Description: The error skill implements basic error handling required by all AEAs.\n"
                "Author: fetchai\n"
                "Version: {}\n"
                "------------------------------\n\n"
            ).format(str(public_id_error), str(public_id_error.version),),
        ]
        assert [strings in self.result.output for strings in expected]

    @classmethod
    def teardown_class(cls):
        """Tear the test down."""
        os.chdir(cls.cwd)
        try:
            shutil.rmtree(cls.t)
        except (OSError, IOError):
            pass
