"""Board data model and file persistence."""

from __future__ import annotations

import json
import os
import secrets
import time
from dataclasses import dataclass, field, asdict
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

BOARD_DIR = ".agentboard"
BOARD_FILE = "board.json"
COLUMNS = ["backlog", "todo", "in_progress", "done"]
TAGS = ["feature", "bug", "chore", "docs", "test", "design"]
PRIORITIES = ["high", "medium", "low"]


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _task_id() -> str:
    return f"t_{secrets.token_hex(4)}"


@dataclass
class Task:
    id: str
    title: str
    column: str
    created_at: str
    updated_at: str
    created_by: str = "user"
    description: str = ""
    tag: Optional[str] = None
    priority: Optional[str] = None
    assigned_to: Optional[str] = None
    order: int = 0

    def to_dict(self) -> dict:
        return asdict(self)

    @classmethod
    def from_dict(cls, data: dict) -> Task:
        return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})


@dataclass
class Board:
    version: int = 1
    project: str = ""
    created_at: str = field(default_factory=_now)
    updated_at: str = field(default_factory=_now)
    updated_by: str = "user"
    columns: list[str] = field(default_factory=lambda: list(COLUMNS))
    tasks: list[Task] = field(default_factory=list)

    def to_dict(self) -> dict:
        return {
            "version": self.version,
            "project": self.project,
            "created_at": self.created_at,
            "updated_at": self.updated_at,
            "updated_by": self.updated_by,
            "columns": self.columns,
            "tasks": [t.to_dict() for t in self.tasks],
        }

    @classmethod
    def from_dict(cls, data: dict) -> Board:
        tasks = [Task.from_dict(t) for t in data.get("tasks", [])]
        return cls(
            version=data.get("version", 1),
            project=data.get("project", ""),
            created_at=data.get("created_at", _now()),
            updated_at=data.get("updated_at", _now()),
            updated_by=data.get("updated_by", "user"),
            columns=data.get("columns", list(COLUMNS)),
            tasks=tasks,
        )

    def add_task(
        self,
        title: str,
        column: str = "backlog",
        tag: Optional[str] = None,
        priority: Optional[str] = None,
        assigned_to: Optional[str] = None,
        created_by: str = "user",
        description: str = "",
    ) -> Task:
        max_order = max((t.order for t in self.tasks if t.column == column), default=-1)
        task = Task(
            id=_task_id(),
            title=title,
            column=column,
            tag=tag,
            priority=priority,
            assigned_to=assigned_to,
            created_by=created_by,
            description=description,
            created_at=_now(),
            updated_at=_now(),
            order=max_order + 1,
        )
        self.tasks.append(task)
        self._touch("user")
        return task

    def move_task(self, task_id: str, to_column: str, order: Optional[int] = None) -> Optional[Task]:
        task = self.get_task(task_id)
        if not task:
            return None
        task.column = to_column
        if order is not None:
            task.order = order
        else:
            max_order = max((t.order for t in self.tasks if t.column == to_column and t.id != task_id), default=-1)
            task.order = max_order + 1
        task.updated_at = _now()
        self._touch("user")
        return task

    def delete_task(self, task_id: str) -> bool:
        before = len(self.tasks)
        self.tasks = [t for t in self.tasks if t.id != task_id]
        if len(self.tasks) < before:
            self._touch("user")
            return True
        return False

    def update_task(self, task_id: str, **kwargs) -> Optional[Task]:
        task = self.get_task(task_id)
        if not task:
            return None
        for key, value in kwargs.items():
            if hasattr(task, key) and key not in ("id", "created_at", "created_by"):
                setattr(task, key, value)
        task.updated_at = _now()
        self._touch("user")
        return task

    def get_task(self, task_id: str) -> Optional[Task]:
        for task in self.tasks:
            if task.id == task_id:
                return task
        return None

    def tasks_in_column(self, column: str) -> list[Task]:
        return sorted([t for t in self.tasks if t.column == column], key=lambda t: t.order)

    def _touch(self, by: str = "user"):
        self.updated_at = _now()
        self.updated_by = by


def find_board_root(start: Optional[Path] = None) -> Optional[Path]:
    """Walk up from start (default cwd) looking for .agentboard/board.json."""
    current = start or Path.cwd()
    while True:
        board_path = current / BOARD_DIR / BOARD_FILE
        if board_path.is_file():
            return current
        parent = current.parent
        if parent == current:
            return None
        current = parent


def board_path(root: Optional[Path] = None) -> Path:
    """Return the full path to board.json given a project root."""
    if root is None:
        root = find_board_root()
    if root is None:
        raise FileNotFoundError("No .agentboard/board.json found. Run 'agentboard init' first.")
    return root / BOARD_DIR / BOARD_FILE


def load_board(path: Optional[Path] = None) -> Board:
    """Load board from JSON file."""
    if path is None:
        path = board_path()
    with open(path) as f:
        data = json.load(f)
    return Board.from_dict(data)


def save_board(board: Board, path: Optional[Path] = None):
    """Save board to JSON file with atomic write."""
    if path is None:
        path = board_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    tmp = path.with_suffix(".tmp")
    with open(tmp, "w") as f:
        json.dump(board.to_dict(), f, indent=2)
        f.write("\n")
    os.replace(tmp, path)


def init_board(root: Optional[Path] = None, project: str = "") -> Path:
    """Initialize a new board in the given root directory."""
    if root is None:
        root = Path.cwd()
    board_dir = root / BOARD_DIR
    board_dir.mkdir(parents=True, exist_ok=True)
    bp = board_dir / BOARD_FILE
    if bp.exists():
        return bp
    if not project:
        project = root.name
    b = Board(project=project)
    save_board(b, bp)
    return bp
