# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright (C) 2017-2018 Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
from unittest import mock

import fixtures
from testtools.matchers import Contains, Equals

from snapcraft import storeapi
from snapcraft.cli import assertions
from tests import unit

from . import StoreCommandsBaseTestCase


class CollaborateBaseTestCase(StoreCommandsBaseTestCase):
    def setUp(self):
        super().setUp()
        patcher = mock.patch("subprocess.Popen")
        self.popen_mock = patcher.start()
        process_mock = mock.Mock()
        process_mock.returncode = 0
        process_mock.communicate.return_value = [b"foo", b""]
        self.popen_mock.return_value = process_mock
        self.addCleanup(patcher.stop)
        patcher = mock.patch("snapcraft.cli.assertions._edit_developers")
        self.edit_developers_mock = patcher.start()
        self.edit_developers_mock.return_value = [
            {"developer-id": "dummy-id", "since": "2015-07-19 19:30:00"}
        ]
        self.addCleanup(patcher.stop)
        self.client.login(email="dummy", password="test correct password")


class CollaboratorsCommandTestCase(CollaborateBaseTestCase):

    command_name = "edit-collaborators"

    def test_collaborators_without_snap_name_must_error(self):
        result = self.run_command([self.command_name])

        self.assertThat(result.exit_code, Equals(2))
        self.assertThat(result.output, Contains("Usage:"))

    def test_collaborators_update_developers(self):
        result = self.run_command(
            [self.command_name, "test-snap-with-dev", "--key-name", "key-name"]
        )

        self.assertThat(result.exit_code, Equals(0))
        self.popen_mock.assert_called_with(
            ["snap", "sign", "-k", "key-name"], stderr=-1, stdin=-1, stdout=-1
        )

    def test_collaborators_add_developers(self):
        result = self.run_command([self.command_name, "core", "--key-name", "key-name"])

        self.assertThat(result.exit_code, Equals(0))
        self.popen_mock.assert_called_with(
            ["snap", "sign", "-k", "key-name"], stderr=-1, stdin=-1, stdout=-1
        )

    def test_no_prior_developer_assertion(self):
        result = self.run_command(
            [self.command_name, "core-no-dev", "--key-name", "key-name"]
        )

        self.assertThat(result.exit_code, Equals(0))
        self.popen_mock.assert_called_with(
            ["snap", "sign", "-k", "key-name"], stderr=-1, stdin=-1, stdout=-1
        )

    def test_collaborate_no_revoke_uploads_request(self):
        result = self.run_command(
            [self.command_name] + ["no-revoked", "--key-name", "key-name"]
        )

        self.assertThat(result.exit_code, Equals(0))
        self.assertThat(
            result.output,
            Contains("This will revoke the following collaborators: 'this'"),
        )
        self.assertThat(
            result.output, Contains("Are you sure you want to continue? [y/N]:")
        )
        self.assertThat(
            result.output,
            Contains("The collaborators for this snap have not been altered."),
        )

    def test_collaborate_unchanged_collaborators(self):
        self.edit_developers_mock.return_value = [
            {
                "developer-id": "test-dev-id",
                "since": "2017-02-10 08:35:00",
                "until": "2018-02-10 08:35:00",
            }
        ]

        result = self.run_command(
            [self.command_name] + ["test-snap-with-dev", "--key-name", "key-name"]
        )

        self.assertThat(result.exit_code, Equals(0))
        self.assertThat(
            result.output, Contains("Aborting due to unchanged collaborators list.")
        )


class CollaborateErrorsTestCase(CollaborateBaseTestCase):

    command_name = "edit-collaborators"

    def test_collaborate_snap_not_found(self):
        raised = self.assertRaises(
            storeapi.errors.SnapNotFoundError,
            self.run_command,
            [self.command_name, "notfound", "--key-name", "key-name"],
        )

        self.assertThat(str(raised), Contains("Snap 'notfound' was not found"))

    def test_collaborate_bad_request(self):
        raised = self.assertRaises(
            storeapi.errors.StoreValidationError,
            self.run_command,
            [self.command_name, "badrequest", "--key-name", "key-name"],
        )

        self.assertThat(
            str(raised),
            Contains(
                "Received error 400: 'The given `snap-id` does not match "
                "the assertion.'"
            ),
        )

    def test_collaborate_yes_revoke_uploads_request(self):
        raised = self.assertRaises(
            storeapi.errors.StoreValidationError,
            self.run_command,
            [self.command_name, "revoked", "--key-name", "key-name"],
            input="y\n",
        )

        self.assertThat(
            str(raised),
            Contains(
                'Received error 409: "{}"'.format(
                    "The assertion's `developers` would revoke existing uploads."
                )
            ),
        )


class EditDevelopersTestCase(unit.TestCase):
    def setUp(self):
        super().setUp()
        patcher = mock.patch("subprocess.check_call")
        patcher.start()
        self.addCleanup(patcher.stop)

    def test_edit_developers_must_write_header_and_developers(self):
        developers_from_assertion = [
            {
                "developer-id": "test-dev-id1",
                "since": "2017-02-10T08:35:00.000000Z",
                "until": "2018-02-10T08:35:00.000000Z",
            },
            {
                "developer-id": "test-dev-id2",
                "since": "2016-02-10T08:35:00.000000Z",
                "until": "2019-02-10T08:35:00.000000Z",
            },
        ]

        existing_developers = (
            "developers:\n"
            "- developer-id: test-dev-id1\n"
            "  since: '2017-02-10 08:35:00'\n"
            "  until: '2018-02-10 08:35:00'\n"
            "- developer-id: test-dev-id2\n"
            "  since: '2016-02-10 08:35:00'\n"
            "  until: '2019-02-10 08:35:00'\n"
        )
        expected_written = assertions._COLLABORATION_HEADER + "\n" + existing_developers

        with mock.patch(
            "builtins.open", new_callable=mock.mock_open, read_data=expected_written
        ) as mock_open:
            developers_for_assertion = assertions._update_developers(
                developers_from_assertion
            )

        written = ""
        for call in mock_open().write.call_args_list:
            written += str(call.call_list()[0][0][0])
        self.assertThat(written, Equals(expected_written))
        self.assertThat(developers_for_assertion, Equals(developers_from_assertion))

    def test_edit_developers_must_return_new_developers(self):
        developers_from_assertion = [
            {
                "developer-id": "test-dev-id1",
                "since": "2017-02-10T08:35:00.000000Z",
                "until": "2018-02-10T08:35:00.000000Z",
            }
        ]

        new_developers = (
            "developers:\n"
            "- developer-id: test-dev-id1\n"
            "  since: '2017-02-10 08:35:00'\n"
            "  until: '2018-02-10 08:35:00'\n"
            "- developer-id: test-dev-id2\n"
            "  since: '2016-02-10 08:35:00'\n"
            "  until: '2019-02-10 08:35:00'\n"
        )

        with mock.patch(
            "builtins.open", new_callable=mock.mock_open, read_data=new_developers
        ):
            developers_for_assertion = assertions._update_developers(
                developers_from_assertion
            )

        expected_developers = developers_from_assertion + [
            {
                "developer-id": "test-dev-id2",
                "since": "2016-02-10T08:35:00.000000Z",
                "until": "2019-02-10T08:35:00.000000Z",
            }
        ]
        self.assertThat(developers_for_assertion, Equals(expected_developers))


class EditDevelopersOpenEditorTestCase(unit.TestCase):
    def setUp(self):
        super().setUp()
        patcher = mock.patch("subprocess.check_call")
        self.check_call_mock = patcher.start()
        self.addCleanup(patcher.stop)

    def test_edit_collaborators_must_open_editor_default(self):
        self.useFixture(fixtures.EnvironmentVariable("EDITOR", None))
        assertions._edit_developers({})
        self.check_call_mock.assert_called_with(["vi", mock.ANY])

    def test_edit_collaborators_must_open_editor_environment(self):
        self.useFixture(fixtures.EnvironmentVariable("EDITOR", "test-editor"))
        assertions._edit_developers({})
        self.check_call_mock.assert_called_with(["test-editor", mock.ANY])
