"""FlyDocs Local Backend — filesystem-based issue management.

All local tier operations are implemented here. The unified client
delegates to this module when tier is "local".
"""

import json
import re
from datetime import datetime
from pathlib import Path

from status_vocab import CANONICAL_STATUSES, CLOSED_STATUSES


# Status directories map to workflow states. Derived from the canonical
# vocabulary (FLY-1272) rather than retyped: every closed state shares the
# `done` directory, every other status gets its own lowercase one. A status
# added to the vocabulary therefore gets local-tier storage automatically
# instead of quietly having nowhere to live.
#
# FLY-1265: keyed on CLOSED_STATUSES, not TERMINAL_STATUSES — the terminal set
# narrowed to {COMPLETE, DUPLICATE} when ARCHIVED and CANCELED gained revival
# edges, and following it here would have moved every archived and canceled
# issue on disk into new directories for a reason that has nothing to do with
# where files live.
STATUSES = {
    status: ("done" if status in CLOSED_STATUSES else status.lower())
    for status in CANONICAL_STATUSES
}

ISSUE_TYPES = {"feature", "bug", "chore", "idea"}


def _issues_root(project_root: Path) -> Path:
    """Find flydocs/issues/ relative to project root."""
    root = project_root / "flydocs" / "issues"
    root.mkdir(parents=True, exist_ok=True)
    return root


def _ensure_dirs(project_root: Path) -> Path:
    """Ensure all status directories exist."""
    root = _issues_root(project_root)
    for dirname in set(STATUSES.values()):
        (root / dirname).mkdir(exist_ok=True)
    return root


def _counter_path(project_root: Path) -> Path:
    return project_root / ".flydocs" / "issues.counter"


def _next_id(project_root: Path) -> str:
    """Auto-increment and return next FD-XXX identifier."""
    path = _counter_path(project_root)
    path.parent.mkdir(parents=True, exist_ok=True)
    current = int(path.read_text().strip()) if path.exists() else 0
    next_num = current + 1
    path.write_text(str(next_num))
    return f"FD-{next_num:03d}"


def _slugify(title: str) -> str:
    slug = re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-")
    return slug[:50]


def _parse_issue(filepath: Path) -> dict:
    """Parse a markdown issue file into frontmatter dict + body + comments."""
    text = filepath.read_text()
    if not text.startswith("---"):
        raise ValueError(f"Invalid issue file: {filepath}")
    _, fm_raw, *rest = text.split("---", 2)
    body_and_comments = rest[0] if rest else ""

    # Parse YAML frontmatter manually (avoid pyyaml dependency)
    frontmatter = {}
    for line in fm_raw.strip().splitlines():
        if ":" in line:
            key, val = line.split(":", 1)
            val = val.strip()
            if val.isdigit():
                val = int(val)
            frontmatter[key.strip()] = val

    # Split body and comments
    parts = body_and_comments.split("\n---\n## Comments", 1)
    description = parts[0].strip()
    comments_raw = parts[1].strip() if len(parts) > 1 else ""

    comments = []
    if comments_raw:
        for block in re.split(r"\n(?=\*\*)", comments_raw):
            block = block.strip()
            if block:
                comments.append(block)

    return {**frontmatter, "description": description, "comments": comments, "_path": filepath}


def _find_issue(project_root: Path, ref: str) -> Path:
    """Find an issue file by its identifier across all directories."""
    root = _issues_root(project_root)
    prefix = ref.upper() + "-"
    for dirname in set(STATUSES.values()):
        dirpath = root / dirname
        if not dirpath.exists():
            continue
        for f in dirpath.iterdir():
            if f.name.upper().startswith(prefix) and f.suffix == ".md":
                return f
    raise FileNotFoundError(f"Issue {ref} not found")


def _status_from_path(filepath: Path) -> str:
    """Get the FlyDocs status from a file's parent directory."""
    dirname = filepath.parent.name
    for status, dname in STATUSES.items():
        if dname == dirname:
            return status
    return "BACKLOG"


def _write_issue(filepath: Path, frontmatter: dict, description: str, comments: list[str]) -> None:
    """Write an issue file with frontmatter, description, and comments."""
    fm_lines = "\n".join(f"{k}: {v}" for k, v in frontmatter.items() if not k.startswith("_"))
    parts = [f"---\n{fm_lines}\n---\n\n{description}"]
    if comments:
        parts.append("\n---\n## Comments\n\n" + "\n\n".join(comments))
    filepath.write_text("".join(parts) + "\n")


# --- Public API (matches relay backend interface) ---


def create_issue(project_root: Path, title: str, issue_type: str, description: str = "",
                 priority: int = 3, estimate: int = 0,
                 assignee: str = "", triage: bool = False, **_kwargs: object) -> dict:
    root = _ensure_dirs(project_root)
    identifier = _next_id(project_root)
    slug = _slugify(title)
    filename = f"{identifier}-{slug}.md"
    now = datetime.now().strftime("%Y-%m-%d")
    filepath = root / "backlog" / filename

    frontmatter = {
        "id": identifier,
        "title": title,
        "type": issue_type,
        "priority": priority,
        "estimate": estimate,
        "assignee": assignee,
        "created": now,
        "updated": now,
    }
    if triage:
        frontmatter["triage"] = "true"

    _write_issue(filepath, frontmatter, description or f"## Context\n\n{title}", [])
    return {"id": identifier, "identifier": identifier, "title": title, "url": ""}


def transition(project_root: Path, ref: str, status: str, comment: str) -> dict:
    filepath = _find_issue(project_root, ref)
    data = _parse_issue(filepath)
    prev_status = _status_from_path(filepath)

    new_status = status.upper()
    if new_status not in STATUSES:
        raise ValueError(f"Invalid status: {new_status}. Valid: {', '.join(STATUSES.keys())}")

    target_dir = _issues_root(project_root) / STATUSES[new_status]
    target_dir.mkdir(exist_ok=True)
    new_path = target_dir / filepath.name

    now = datetime.now().strftime("%Y-%m-%d %H:%M")
    data["comments"].append(f"**{new_status}** — {comment}\n_{now}_")
    data["updated"] = datetime.now().strftime("%Y-%m-%d")

    fm = {k: v for k, v in data.items() if k not in ("description", "comments", "_path")}
    _write_issue(new_path, fm, data["description"], data["comments"])
    if new_path != filepath:
        filepath.unlink()

    return {"success": True, "issue": ref, "previousStatus": prev_status, "newStatus": new_status}


def add_comment(project_root: Path, ref: str, body: str) -> dict:
    filepath = _find_issue(project_root, ref)
    data = _parse_issue(filepath)
    now = datetime.now().strftime("%Y-%m-%d %H:%M")
    data["comments"].append(f"{body}\n_{now}_")
    data["updated"] = datetime.now().strftime("%Y-%m-%d")

    fm = {k: v for k, v in data.items() if k not in ("description", "comments", "_path")}
    _write_issue(filepath, fm, data["description"], data["comments"])
    return {"success": True, "commentId": len(data["comments"])}


def list_issues(project_root: Path, status: str = "", assignee: str = "",
                limit: int = 50, **_kwargs: object) -> list[dict]:
    root = _issues_root(project_root)
    results: list[dict] = []
    dirs_to_scan = [STATUSES[status]] if status and status in STATUSES else list(set(STATUSES.values()))

    for dirname in dirs_to_scan:
        dirpath = root / dirname
        if not dirpath.exists():
            continue
        for f in sorted(dirpath.iterdir()):
            if f.suffix != ".md":
                continue
            data = _parse_issue(f)
            if assignee and data.get("assignee", "") != assignee:
                continue
            results.append({
                "id": data.get("id", ""),
                "identifier": data.get("id", ""),
                "title": data.get("title", ""),
                "status": _status_from_path(f),
                "assignee": data.get("assignee", ""),
                "priority": data.get("priority", 3),
            })
            if len(results) >= limit:
                return results
    return results


def get_issue(project_root: Path, ref: str, **_kwargs: object) -> dict:
    filepath = _find_issue(project_root, ref)
    data = _parse_issue(filepath)
    return {
        "id": data.get("id", ""),
        "identifier": data.get("id", ""),
        "title": data.get("title", ""),
        "description": data.get("description", ""),
        "status": _status_from_path(filepath),
        "assignee": data.get("assignee", ""),
        "priority": data.get("priority", 3),
        "estimate": data.get("estimate", 0),
        "comments": data.get("comments", []),
    }


def assign_issue(project_root: Path, ref: str, assignee: str | None) -> dict:
    filepath = _find_issue(project_root, ref)
    data = _parse_issue(filepath)
    if assignee is None:
        data.pop("assignee", None)
    else:
        data["assignee"] = assignee
    data["updated"] = datetime.now().strftime("%Y-%m-%d")

    fm = {k: v for k, v in data.items() if k not in ("description", "comments", "_path")}
    _write_issue(filepath, fm, data["description"], data["comments"])
    return {"success": True, "issue": ref, "assignee": assignee}


def update_description(project_root: Path, ref: str, text: str) -> dict:
    filepath = _find_issue(project_root, ref)
    data = _parse_issue(filepath)
    data["updated"] = datetime.now().strftime("%Y-%m-%d")

    fm = {k: v for k, v in data.items() if k not in ("description", "comments", "_path")}
    _write_issue(filepath, fm, text, data["comments"])
    return {"success": True, "issue": ref}


def update_issue(project_root: Path, ref: str, **fields: object) -> dict:
    """Bulk update — set multiple fields on an issue."""
    filepath = _find_issue(project_root, ref)
    data = _parse_issue(filepath)
    updated: list[str] = []

    # Handle state transition separately
    state = fields.get("state")
    comment = fields.get("comment")
    if state and isinstance(state, str):
        result = transition(project_root, ref, state, str(comment or f"Transitioned to {state}"))
        updated.append("state")
        # Re-find the file after transition (it may have moved)
        filepath = _find_issue(project_root, ref)
        data = _parse_issue(filepath)

    # Handle comment separately (if no state transition already handled it)
    if comment and isinstance(comment, str) and "state" not in updated:
        add_comment(project_root, ref, comment)
        updated.append("comment")
        data = _parse_issue(filepath)

    # Update simple fields
    for field in ("title", "priority", "estimate", "assignee"):
        val = fields.get(field)
        if val is not None:
            data[field] = val
            updated.append(field)

    # Update description
    desc = fields.get("description")
    if desc and isinstance(desc, str):
        data["description"] = desc
        updated.append("description")

    data["updated"] = datetime.now().strftime("%Y-%m-%d")
    fm = {k: v for k, v in data.items() if k not in ("description", "comments", "_path")}
    _write_issue(filepath, fm, data.get("description", ""), data.get("comments", []))

    return {"success": True, "issue": ref, "updated": updated}


def estimate_issue(project_root: Path, ref: str, estimate: int) -> dict:
    filepath = _find_issue(project_root, ref)
    data = _parse_issue(filepath)
    data["estimate"] = estimate
    data["updated"] = datetime.now().strftime("%Y-%m-%d")

    fm = {k: v for k, v in data.items() if k not in ("description", "comments", "_path")}
    _write_issue(filepath, fm, data["description"], data.get("comments", []))
    return {"success": True, "issue": ref, "estimate": estimate}


def priority_issue(project_root: Path, ref: str, priority: int) -> dict:
    filepath = _find_issue(project_root, ref)
    data = _parse_issue(filepath)
    data["priority"] = priority
    data["updated"] = datetime.now().strftime("%Y-%m-%d")

    fm = {k: v for k, v in data.items() if k not in ("description", "comments", "_path")}
    _write_issue(filepath, fm, data["description"], data.get("comments", []))
    return {"success": True, "issue": ref, "priority": priority}


def link_issues(project_root: Path, ref: str, related_ref: str, link_type: str) -> dict:
    add_comment(project_root, ref, f"Linked ({link_type}): {related_ref}")
    add_comment(project_root, related_ref, f"Linked ({link_type}): {ref}")
    return {"success": True, "type": link_type}


def project_update(project_root: Path, health: str, body: str, **_kwargs: object) -> dict:
    """Write a project update as a markdown file."""
    updates_dir = project_root / "flydocs" / "updates"
    updates_dir.mkdir(parents=True, exist_ok=True)

    now = datetime.now()
    timestamp = now.strftime("%Y%m%d-%H%M%S")
    filename = f"{timestamp}.md"

    content = f"---\nhealth: {health}\ndate: {now.strftime('%Y-%m-%d')}\n---\n\n{body}\n"
    (updates_dir / filename).write_text(content)

    return {"success": True, "id": timestamp}


def status_summary(project_root: Path) -> dict:
    root = _issues_root(project_root)
    counts: dict[str, int] = {}
    total = 0
    for status, dirname in STATUSES.items():
        dirpath = root / dirname
        if not dirpath.exists():
            continue
        count = len([f for f in dirpath.iterdir() if f.suffix == ".md"])
        if count > 0:
            counts[status] = counts.get(status, 0) + count
            total += count
    return {"statuses": counts, "total": total}
