"""iCalendar VTODO operations with Taskwarrior-style urgency scoring.

Uses ``icalendar`` for parsing/serialization. One .ics file per task,
filename derived from UID. Urgency is computed at query time (never stored).

Custom VTODO fields via X- properties:
- X-ICARUS-PROJECT: Project grouping
- X-ICARUS-TAGS: Comma-separated tags
- X-ICARUS-BLOCKED: "true" if task is blocked
"""

from __future__ import annotations

import logging
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import icalendar
from icarus.pim.common import (
    _find_ics_file,
    _parse_attendees,
    _serialize_attendee,
    _to_datetime,
)

logger = logging.getLogger(__name__)

# ── Priority mapping ────────────────────────────────────────────
# iCalendar priority: 1 (highest) to 9 (lowest), 0 = undefined
_PRIORITY_MAP = {"high": 1, "medium": 5, "low": 9, "none": 0}
_PRIORITY_LABELS = {1: "high", 2: "high", 3: "high", 4: "medium", 5: "medium",
                    6: "medium", 7: "low", 8: "low", 9: "low", 0: "none"}


def _todo_to_dict(component: icalendar.cal.Component, path: Path | None = None) -> dict[str, Any]:
    """Convert a VTODO component to a dictionary."""
    uid = str(component.get("uid", ""))
    summary = str(component.get("summary", ""))
    description = str(component.get("description", ""))
    status = str(component.get("status", "NEEDS-ACTION")).upper()
    priority_int = int(component.get("priority", 0))
    priority_label = _PRIORITY_LABELS.get(priority_int, "none")

    due = _to_datetime(component.get("due"))
    created = _to_datetime(component.get("created"))
    completed_dt = _to_datetime(component.get("completed"))

    # Attendees / assigned_to — parse ATTENDEE properties
    attendees, assigned_to = _parse_attendees(
        component.get("attendee"), detect_assignee=True,
    )

    # RELATED-TO — linked events/todos
    related_to: list[str] = []
    raw_related = component.get("related-to")
    if raw_related is not None:
        if isinstance(raw_related, list):
            related_to = [str(r) for r in raw_related]
        else:
            related_to = [str(raw_related)]

    # X-ICARUS fields
    project = str(component.get("x-icarus-project", ""))
    tags_raw = str(component.get("x-icarus-tags", ""))
    tags = [t.strip() for t in tags_raw.split(",") if t.strip()] if tags_raw else []
    blocked = str(component.get("x-icarus-blocked", "")).lower() == "true"

    return {
        "uid": uid,
        "summary": summary,
        "description": description,
        "status": status,
        "priority": priority_label,
        "priority_int": priority_int,
        "due": due,
        "created": created,
        "completed": completed_dt,
        "attendees": attendees,
        "assigned_to": assigned_to,
        "related_to": related_to,
        "project": project,
        "tags": tags,
        "blocked": blocked,
        "_path": path,
    }


def parse_todo(path: Path) -> dict[str, Any]:
    """Parse a .ics file containing a single VTODO.

    Returns:
        Todo dictionary with uid, summary, status, priority, due, etc.
    """
    data = path.read_bytes()
    cal = icalendar.Calendar.from_ical(data)
    for component in cal.walk():
        if component.name == "VTODO":
            return _todo_to_dict(component, path)
    return {"uid": "", "summary": path.stem, "_path": path}


def list_todos(
    directory: Path,
    status: str = "open",
) -> list[dict[str, Any]]:
    """List todos filtered by status.

    Args:
        directory: Path to the todos directory.
        status: Filter — "open" (NEEDS-ACTION/IN-PROCESS), "completed",
                or "all".

    Returns:
        List of todo dicts.
    """
    if not directory.exists():
        return []

    todos: list[dict[str, Any]] = []

    for ics_path in sorted(directory.glob("*.ics")):
        try:
            todo = parse_todo(ics_path)
        except Exception as e:  # noqa: BLE001
            logger.warning("Failed to parse %s: %s", ics_path, e)
            continue

        if not todo.get("uid"):
            continue

        todo_status = todo.get("status", "NEEDS-ACTION")

        if status == "open" and todo_status in {"COMPLETED", "CANCELLED"}:
            continue
        if status == "completed" and todo_status != "COMPLETED":
            continue

        todos.append(todo)

    return todos


def compute_urgency(todo: dict[str, Any], now: datetime | None = None) -> float:
    """Compute Taskwarrior-style urgency score for a todo.

    Urgency is always computed at query time, never stored.

    Components:
    - Due date proximity: max(0, (14 - days_until) / 14) * 4.0
    - Priority: (10 - priority_int) / 9 * 3.0  (where 0 → none → score 0)
    - Age: min(age_days / 30, 1.0) * 1.0
    - Blocked: score *= 0.5
    """
    if now is None:
        now = datetime.now(tz=timezone.utc)

    score = 0.0

    # Due date component
    due = todo.get("due")
    if due is not None:
        days_until = (due - now).total_seconds() / 86400
        due_score = max(0.0, (14 - days_until) / 14) * 4.0
        score += due_score

    # Priority component (0 = undefined → no contribution)
    priority_int = todo.get("priority_int", 0)
    if priority_int > 0:
        score += (10 - priority_int) / 9 * 3.0

    # Age component
    created = todo.get("created")
    if created is not None:
        age_days = (now - created).total_seconds() / 86400
        score += min(age_days / 30, 1.0) * 1.0

    # Blocked penalty
    if todo.get("blocked"):
        score *= 0.5

    return round(score, 2)


def _build_todo_calendar(data: dict[str, Any], uid: str) -> icalendar.Calendar:
    """Build an iCalendar object with a VTODO from data."""
    cal = icalendar.Calendar()
    cal.add("prodid", "-//ICARUS//PIM//EN")
    cal.add("version", "2.0")

    todo = icalendar.Todo()
    todo.add("uid", uid)
    todo.add("summary", data.get("summary", ""))

    # Status
    status = data.get("status", "NEEDS-ACTION").upper()
    todo.add("status", status)

    # Priority
    priority = data.get("priority", "none")
    if isinstance(priority, str):
        priority_int = _PRIORITY_MAP.get(priority.lower(), 0)
    else:
        priority_int = int(priority)
    if priority_int > 0:
        todo.add("priority", priority_int)

    # Due date
    due = data.get("due")
    if isinstance(due, str):
        due = datetime.fromisoformat(due)
    if due:
        todo.add("due", due)

    # Optional fields
    if data.get("description"):
        todo.add("description", data["description"])

    # Timestamps
    now = datetime.now(tz=timezone.utc)
    todo.add("dtstamp", now)
    created = data.get("created")
    if created:
        if isinstance(created, str):
            created = datetime.fromisoformat(created)
        todo.add("created", created)
    else:
        todo.add("created", now)

    if status == "COMPLETED":
        completed_dt = data.get("completed") or now
        if isinstance(completed_dt, str):
            completed_dt = datetime.fromisoformat(completed_dt)
        todo.add("completed", completed_dt)

    # Attendees (related people)
    for att in data.get("attendees", []):
        _serialize_attendee(todo, att)

    # Assigned to (single person responsible for completing the task)
    assigned = data.get("assigned_to", "")
    if assigned:
        _serialize_attendee(todo, assigned, role="ASSIGNEE")

    # Related events/todos (RELATED-TO property)
    for rel_uid in data.get("related_to", []):
        if rel_uid:
            todo.add("related-to", rel_uid)

    # X-ICARUS fields
    if data.get("project"):
        todo.add("x-icarus-project", data["project"])
    if data.get("tags"):
        tags = data["tags"]
        if isinstance(tags, list):
            tags = ", ".join(tags)
        todo.add("x-icarus-tags", tags)
    if data.get("blocked"):
        todo.add("x-icarus-blocked", "true")

    cal.add_component(todo)
    return cal


def create_todo(directory: Path, data: dict[str, Any]) -> Path:
    """Create a new VTODO .ics file.

    Returns:
        Path to the created file.
    """
    directory.mkdir(parents=True, exist_ok=True)
    uid = data.get("uid") or str(uuid.uuid4())
    cal = _build_todo_calendar(data, uid)

    safe_uid = uid.replace("/", "_").replace("\\", "_")
    path = directory / f"{safe_uid}.ics"
    path.write_bytes(cal.to_ical())
    return path


def complete_todo(directory: Path, uid: str) -> Path:
    """Mark a todo as COMPLETED by UID.

    Returns:
        Path to the updated file.

    Raises:
        FileNotFoundError: If no todo with the given UID exists.
    """
    target = _find_todo_file(directory, uid)
    if target is None:
        msg = f"Todo not found: {uid}"
        raise FileNotFoundError(msg)

    existing = parse_todo(target)
    if existing.get("status") == "COMPLETED":
        return target
    existing["status"] = "COMPLETED"
    existing["completed"] = datetime.now(tz=timezone.utc)

    cal = _build_todo_calendar(existing, uid)
    target.write_bytes(cal.to_ical())
    return target


def _find_todo_file(directory: Path, uid: str) -> Path | None:
    """Find the .ics file containing a VTODO with the given UID."""
    return _find_ics_file(directory, uid, "VTODO")
