from __future__ import annotations

import collections
from typing import TYPE_CHECKING, ClassVar
from unittest import TestSuite

from spidermon import settings
from spidermon.exceptions import InvalidMonitorIterable, NotAllowedMethod

from .factories import ActionFactory, MonitorFactory
from .monitors import Monitor
from .options import MonitorOptionsMetaclass

if TYPE_CHECKING:
    from collections.abc import Sequence


class MonitorSuite(TestSuite, metaclass=MonitorOptionsMetaclass):
    monitors: ClassVar[
        Sequence[
            type[MonitorSuite | Monitor] | tuple[str, type[MonitorSuite | Monitor]]
        ]
    ] = []
    monitors_finished_actions: ClassVar[list[str]] = []
    monitors_passed_actions: ClassVar[list[str]] = []
    monitors_failed_actions: ClassVar[list[str]] = []

    def __init__(  # noqa: PLR0913
        self,
        name=None,
        monitors=None,
        monitors_finished_actions=None,
        monitors_passed_actions=None,
        monitors_failed_actions=None,
        order=None,
        crawler=None,
    ):
        self._tests = []
        self._removed_tests = 0
        self._name = name
        self._parent = None
        self._order = order
        self._crawler = crawler

        self.add_monitors(self.monitors)
        self.add_monitors(monitors or [])

        declarative_monitors_finished_actions = self.monitors_finished_actions
        self.monitors_finished_actions = []
        self.add_monitors_finished_actions(declarative_monitors_finished_actions)
        self.add_monitors_finished_actions(monitors_finished_actions or [])

        declarative_monitors_passed_actions = self.monitors_passed_actions
        self.monitors_passed_actions = []
        self.add_monitors_passed_actions(declarative_monitors_passed_actions)
        self.add_monitors_passed_actions(monitors_passed_actions or [])

        declarative_monitors_failed_actions = self.monitors_failed_actions
        self.monitors_failed_actions = []
        self.add_monitors_failed_actions(declarative_monitors_failed_actions)
        self.add_monitors_failed_actions(monitors_failed_actions or [])

    @property
    def name(self):
        return self._name or self.options.name or self.__class__.__name__

    @property
    def level(self):
        return self.options.level or self.parent_level

    @property
    def parent_level(self):
        if self.parent:
            return self.parent.level
        return settings.MONITOR.LEVELS.DEFAULT

    @property
    def full_name(self):
        parts = []
        if self.parent and self.parent.full_name:
            parts.append(self.parent.full_name)
        if self.have_custom_name:
            parts.append(self.name)
        return "/".join(parts)

    @property
    def have_custom_name(self):
        return self._name or self.options.name

    @property
    def description(self):
        return (
            self.options.description
            or self.__class__.__doc__
            or settings.MONITOR.DEFAULT_DESCRIPTION
        )

    @property
    def parent(self):
        return self._parent

    @property
    def order(self):
        return self._order if self._order is not None else None or self.options.order

    @property
    def number_of_monitors(self):
        return sum(
            [
                1 if isinstance(monitor, Monitor) else monitor.number_of_monitors
                for monitor in self
            ],
        )

    @property
    def all_monitors(self):
        monitors = []
        for monitor in self:
            if isinstance(monitor, Monitor):
                monitors += [monitor]
            else:
                monitors += monitor.all_monitors
        return monitors

    def set_parent(self, parent):
        self._parent = parent

    def init_data(self, data):
        for test in self:
            test.init_data(data)

    def add_monitors(self, monitors):
        if not isinstance(monitors, collections.abc.Iterable):
            raise InvalidMonitorIterable("Monitors definition is not iterable")
        for m in monitors:
            self.add_monitor(m)

    def add_monitor(self, monitor, name=None):
        monitor = MonitorFactory.load_monitor(monitor, name)
        monitor.set_parent(self)
        super().addTest(monitor)
        self._reorder_tests()

    def add_monitors_finished_actions(self, actions):
        for action in actions:
            self.add_monitors_finished_action(action)

    def add_monitors_finished_action(self, action):
        self._add_action(action, self.monitors_finished_actions)

    def add_monitors_passed_actions(self, actions):
        for action in actions:
            self.add_monitors_passed_action(action)

    def add_monitors_passed_action(self, action):
        self._add_action(action, self.monitors_passed_actions)

    def add_monitors_failed_actions(self, actions):
        for action in actions:
            self.add_monitors_failed_action(action)

    def add_monitors_failed_action(self, action):
        self._add_action(action, self.monitors_failed_actions)

    def _add_action(self, action, target_actions_list):
        action = ActionFactory.load_action(action, crawler=self._crawler)
        target_actions_list.append(action)

    def debug_tree(self, level=0):
        s = level * "\t" + repr(self) + "\n"
        for test in self:
            s += test.debug_tree(level=level + 1)
        return s

    def debug_monitors(
        self,
        show_monitor=True,
        show_method=True,
        show_level=True,
        show_order=False,
        show_description=True,
    ):
        def debug_attribute(condition, name, value):
            return f"{name:>12}: {value!s}\n" if condition else ""

        s = "-" * 80 + "\n"
        for t in self.all_monitors:
            s += debug_attribute(show_monitor, "MONITOR", t.monitor_full_name)
            s += debug_attribute(show_method, "METHOD", t.method_name)
            s += debug_attribute(show_level, "LEVEL", t.level)
            s += debug_attribute(show_order, "ORDER", t.order)
            s += debug_attribute(
                show_description,
                "DESCRIPTION",
                t.method_description or "...",
            )
            s += "-" * 80 + "\n"
        return s

    def _reorder_tests(self):
        self._tests = sorted(self._tests, key=lambda x: x.order, reverse=False)

    def __repr__(self):
        return f"<SUITE:{self.name}[{len(self._tests)},{self.number_of_monitors}] at {hex(id(self))}>"

    def __str__(self):
        return self.name

    def __not_allowed_method(self, *args, **kwargs):
        raise NotAllowedMethod

    addTest = __not_allowed_method
    addTests = __not_allowed_method

    def on_monitors_finished(self, result):
        pass

    def on_monitors_passed(self, result):
        pass

    def on_monitors_failed(self, result):
        pass
