# -*- coding: utf-8 -*-
# pylint: disable=no-self-use, line-too-long

from __future__ import absolute_import, print_function, with_statement
import sys
import unittest
from mock import Mock, patch
from nose.tools import *  # pylint: disable=wildcard-import, unused-wildcard-import
import six
from six.moves import range     # pylint: disable=redefined-builtin
from six.moves import zip       # pylint: disable=redefined-builtin

from behave.model_core import FileLocation
from behave.model import Feature, Scenario, ScenarioOutline, Step
from behave.model import
 Table, Row
from behave.matchers import NoMatch
from behave.configuration import Configuration
from behave.compat.collections import OrderedDict
from behave import step_registry


# -- CONVENIENCE-ALIAS:
_text = six.text_type


class TestFeatureRun(unittest.TestCase):
    # pylint: disable=invalid-name

    def setUp(self):
        self.runner = Mock()
        self.runner.feature.tags = []

        self.config = self.runner.config = Mock()
        self.context = self.runner.context = Mock()
        self.formatters = self.runner.formatters = [Mock()]
        self.run_hook = self.runner.run_hook = Mock()

    def test_formatter_feature_called(self):
        feature = Feature('foo.feature', 1, u'Feature', u'foo',
                          background=Mock())

        feature.run(self.runner)

        self.formatters[0].feature.assert_called_with(feature)

    def test_formatter_background_called_when_feature_has_background(self):
        feature = Feature('foo.feature', 1, u'Feature', u'foo',
                          background=Mock())

        feature.run(self.runner)

        self.formatters[0].background.assert_called_with(feature.background)

    def test_formatter_background_not_called_when_feature_has_no_background(self):
        feature = Feature('foo.feature', 1, u'Feature', u'foo')

        feature.run(self.runner)

        assert not self.formatters[0].background.called

    def test_run_runs_scenarios(self):
        scenarios = [Mock(), Mock()]
        for scenario in scenarios:
            scenario.tags = []
            scenario.run.return_value = False

        self.config.tags.check.return_value = True  # pylint: disable=no-member
        self.config.name = []

        feature = Feature('foo.feature', 1, u'Feature', u'foo',
                          scenarios=scenarios)

        feature.run(self.runner)

        for scenario in scenarios:
            scenario.run.assert_called_with(self.runner)

    def test_run_runs_named_scenarios(self):
        scenarios = [Mock(Scenario), Mock(Scenario)]
        scenarios[0].name = 'first scenario'
        scenarios[1].name = 'second scenario'
        scenarios[0].tags = []
        scenarios[1].tags = []
        # -- FAKE-CHECK:
        scenarios[0].should_run_with_name_select.return_value = True
        scenarios[1].should_run_with_name_select.return_value = False

        for scenario in scenarios:
            scenario.run.return_value = False

        self.config.tags.check.return_value = True  # pylint: disable=no-member
        self.config.name = ['first', 'third']
        self.config.name_re = Configuration.build_name_re(self.config.name)

        feature = Feature('foo.feature', 1, u'Feature', u'foo',
                          scenarios=scenarios)

        feature.run(self.runner)

        scenarios[0].run.assert_called_with(self.runner)
        assert not scenarios[1].run.called
        scenarios[0].should_run_with_name_select.assert_called_with(self.config)
        scenarios[1].should_run_with_name_select.assert_called_with(self.config)

    def test_run_runs_named_scenarios_with_regexp(self):
        scenarios = [Mock(), Mock()]
        scenarios[0].name = 'first scenario'
        scenarios[1].name = 'second scenario'
        scenarios[0].tags = []
        scenarios[1].tags = []
        # -- FAKE-CHECK:
        scenarios[0].should_run_with_name_select.return_value = False
        scenarios[1].should_run_with_name_select.return_value = True

        for scenario in scenarios:
            scenario.run.return_value = False

        self.config.tags.check.return_value = True  # pylint: disable=no-member
        self.config.name = ['third .*', 'second .*']
        self.config.name_re = Configuration.build_name_re(self.config.name)

        feature = Feature('foo.feature', 1, u'Feature', u'foo',
                          scenarios=scenarios)

        feature.run(self.runner)

        assert not scenarios[0].run.called
        scenarios[1].run.assert_called_with(self.runner)
        scenarios[0].should_run_with_name_select.assert_called_with(self.config)
        scenarios[1].should_run_with_name_select.assert_called_with(self.config)

    def test_feature_hooks_not_run_if_feature_not_being_run(self):
        self.config.tags.check.return_value = False  # pylint: disable=no-member

        feature = Feature('foo.feature', 1, u'Feature', u'foo')
        feature.run(self.runner)
        assert not self.run_hook.called


class TestScenarioRun(unittest.TestCase):
    # pylint: disable=invalid-name

    def setUp(self):
        self.runner = Mock()
        self.runner.feature.tags = []
        self.config = self.runner.config = Mock()
        self.config.dry_run = False
        self.context = self.runner.context = Mock()
        self.formatters = self.runner.formatters = [Mock()]
        self.run_hook = self.runner.run_hook = Mock()

    def test_run_invokes_formatter_scenario_and_steps_correctly(self):
        self.config.stdout_capture = False
        self.config.log_capture = False
        self.config.tags.check.return_value = True  # pylint: disable=no-member
        steps = [Mock(), Mock()]
        scenario = Scenario('foo.feature', 17, u'Scenario', u'foo',
                            steps=steps)

        scenario.run(self.runner)

        self.formatters[0].scenario.assert_called_with(scenario)
        for step in steps:
            step.run.assert_called_with(self.runner)

    if sys.version_info[0] == 3:
        stringio_target = 'io.StringIO'
    else:
        stringio_target = 'StringIO.StringIO'

    def test_handles_stdout_and_log_capture(self):
        self.config.stdout_capture = True
        self.config.log_capture = True
        self.config.tags.check.return_value = True  # pylint: disable=no-member

        steps = [Mock(), Mock()]
        scenario = Scenario('foo.feature', 17, u'Scenario', u'foo',
                            steps=steps)

        scenario.run(self.runner)

        self.runner.setup_capture.assert_called_with()
        self.runner.teardown_capture.assert_called_with()

    def test_failed_step_causes_remaining_steps_to_be_skipped(self):
        self.config.stdout_capture = False
        self.config.log_capture = False
        self.config.tags.check.return_value = True  # pylint: disable=no-member

        steps = [Mock(), Mock()]
        scenario = Scenario('foo.feature', 17, u'Scenario', u'foo',
                            steps=steps)
        steps[0].run.return_value = False
        steps[1].step_type = "when"
        steps[1].name = "step1"

        def step1_function(context):    # pylint: disable=unused-argument
            pass
        my_step_registry = step_registry.StepRegistry()
        my_step_registry.add_step_definition("when", "step1", step1_function)

        with patch("behave.step_registry.registry", my_step_registry):
            assert scenario.run(self.runner)
            eq_(steps[1].status, 'skipped')

    def test_failed_step_causes_context_failure_to_be_set(self):
        self.config.stdout_capture = False
        self.config.log_capture = False
        self.config.tags.check.return_value = True  # pylint: disable=no-member

        steps = [
            Mock(step_type="given", name="step0"),
            Mock(step_type="then", name="step1"),
        ]
        scenario = Scenario('foo.feature', 17, u'Scenario', u'foo',
                            steps=steps)
        steps[0].run.return_value = False

        assert scenario.run(self.runner)
        # pylint: disable=protected-access
        self.context._set_root_attribute.assert_called_with('failed', True)

    def test