from dataclasses import dataclass
from typing import Literal
from rich.text import Text
from textual import on
from textual.app import ComposeResult
from textual.widgets import Tree, Button
from textual.containers import Vertical, Horizontal, Container
from textual.reactive import var

from analyst_klondike.features.app.actions import EditorScreenReadyAction
from analyst_klondike.features.code.actions import (
    RunAllCodeAction,
    RunCodeAndSetResultsAction,
    UpdateCodeAction
)
from analyst_klondike.features.code_explorer.actions import (
    QuizNodeCollapseAction,
    QuizNodeExpandAction
)
from analyst_klondike.features.code_import.ui_actions.actions import (
    ImportCodeForTaskFromSource,
    ImportTasksAction
)
from analyst_klondike.features.current.actions import (
    MakeQuizCurrentAction,
    MakeTaskCurrentAction
)
from analyst_klondike.features.current.selectors import select_current_task
from analyst_klondike.features.err_handling import catch_error
from analyst_klondike.features.events.event_hook import send_event
from analyst_klondike.features.helpers import exclude_actions, for_actions
from analyst_klondike.features.teacher.clear_solutions import ClearSolutionsAction
import analyst_klondike.features.teacher.create_file as create_file_feature
import analyst_klondike.features.teacher.create_quiz as create_quiz_feature
import analyst_klondike.features.teacher.create_task as create_task_feature
import analyst_klondike.features.teacher.edit_task as edit_task_feature
from analyst_klondike.state.app_dispatch import app_dispatch
from analyst_klondike.state.app_state import INITIAL_STATE, AppState, select
from analyst_klondike.features.data_context.init_action import InitAction
from analyst_klondike.state.selectors import select_task_by_id, select_tasks_for_quiz


class Explorer(Vertical):

    DEFAULT_CSS = """
        Explorer {
            border: solid $primary;
            height: 1fr;

            Tree {
                background: $background;

                &:focus {
                    background: transparent;
                }                
            }

            #button_container {                
                height: auto;
                align-horizontal: center;

                .separator {
                    width: 1;
                    height: 0;
                }
            }
        }
    """

    state = var(INITIAL_STATE, init=False)

    @dataclass
    class ExpNodeData:
        node_type: Literal["root", "group", "quiz", "task"]
        object_id: int | str | None = None

    def __init__(self) -> None:
        super().__init__()
        self._is_tree_updated = False

    def on_mount(self) -> None:
        self.border_title = "Навигатор"

    def compose(self) -> ComposeResult:
        with Vertical():
            yield Tree(self.state.user_email, id="tasks")
            with Horizontal(id="button_container"):
                yield Button(
                    id="create_quiz_button",
                    label="+ Тест",
                    variant='primary',
                    compact=True
                )
                yield Container(classes="separator")
                yield Button(
                    id="create_task_button",
                    label="+ Задача",
                    variant='primary',
                    compact=True,
                )
                yield Container(classes="separator")
                yield Button(
                    id="edit_task_content",
                    label="Редактировать",
                    variant='primary',
                    compact=True
                )

    @on(Tree.NodeSelected)
    @catch_error
    def on_node_selected(self, event: Tree.NodeSelected[ExpNodeData]) -> None:
        node_data = event.node.data
        if node_data is None:
            return
        if node_data.node_type == "task":
            task_id = node_data.object_id
            assert isinstance(task_id, int)
            app_dispatch(MakeTaskCurrentAction(task_id))
            selected_task = select(select_task_by_id, task_id)
            send_event("python_task_selected", "/",
                       event_json={
                           'id': str(task_id),
                           'title': selected_task.title
                       })
        elif node_data.node_type == "quiz":
            assert isinstance(node_data.object_id, str)
            app_dispatch(MakeQuizCurrentAction(quiz_id=node_data.object_id))

    def watch_state(self, state: AppState) -> None:
        self._update_edit_button(state)
        self._update_tree(state)
        self._update_button_container(state)

    @for_actions(
        InitAction,
        RunCodeAndSetResultsAction,
        RunAllCodeAction,
        ImportTasksAction,
        create_file_feature.CreateFileAction,
        create_quiz_feature.CreateQuizAction,
        create_task_feature.CreateTaskAction,
        edit_task_feature.ToggleTaskEditMode,
        EditorScreenReadyAction,
        ImportCodeForTaskFromSource,
        ClearSolutionsAction
    )
    def _update_tree(self, state: AppState):
        tree: Tree[Explorer.ExpNodeData] = self.query_one(  # type: ignore
            "Tree", Tree)
        tree.clear()
        tree.root.label = Text.from_markup(f":id: {state.user_email}")
        tree.root.data = Explorer.ExpNodeData(node_type="root")
        quiz_group_node = tree.root.add(Text.from_markup(":open_file_folder: Тесты"),
                                        expand=True,
                                        data=Explorer.ExpNodeData(node_type="group"))

        for quiz in state.data.quizes.values():
            quiz_node = quiz_group_node.add(
                label=Text.from_markup(f":books: {quiz.title}"),
                data=Explorer.ExpNodeData(
                    object_id=quiz.id,
                    node_type="quiz"
                ),
                expand=quiz.is_node_expanded)

            for task in select_tasks_for_quiz(state, quiz.id):
                task_node_text = self._get_node_text(state, task.id)
                quiz_node.add_leaf(task_node_text,
                                   data=Explorer.ExpNodeData(
                                       object_id=task.id,
                                       node_type="task"))

        tree.root.expand()
        self._is_tree_updated = True

    @exclude_actions(UpdateCodeAction)
    def _update_button_container(self, state: AppState) -> None:
        # set button container visibility depending on teacher's mode
        editor_container = self.query_one('#button_container', Horizontal)
        editor_container.display = state.is_teacher_mode

        no_file = state.current.opened_file_name == ""
        self.query_one("#create_quiz_button").disabled = no_file
        self.query_one("#create_task_button").disabled = no_file

        edit_task_content_button = self.query_one(
            "#edit_task_content", Button)
        curr_task = select(select_current_task)
        edit_task_content_button.disabled = no_file or curr_task is None

    @for_actions(edit_task_feature.ToggleTaskEditMode, MakeTaskCurrentAction)
    def _update_edit_button(self, _: AppState):
        edit_task_content_button = self.query_one(
            "#edit_task_content", Button
        )
        curr_task = select(select_current_task)
        if curr_task is not None:
            if curr_task.is_edited:
                edit_task_content_button.label = "Завершить редактирование"
            else:
                edit_task_content_button.label = "Редактировать"

    def _get_node_text(self, state: AppState, task_id: int) -> Text:
        task = state.data.tasks[task_id]
        task_run_result = task.is_passed
        if task_run_result == "not_runned":
            return Text.from_markup(f"{task.title}")
        if task_run_result == "passed":
            return Text.from_markup(f":green_circle: {task.title}")
        if task_run_result == "failed":
            return Text.from_markup(f":red_circle: {task.title}")
        return task_run_result.title

    @on(Tree.NodeExpanded)
    def on_tree_node_expanded(self, ev: Tree.NodeExpanded[ExpNodeData]):
        node_data = ev.node.data
        if node_data is None:
            return
        self._set_expanded_collapsed(node_data, "expanded")

    @on(Tree.NodeCollapsed)
    def on_tree_node_collapsed(self, ev: Tree.NodeCollapsed[ExpNodeData]):
        node_data = ev.node.data
        if node_data is None:
            return
        self._set_expanded_collapsed(node_data, "collapsed")

    @on(Button.Pressed, "#create_quiz_button")
    def on_create_quiz_button(self):
        @catch_error
        def _screen_callback(quiz_name: str | None) -> None:
            if quiz_name is not None:
                app_dispatch(create_quiz_feature.CreateQuizAction(quiz_name))

        create_quiz_feature.push_screen(_screen_callback)

    @on(Button.Pressed, "#create_task_button")
    def on_create_task_button(self):
        @catch_error
        def _screen_callback(task_title: str | None) -> None:
            if task_title is not None:
                app_dispatch(create_task_feature.CreateTaskAction(task_title))

        create_task_feature.push_screen(_screen_callback)

    @on(Button.Pressed, "#edit_task_content")
    def on_edit_task_content_button_click(self) -> None:
        curr_task = select(select_current_task)
        assert curr_task is not None, "Current task expected"
        app_dispatch(edit_task_feature.ToggleTaskEditMode(curr_task.id))

    @catch_error
    def _set_expanded_collapsed(self,
                                node_data: ExpNodeData,
                                action: Literal["expanded", "collapsed"]):
        if node_data.node_type == "quiz" and isinstance(node_data.object_id, str):
            quiz_id = node_data.object_id
            if action == "expanded":
                app_dispatch(QuizNodeExpandAction(quiz_id=quiz_id))
            if action == "collapsed":
                app_dispatch(QuizNodeCollapseAction(quiz_id=quiz_id))

            # self.post_message(Explorer.QuizNodeExpandedOrCollapsed(
            #     quiz_id=quiz_id,
            #     action_type=action
            # ))
