import pytest

from spidermon import MonitorSuite
from spidermon.exceptions import (
    InvalidMonitor,
    InvalidMonitorIterable,
    InvalidMonitorClass,
    InvalidMonitorTuple,
    NotAllowedMethod,
)

from .fixtures.suites import *


class SuiteDefinition:
    def __init__(self, suite_class, monitors=None, expected_number_of_monitors=0):
        self.suite_class = suite_class
        self.monitors = monitors or []
        self.expected_number_of_monitors = expected_number_of_monitors


CLASS_SUITE_DEFINITIONS = [
    # ---------------------------------------------------------------------------------
    #               suite_class    monitors               expected_number_of_monitors
    # ---------------------------------------------------------------------------------
    # no monitors
    SuiteDefinition(EmptySuite, [], 0),
    SuiteDefinition(Suite01, [], 3),
    SuiteDefinition(Suite02, [], 5),
    SuiteDefinition(Suite03, [], 8),
    SuiteDefinition(Suite04, [], 13),
    # monitors as classes
    SuiteDefinition(EmptySuite, [EmptyMonitor], 0),
    SuiteDefinition(EmptySuite, [Monitor01], 3),
    SuiteDefinition(EmptySuite, [Monitor02], 2),
    SuiteDefinition(EmptySuite, [Monitor01, Monitor02], 5),
    SuiteDefinition(EmptySuite, [Suite01], 3),
    SuiteDefinition(EmptySuite, [Suite01, Suite02], 8),
    SuiteDefinition(EmptySuite, [Suite01, Suite02, Monitor01, Monitor02], 13),
    SuiteDefinition(Suite01, [Suite01, Suite02, Monitor01, Monitor02], 16),
    SuiteDefinition(Suite02, [Suite01, Suite02, Monitor01, Monitor02], 18),
    SuiteDefinition(Suite03, [Suite01, Suite02, Monitor01, Monitor02], 21),
    SuiteDefinition(Suite04, [Suite01, Suite02, Monitor01, Monitor02], 26),
    # monitors as instances
    SuiteDefinition(EmptySuite, [Suite01()], 3),
    SuiteDefinition(EmptySuite, [Suite02()], 5),
    SuiteDefinition(EmptySuite, [Suite03()], 8),
    SuiteDefinition(EmptySuite, [Suite04()], 13),
    SuiteDefinition(EmptySuite, [Suite01(), Suite02(), Suite03(), Suite04()], 29),
    # monitors as tuples
    SuiteDefinition(EmptySuite, [("M1", Monitor01)], 3),
    SuiteDefinition(EmptySuite, [("M1", Monitor01), ("M2", Monitor02)], 5),
    SuiteDefinition(EmptySuite, [("S", Suite04)], 13),
]
INVALID_SUITE_DEFINITION_NON_ITERABLE = SuiteDefinition(EmptySuite, 10)
INVALID_SUITE_DEFINITION_NONE = SuiteDefinition(EmptySuite, [None])
INVALID_SUITE_DEFINITION_NUMBER = SuiteDefinition(EmptySuite, [10])
INVALID_SUITE_DEFINITION_CLASS = SuiteDefinition(EmptySuite, [object])
INVALID_SUITE_DEFINITION_TUPLE1 = SuiteDefinition(EmptySuite, [(1,)])
INVALID_SUITE_DEFINITION_TUPLE3 = SuiteDefinition(EmptySuite, [(1, 2, 3)])
INVALID_SUITE_DEFINITION_TUPLE2_WRONG1 = SuiteDefinition(EmptySuite, [(1, 2)])
INVALID_SUITE_DEFINITION_TUPLE2_WRONG2 = SuiteDefinition(EmptySuite, [("A", 2)])

INVALID_SUITE_DEFINITIONS = [
    # --------------------------------------------------------------
    # definition                                raises
    # --------------------------------------------------------------
    (INVALID_SUITE_DEFINITION_NONE, InvalidMonitor),
    (INVALID_SUITE_DEFINITION_NUMBER, InvalidMonitor),
    (INVALID_SUITE_DEFINITION_CLASS, InvalidMonitorClass),
    (INVALID_SUITE_DEFINITION_TUPLE1, InvalidMonitorTuple),
    (INVALID_SUITE_DEFINITION_TUPLE3, InvalidMonitorTuple),
    (INVALID_SUITE_DEFINITION_TUPLE2_WRONG1, InvalidMonitorTuple),
    (INVALID_SUITE_DEFINITION_TUPLE2_WRONG2, InvalidMonitor),
]


def test_creation_error_non_iterable():
    with pytest.raises(InvalidMonitorIterable):
        _test_creation_from_init(INVALID_SUITE_DEFINITION_NON_ITERABLE)
    with pytest.raises(InvalidMonitorIterable):
        _test_creation_from_add_monitors(INVALID_SUITE_DEFINITION_NON_ITERABLE)


def test_creation_error_invalid_from_init():
    for definition, exception_to_raise in INVALID_SUITE_DEFINITIONS:
        with pytest.raises(exception_to_raise):
            _test_creation_from_init(definition)


def test_creation_error_invalid_from_add_monitors():
    for definition, exception_to_raise in INVALID_SUITE_DEFINITIONS:
        with pytest.raises(exception_to_raise):
            _test_creation_from_add_monitors(definition)


def test_creation_error_invalid_from_add_monitor():
    for definition, exception_to_raise in INVALID_SUITE_DEFINITIONS:
        with pytest.raises(exception_to_raise):
            _test_creation_from_add_monitor(definition)


def test_creation_from_init():
    for definition in CLASS_SUITE_DEFINITIONS:
        _test_creation_from_init(definition)


def test_creation_from_add_monitors():
    for definition in CLASS_SUITE_DEFINITIONS:
        _test_creation_from_add_monitors(definition)


def test_creation_from_add_monitor():
    for definition in CLASS_SUITE_DEFINITIONS:
        _test_creation_from_add_monitor(definition)


def _test_creation_from_init(definition):
    suite = definition.suite_class(monitors=definition.monitors)
    check_suite(
        suite=suite, expected_number_of_monitors=definition.expected_number_of_monitors
    )


def test_not_allowed_methods():
    suite = EmptySuite()
    with pytest.raises(NotAllowedMethod):
        suite.addTest()
    with pytest.raises(NotAllowedMethod):
        suite.addTests()


def _test_creation_from_add_monitors(definition):
    suite = definition.suite_class()
    suite.add_monitors(definition.monitors)
    check_suite(
        suite=suite, expected_number_of_monitors=definition.expected_number_of_monitors
    )


def _test_creation_from_add_monitor(definition):
    suite = definition.suite_class()
    for monitor in definition.monitors:
        suite.add_monitor(monitor)
    check_suite(
        suite=suite, expected_number_of_monitors=definition.expected_number_of_monitors
    )


def check_suite(suite, expected_number_of_monitors):
    # print
    # print suite.debug_tree()
    # print
    assert isinstance(suite, MonitorSuite)
    assert suite.number_of_monitors == expected_number_of_monitors
    for monitor in suite:
        assert isinstance(monitor, (Monitor, MonitorSuite))
    all_monitors = suite.all_monitors
    assert len(all_monitors) == expected_number_of_monitors
    for test in all_monitors:
        assert isinstance(test, (Monitor, Monitor))
