from pathlib import Path
from unittest import TestCase
from unittest.mock import patch
from samcli.lib.cookiecutter.interactive_flow import InteractiveFlow
from samcli.lib.cookiecutter.question import Question, Choice, Confirm


class TestInteractiveFlow(TestCase):
    _ANY_KEY = "any key"
    _ANY_TEXT = "any text"
    _ANY_ANSWER = "any answer"
    _PARTICULAR_ANSWER = "particular answer"
    _FIRST_QUESTION = Question(key="1st", text=_ANY_TEXT, default_next_question_key="2nd")
    _SECOND_QUESTION = Confirm(
        key="2nd",
        text=_ANY_TEXT,
        next_question_map={_PARTICULAR_ANSWER: "1st"},
        default_next_question_key="3rd",
    )
    _THIRD_QUESTION = Choice(key="3rd", text=_ANY_TEXT, options=["option1", "option2"])
    _QUESTIONS = {"1st": _FIRST_QUESTION, "2nd": _SECOND_QUESTION, "3rd": _THIRD_QUESTION}

    def setUp(self) -> None:
        self.flow = InteractiveFlow(questions=self._QUESTIONS, first_question_key=self._FIRST_QUESTION.key)

    def test_create_interactive_flow(self):
        self.assertEqual(self.flow._questions, self._QUESTIONS)
        self.assertEqual(self.flow._first_question_key, self._FIRST_QUESTION.key)

    def test_advance_to_next_question(self):
        self.assertEqual(self.flow.advance_to_next_question(), self._FIRST_QUESTION)
        self.assertEqual(self.flow.advance_to_next_question(self._ANY_ANSWER), self._SECOND_QUESTION)
        self.assertEqual(self.flow.advance_to_next_question(self._PARTICULAR_ANSWER), self._FIRST_QUESTION)
        self.assertEqual(self.flow.advance_to_next_question(self._ANY_ANSWER), self._SECOND_QUESTION)
        self.assertEqual(self.flow.advance_to_next_question(self._ANY_ANSWER), self._THIRD_QUESTION)
        self.assertIsNone(self.flow.advance_to_next_question(self._ANY_ANSWER))

    @patch.object(Question, "ask")
    @patch.object(Confirm, "ask")
    @patch.object(Choice, "ask")
    def test_run(self, mock_3rd_q, mock_2nd_q, mock_1st_q):
        mock_1st_q.return_value = "answer1"
        mock_2nd_q.return_value = False
        mock_3rd_q.return_value = "option1"
        expected_context = {"1st": "answer1", "2nd": False, "3rd": "option1"}
        initial_context = {}
        actual_context = self.flow.run(context=initial_context)
        mock_1st_q.assert_called_once()
        mock_2nd_q.assert_called_once()
        mock_3rd_q.assert_called_once()
        self.assertEqual(expected_context, actual_context)
        self.assertIsNot(actual_context, initial_context)  # shouldn't modify the input, it should copy and return new

    @patch.object(Question, "ask")
    @patch.object(Confirm, "ask")
    @patch.object(Choice, "ask")
    def test_run_with_preloaded_default_values(self, mock_3rd_q, mock_2nd_q, mock_1st_q):
        mock_1st_q.return_value = "answer1"
        mock_2nd_q.return_value = False
        mock_3rd_q.return_value = "option1"

        initial_context = {"key": "value", "['beta', 'bootstrap', 'x']": "y"}

        actual_context = self.flow.run(initial_context)

        mock_1st_q.assert_called_once()
        mock_2nd_q.assert_called_once()
        mock_3rd_q.assert_called_once()

        self.assertEqual(
            {"1st": "answer1", "2nd": False, "3rd": "option1", "['beta', 'bootstrap', 'x']": "y", "key": "value"},
            actual_context,
        )
        self.assertIsNot(actual_context, initial_context)  # shouldn't modify the input, it should copy and return new