from time import time
from typing import Optional

from nextcloud_tasks_api.ical import Task

from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Vertical, Horizontal
from textual.events import Blur
from textual.message import Message
from textual.reactive import reactive, Reactive
from textual.timer import Timer
from textual.widgets import Input, TextArea, Checkbox

from .repo import TaskUpdater
from .status import StatusBar


class TaskEntry(Vertical):
    class CheckCheckbox(Checkbox):
        BUTTON_INNER = "✔"

    class QuitAction(Message):
        pass

    DEFAULT_CSS = """
    Horizontal {
        height: auto;
        margin: 0 0 1 0;
    }
    Input {
        width: 1fr;
        text-style: bold;
    }
    """

    BINDINGS = [
        Binding("escape", "quit", "Back", show=True),
    ]

    uid: Reactive[Optional[str]] = reactive(None)

    def __init__(self, status: StatusBar, repo: TaskUpdater) -> None:
        self._status: StatusBar = status
        self._repo: TaskUpdater = repo
        self._timer: Optional[Timer] = None
        self._entry: Optional[Task] = None

        self._completed: Checkbox = TaskEntry.CheckCheckbox(label="")
        self._summary: Input = Input()

        self._description: TextArea = TextArea(language="markdown", theme="monokai")
        self._description.border_title = None

        super().__init__()

    def compose(self) -> ComposeResult:
        yield Horizontal(self._completed, self._summary)
        yield self._description

    def action_quit(self) -> None:
        self.post_message(self.QuitAction())

    def set_focus(self) -> None:
        self._description.focus()

    def toggle(self, uid: str) -> None:
        if self._entry is not None and self._entry.uid == uid:
            cb: Checkbox = self.query_one(Checkbox)
            cb.value = not cb.value

    def flush(self) -> None:
        if self._timer is not None:
            self._timer.stop()
            self._timer = None
        if self._status.dirty:
            self._status.dirty = False
            if self._entry is not None:
                self._repo.set(self._entry)

    def _schedule_flush(self) -> None:
        if self._timer is None:
            self._timer = self.set_timer(3.0, self.flush, name="update_timer", pause=False)
        else:
            self._timer.reset()

    def watch_uid(self, uid: Optional[str]) -> None:
        self.flush()
        if uid is None:
            self.visible = False
            self._entry = None
        else:
            self.visible = True
            self.disabled = True
            self.loading = True
            self.run_worker(self._load(uid))

    async def _load(self, uid: str) -> None:
        new_entry: Task = await self._repo.get(uid)
        if new_entry.uid == self.uid:  # run_worker exclusive=True might lead to 'coroutine was never awaited'
            self._entry = new_entry
            self._summary.value = self._entry.summary or ""
            self._completed.value = self._entry.completed is not None
            self._description.text = self._entry.description or ""
            self.loading = False
            self.disabled = False

    def on_input_changed(self, evt: Input.Changed) -> None:
        if self._entry is not None and evt.value != (self._entry.summary or ""):
            self._entry.summary = evt.value or None
            self._status.dirty = True
            self._schedule_flush()

    def on_checkbox_changed(self, evt: Checkbox.Changed) -> None:
        if self._entry is not None and evt.value != (self._entry.completed is not None):
            self._entry.completed = round(time()) if evt.value else None
            self._status.dirty = True
            self.flush()

    def on_text_area_changed(self, evt: TextArea.Changed) -> None:
        if self._entry is not None and evt.text_area.text != (self._entry.description or ""):
            self._entry.description = evt.text_area.text or None
            self._status.dirty = True
            self._schedule_flush()

    def on_descendant_blur(self, event: Blur) -> None:
        self.flush()