"""Changeset commands — create, status, and consume changelog fragments."""

from __future__ import annotations

import re
import uuid
from datetime import date
from pathlib import Path

from afd import CommandResult, error, success

from botcore.utils.workspace import find_workspace

CHANGESET_DIR = ".changeset"
VALID_TYPES = ("added", "changed", "deprecated", "removed", "fixed", "security")
_CATEGORY_ORDER = ["Added", "Changed", "Deprecated", "Removed", "Fixed", "Security"]


def _changeset_dir(ws: Path) -> Path:
    return ws / CHANGESET_DIR


def _list_changeset_files(changeset_dir: Path) -> list[Path]:
    """List all changeset .md files, excluding README.md."""
    if not changeset_dir.exists():
        return []
    return sorted(
        f for f in changeset_dir.glob("*.md")
        if f.name.lower() != "readme.md"
    )


def _parse_changeset(path: Path) -> tuple[str | None, str]:
    """Parse a changeset file into (type, description)."""
    content = path.read_text(encoding="utf-8").strip()
    if not content.startswith("---"):
        return None, content

    end = content.find("---", 3)
    if end <= 0:
        return None, content

    frontmatter = content[3:end]
    body = content[end + 3:].strip()

    change_type = None
    for line in frontmatter.splitlines():
        if ":" not in line:
            continue
        key, value = line.split(":", 1)
        if key.strip().lower() == "type":
            change_type = value.strip().lower()

    return change_type, body


async def changeset_create(
    change_type: str,
    description: str,
) -> CommandResult[dict]:
    """Create a changeset file for the next release.

    Args:
        change_type: One of: added, changed, deprecated, removed, fixed, security.
        description: Changelog entry text (markdown). Bold the component name.
    """
    ws = find_workspace()
    if not ws:
        return error(
            "NO_WORKSPACE",
            "Could not find workspace root",
            suggestion="Run from within a Git repository",
        )

    change_type = change_type.strip().lower()
    if change_type not in VALID_TYPES:
        return error(
            "INVALID_TYPE",
            f"Invalid changeset type: {change_type!r}",
            suggestion=f"Use one of: {', '.join(VALID_TYPES)}",
        )

    description = description.strip()
    if not description:
        return error(
            "EMPTY_DESCRIPTION",
            "Description cannot be empty",
            suggestion="Provide a changelog entry describing the change",
        )

    cs_dir = _changeset_dir(ws)
    cs_dir.mkdir(parents=True, exist_ok=True)

    filename = f"{uuid.uuid4().hex[:8]}.md"
    filepath = cs_dir / filename

    content = f"---\ntype: {change_type}\n---\n\n{description}\n"
    filepath.write_text(content, encoding="utf-8")

    return success(
        data={
            "path": str(filepath.relative_to(ws)),
            "type": change_type,
            "description": description,
        }
    )


async def changeset_status() -> CommandResult[dict]:
    """Show pending changeset files and their types."""
    ws = find_workspace()
    if not ws:
        return error(
            "NO_WORKSPACE",
            "Could not find workspace root",
            suggestion="Run from within a Git repository",
        )

    files = _list_changeset_files(_changeset_dir(ws))

    entries: list[dict] = []
    by_type: dict[str, int] = {}
    for f in files:
        change_type, description = _parse_changeset(f)
        entries.append({
            "file": str(f.relative_to(ws)),
            "type": change_type or "unknown",
            "description": description[:100],
        })
        key = change_type or "unknown"
        by_type[key] = by_type.get(key, 0) + 1

    return success(
        data={
            "count": len(entries),
            "by_type": by_type,
            "entries": entries,
        }
    )


async def changeset_consume(
    version: str | None = None,
) -> CommandResult[dict]:
    """Consume changeset files and update CHANGELOG.md.

    Reads all pending changesets, groups by type, generates a changelog
    section, inserts it into CHANGELOG.md, and deletes consumed files.

    Args:
        version: Version string (e.g. "1.2.0"). If None, uses [Unreleased].
    """
    ws = find_workspace()
    if not ws:
        return error(
            "NO_WORKSPACE",
            "Could not find workspace root",
            suggestion="Run from within a Git repository",
        )

    cs_dir = _changeset_dir(ws)
    files = _list_changeset_files(cs_dir)
    if not files:
        return error(
            "NO_CHANGESETS",
            "No changeset files found",
            suggestion="Create changesets with changeset_create() "
            f"or add files to {CHANGESET_DIR}/",
        )

    changelog = ws / "CHANGELOG.md"
    if not changelog.exists():
        return error(
            "NO_CHANGELOG",
            "CHANGELOG.md not found",
            suggestion="Create a CHANGELOG.md file first",
        )

    # Parse all changesets
    grouped: dict[str, list[str]] = {cat.lower(): [] for cat in _CATEGORY_ORDER}
    for f in files:
        change_type, description = _parse_changeset(f)
        if change_type and change_type in grouped:
            grouped[change_type].append(description)
        elif description:
            grouped["added"].append(description)

    # Build the new section
    if version:
        heading = f"## [{version}] - {date.today().isoformat()}"
    else:
        heading = "## [Unreleased]"

    section_lines = [heading, ""]
    categories_used = []
    for category in _CATEGORY_ORDER:
        entries = grouped[category.lower()]
        if entries:
            categories_used.append(category)
            section_lines.append(f"### {category}")
            section_lines.append("")
            for entry in entries:
                # Ensure entry starts with "- " for list formatting
                if not entry.startswith("- "):
                    entry = f"- {entry}"
                section_lines.append(entry)
            section_lines.append("")

    new_section = "\n".join(section_lines)

    # Insert into CHANGELOG.md
    content = changelog.read_text(encoding="utf-8")
    # Find the first ## [ heading to insert before
    pattern = re.compile(r"^## \[", re.MULTILINE)
    match = pattern.search(content)
    if match:
        # Replace existing [Unreleased] or insert before first version heading
        unreleased_pattern = re.compile(
            r"^## \[Unreleased\]\s*\n(.*?)(?=^## \[|\Z)",
            re.MULTILINE | re.DOTALL,
        )
        unreleased_match = unreleased_pattern.search(content)
        if unreleased_match:
            # Replace the [Unreleased] section
            if version:
                # Replace [Unreleased] with versioned section, add fresh [Unreleased]
                replacement = "## [Unreleased]\n\n" + new_section
            else:
                replacement = new_section
            content = (
                content[:unreleased_match.start()]
                + replacement
                + content[unreleased_match.end():]
            )
        else:
            # No [Unreleased] section, insert before first version
            content = content[:match.start()] + new_section + "\n" + content[match.start():]
    else:
        # No version headings at all, append after header
        content = content.rstrip() + "\n\n" + new_section

    changelog.write_text(content, encoding="utf-8")

    # Delete consumed changeset files
    consumed = len(files)
    for f in files:
        f.unlink()

    return success(
        data={
            "consumed": consumed,
            "version": version,
            "categories": categories_used,
        }
    )
