"""Task editor page for the pgt TUI."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime
from typing import Literal

from pygit2 import Oid
from pyratatui import (
    Constraint,
    Direction,
    Frame,
    Layout,
    List,
    ListItem,
    ListState,
    Paragraph,
    Rect,
    TextPrompt,
    TextState,
)

from pygittools.tags_sync import delete_task_remote, push_board_tags
from pygittools.tasks import (
    BOARD_REF_PREFIX,
    EMPTY_TREE_OID_HEX,
    TASK_REF_PREFIX,
    Task,
    delete_task_local,
    get_board,
    get_task,
)
from pygittools.tasks_query import BOARD_STATUS_ORDER, get_task_view
from pygittools.tui.git_colors import current_theme, focus_style
from pygittools.tui.keys import KeyLike, is_down, is_enter, is_esc, is_quit, is_up, move_hint
from pygittools.tui.types import PageAction, PageContext, PageResult

_FieldName = Literal["title", "description", "status", "priority", "assignee", "due_date"]
_FIELDS: tuple[_FieldName, ...] = ("title", "description", "status", "priority", "assignee", "due_date")
_PRIORITY_ORDER: tuple[str, ...] = ("LOW", "MEDIUM", "HIGH", "CRITICAL")
_TEXT_FIELDS = frozenset({"title", "description", "assignee", "due_date"})


@dataclass(frozen=True, slots=True)
class _EditorState:
    title: str
    description: str
    status: str | None
    priority: str | None
    assignee: str
    due_date: str


class TaskEditorPage:
    title = "task"

    def __init__(self, *, board_name: str, task_ref: str | None, default_status: str | None) -> None:
        self._ctx: PageContext | None = None
        self._board_name = board_name
        self._task_ref = task_ref
        self._default_status = default_status
        self._state = _EditorState(
            title="",
            description="",
            status=default_status,
            priority=None,
            assignee="",
            due_date="",
        )
        self._list_state = ListState()
        self._list_state.select(0)
        self._input_mode: Literal["nav", "input"] = "nav"
        self._text_state = TextState()
        self._error: str | None = None

    def on_enter(self, ctx: PageContext) -> None:
        self._ctx = ctx
        if self._task_ref is None:
            return
        view = get_task_view(ctx.repo, self._task_ref)
        self._state = _EditorState(
            title=view.get("title", ""),
            description=view.get("description", ""),
            status=view.get("status"),
            priority=view.get("priority"),
            assignee=view.get("assignee") or "",
            due_date=view.get("due_date") or "",
        )

    def status_text(self) -> str:
        if self._error:
            return self._error
        field_index = self._list_state.selected or 0
        move = move_hint()
        if self._task_ref is not None:
            if field_index == len(_FIELDS):
                return f"Enter remove task (local) — {move} — q or Esc back"
            if field_index == len(_FIELDS) + 1:
                return f"Enter remove task (remote & local) — {move} — q or Esc back"
        if self._input_mode == "input":
            field = _FIELDS[field_index] if field_index < len(_FIELDS) else "title"
            if field in ("status", "priority"):
                return f"{move} change value — Enter save — Esc stop editing — q back"
            return "Type to edit — Enter save — Esc stop editing — q back"
        return f"Enter edit field — {move} field — q or Esc back"

    def draw(self, frame: Frame, area: Rect) -> None:
        if area.height <= 0:
            return
        theme = current_theme()
        chunks = (
            Layout().direction(Direction.Vertical).constraints([Constraint.length(1), Constraint.fill(1)]).split(area)
        )
        header = f"Task in board {self._board_name}"
        frame.render_widget(Paragraph.from_string(header).style(theme.header), chunks[0])

        field_index = self._list_state.selected or 0
        if self._input_mode == "input" and field_index < len(_FIELDS) and _FIELDS[field_index] in _TEXT_FIELDS:
            field = _FIELDS[field_index]
            self._text_state.focus()
            frame.render_text_prompt(TextPrompt(f"{field.replace('_', ' ').title()}: "), chunks[1], self._text_state)
            return

        rows = self._row_labels()
        items: list[ListItem] = []
        for index, text in enumerate(rows):
            style = theme.unstaged if self._task_ref is not None and index >= len(_FIELDS) else theme.normal
            if index == field_index:
                style = focus_style(style)
                if self._input_mode == "input" and index < len(_FIELDS):
                    style = style.bold()
            items.append(ListItem(text, style))
        frame.render_stateful_list(
            List(items).highlight_style(theme.focus).highlight_symbol(""),
            chunks[1],
            self._list_state,
        )

    def handle_key(self, key: KeyLike) -> PageResult:
        if is_quit(key) and self._input_mode == "nav":
            return PageResult(action=PageAction.POP)
        if is_esc(key):
            if self._input_mode == "input":
                self._sync_text_field_from_prompt()
                self._input_mode = "nav"
                return PageResult()
            return PageResult(action=PageAction.POP)
        if self._error is not None:
            self._error = None

        field_index = self._list_state.selected or 0
        field = _FIELDS[field_index] if field_index < len(_FIELDS) else "title"

        if self._input_mode == "input":
            if field in _TEXT_FIELDS:
                if is_up(key):
                    self._sync_text_field_from_prompt()
                    self._list_state.select(max(0, field_index - 1))
                    self._begin_field_input()
                    return PageResult()
                if is_down(key):
                    self._sync_text_field_from_prompt()
                    self._list_state.select(min(len(_FIELDS) - 1, field_index + 1))
                    self._begin_field_input()
                    return PageResult()
                if is_enter(key):
                    self._sync_text_field_from_prompt()
                    return self._save()
                self._text_state.focus()
                self._text_state.handle_key(key)
                return PageResult()
            if field == "status":
                if is_enter(key):
                    return self._save()
                self._cycle_status(key)
                return PageResult()
            if field == "priority":
                if is_enter(key):
                    return self._save()
                self._cycle_priority(key)
                return PageResult()
            return PageResult()

        if is_up(key):
            self._list_state.select(max(0, field_index - 1))
            return PageResult()
        if is_down(key):
            max_index = (len(_FIELDS) + 1) if self._task_ref is not None else len(_FIELDS) - 1
            self._list_state.select(min(max_index, field_index + 1))
            return PageResult()
        if is_enter(key):
            if self._task_ref is not None:
                if field_index == len(_FIELDS):
                    return self._delete_local()
                if field_index == len(_FIELDS) + 1:
                    return self._delete_remote_local()
            self._input_mode = "input"
            self._begin_field_input()
            return PageResult()
        return PageResult()

    def _row_labels(self) -> list[str]:
        rows = [
            f"Title: {self._state.title}",
            f"Description: {self._state.description}",
            f"Status: {self._state.status or '(none)'}",
            f"Priority: {self._state.priority or '(none)'}",
            f"Assignee: {self._state.assignee}",
            f"Due date (ISO): {self._state.due_date}",
        ]
        if self._task_ref is not None:
            rows.append("Remove task")
            rows.append("Remove task (REMOTE & LOCAL)")
        return rows

    def _begin_field_input(self) -> None:
        field_index = self._list_state.selected or 0
        if field_index >= len(_FIELDS):
            return
        field = _FIELDS[field_index]
        if field in _TEXT_FIELDS:
            self._text_state.set_value(_text_for_field(self._state, field))
            self._text_state.focus()

    def _sync_text_field_from_prompt(self) -> None:
        field_index = self._list_state.selected or 0
        if field_index >= len(_FIELDS):
            return
        field = _FIELDS[field_index]
        if field in _TEXT_FIELDS:
            self._state = _replace_state_field(self._state, field, self._text_state.value())

    def _cycle_status(self, key: KeyLike) -> None:
        if not (is_up(key) or is_down(key)):
            return
        current = self._state.status or self._default_status or BOARD_STATUS_ORDER[0]
        try:
            index = BOARD_STATUS_ORDER.index(current)
        except ValueError:
            index = 0
        index = (index - 1) % len(BOARD_STATUS_ORDER) if is_up(key) else (index + 1) % len(BOARD_STATUS_ORDER)
        self._state = _EditorState(
            title=self._state.title,
            description=self._state.description,
            status=BOARD_STATUS_ORDER[index],
            priority=self._state.priority,
            assignee=self._state.assignee,
            due_date=self._state.due_date,
        )

    def _cycle_priority(self, key: KeyLike) -> None:
        if not (is_up(key) or is_down(key)):
            return
        if not _PRIORITY_ORDER:
            return
        current = self._state.priority or _PRIORITY_ORDER[0]
        try:
            index = _PRIORITY_ORDER.index(current)
        except ValueError:
            index = 0
        index = (index - 1) % len(_PRIORITY_ORDER) if is_up(key) else (index + 1) % len(_PRIORITY_ORDER)
        self._state = _EditorState(
            title=self._state.title,
            description=self._state.description,
            status=self._state.status,
            priority=_PRIORITY_ORDER[index],
            assignee=self._state.assignee,
            due_date=self._state.due_date,
        )

    def _save(self) -> PageResult:
        ctx = self._ctx
        if ctx is None:
            return PageResult(action=PageAction.POP)

        try:
            due_dt = _parse_due_date(self._state.due_date)
        except ValueError as exc:
            self._error = str(exc)
            return PageResult()

        if self._task_ref is None:
            try:
                _create_task(ctx, self._board_name, self._state, due_dt)
            except Exception as exc:  # noqa: BLE001
                self._error = str(exc)
                return PageResult()
            return PageResult(action=PageAction.POP, repo_changed=True)

        task = get_task(ctx.repo, self._task_ref)
        if task is None:
            self._error = f"Task not found: {self._task_ref}"
            return PageResult()
        task.title = self._state.title
        task.description = self._state.description
        task.status = Task.Status(self._state.status) if self._state.status else None
        task.priority = Task.Priority(self._state.priority) if self._state.priority else None
        task.assignee = self._state.assignee or None
        task.due_date = due_dt
        task.update_message()
        task.write(ctx.repo)
        return PageResult(action=PageAction.POP, repo_changed=True)

    def _delete_local(self) -> PageResult:
        ctx = self._ctx
        if ctx is None or self._task_ref is None:
            return PageResult(action=PageAction.POP)
        delete_task_local(ctx.repo, self._task_ref)
        return PageResult(action=PageAction.POP, repo_changed=True)

    def _delete_remote_local(self) -> PageResult:
        ctx = self._ctx
        if ctx is None or self._task_ref is None:
            return PageResult(action=PageAction.POP)

        remote_name = _default_remote_name(ctx)
        if remote_name is not None:
            delete_task_remote(ctx.repo, remote_name, self._task_ref)
        delete_task_local(ctx.repo, self._task_ref)
        if remote_name is not None:
            push_board_tags(ctx.repo, remote_name, self._board_name)
        return PageResult(action=PageAction.POP, repo_changed=True)


def _replace_state_field(state: _EditorState, field: _FieldName, value: str) -> _EditorState:
    if field == "title":
        return _EditorState(
            title=value,
            description=state.description,
            status=state.status,
            priority=state.priority,
            assignee=state.assignee,
            due_date=state.due_date,
        )
    if field == "description":
        return _EditorState(
            title=state.title,
            description=value,
            status=state.status,
            priority=state.priority,
            assignee=state.assignee,
            due_date=state.due_date,
        )
    if field == "assignee":
        return _EditorState(
            title=state.title,
            description=state.description,
            status=state.status,
            priority=state.priority,
            assignee=value,
            due_date=state.due_date,
        )
    return _EditorState(
        title=state.title,
        description=state.description,
        status=state.status,
        priority=state.priority,
        assignee=state.assignee,
        due_date=value,
    )


def _text_for_field(state: _EditorState, field: _FieldName) -> str:
    if field == "title":
        return state.title
    if field == "description":
        return state.description
    if field == "assignee":
        return state.assignee
    return state.due_date


def _parse_due_date(text: str) -> datetime | None:
    if not text.strip():
        return None
    try:
        return datetime.fromisoformat(text)
    except ValueError as exc:
        raise ValueError(f"Invalid due date (expected ISO format): {text}") from exc


def _create_task(ctx: PageContext, board_name: str, state: _EditorState, due_dt: datetime | None) -> None:
    board_ref = f"{BOARD_REF_PREFIX}{board_name}"
    board = get_board(ctx.repo, board_ref)
    if board is None:
        raise ValueError(f"Board not found: {board_name}")
    title = state.title or "Untitled"
    slug = title.lower().replace(" ", "_") or "task"
    full_ref = f"{TASK_REF_PREFIX}{slug}"
    target = Oid(hex=EMPTY_TREE_OID_HEX)
    sig = ctx.repo.default_signature
    tagger = f"{sig.name} <{sig.email}>"
    task = Task(
        target,
        full_ref,
        tagger,
        title=title,
        description=state.description,
        status=Task.Status(state.status) if state.status else None,
        priority=Task.Priority(state.priority) if state.priority else None,
        assignee=state.assignee or None,
        due_date=due_dt,
    )
    task_oid = task.write(ctx.repo)
    board.tasks = [*(getattr(board, "tasks", []) or []), str(task_oid)]
    board.update_message()
    board.write(ctx.repo)


def _default_remote_name(ctx: PageContext) -> str | None:
    names = list(ctx.repo.remotes.names())
    if not names:
        return None
    if "origin" in names:
        return "origin"
    return names[0]