"""Draft document review adapter.

Handles section-by-section review of markdown drafts generated by
``axi signal draft``.  Sections are split at ``##`` boundaries so each
initiative / top-level heading becomes one ReviewItem.

Extra commands beyond the standard set:
    E — Edit the section in $EDITOR
    D — Drop (reject) the section entirely
    C — Add a comment without resolving the item
"""

from __future__ import annotations

import os
import secrets
import subprocess
import tempfile
from pathlib import Path

# ── constants ────────────────────────────────────────────────────────
from axiom import REPO_ROOT as _REPO_ROOT
from axiom.infra.hash_utils import MEDIUM, fingerprint
from axiom.review.models import (
    ReviewItem,
    ReviewSession,
    ReviewSessionStore,
    _now_iso,
    _source_hash,
)

_RUNTIME_DIR = _REPO_ROOT / "runtime"
_DRAFTS_DIR = _RUNTIME_DIR / "drafts"
_APPROVED_DIR = _DRAFTS_DIR / "approved"


# ── section parser ───────────────────────────────────────────────────

def parse_draft_sections(content: str) -> list[ReviewItem]:
    """Split a markdown draft into ReviewItems at ``##`` boundaries.

    The preamble (everything before the first ``##``) becomes item 0
    with heading "(Preamble)".  Each ``## Heading`` block becomes one
    item.
    """
    lines = content.split("\n")
    sections: list[tuple[str, list[str]]] = []
    current_heading = "(Preamble)"
    current_lines: list[str] = []

    for line in lines:
        if line.startswith("## "):
            # Save previous section
            if current_lines or current_heading == "(Preamble)":
                sections.append((current_heading, current_lines))
            current_heading = line
            current_lines = [line]
        else:
            current_lines.append(line)

    # Save final section
    if current_lines:
        sections.append((current_heading, current_lines))

    items = []
    for heading, section_lines in sections:
        body = "\n".join(section_lines).strip()
        if not body:
            continue
        item_id = fingerprint(body, length=MEDIUM)
        items.append(ReviewItem(
            item_id=item_id,
            heading=heading,
            content=body,
        ))

    return items


# ── draft discovery ──────────────────────────────────────────────────

def find_draft(drafts_dir: Path | None = None, file_arg: str | None = None) -> Path | None:
    """Locate a draft file.

    If *file_arg* is given, resolve it directly.  Otherwise pick the
    most recent draft markdown file in *drafts_dir* (excluding changelogs
    and dotfiles).
    """
    if file_arg:
        p = Path(file_arg)
        if p.exists():
            return p
        # Try relative to drafts dir
        drafts_dir = drafts_dir or _DRAFTS_DIR
        candidate = drafts_dir / file_arg
        if candidate.exists():
            return candidate
        return None

    drafts_dir = drafts_dir or _DRAFTS_DIR
    if not drafts_dir.is_dir():
        return None

    # Pick the most recent .md file, excluding changelogs (table format,
    # not prose) and dotfiles.
    candidates = sorted(
        [
            f for f in drafts_dir.glob("*.md")
            if not f.name.startswith(("changelog_", "."))
        ],
        key=lambda f: f.stat().st_mtime,
        reverse=True,
    )
    return candidates[0] if candidates else None


# ── approved output writer ───────────────────────────────────────────

def write_approved_draft(session: ReviewSession, approved_dir: Path | None = None) -> Path | None:
    """Assemble approved/edited sections into an output file.

    Dropped (rejected) sections are omitted.  Skipped and pending
    sections are included as-is (they weren't explicitly removed).

    Returns the path to the written file, or None if nothing to write.
    """
    approved_dir = approved_dir or _APPROVED_DIR
    approved_dir.mkdir(parents=True, exist_ok=True)

    parts: list[str] = []
    for item in session.items:
        if item.status == "rejected":
            continue
        # If edited, use the edited content from the last decision
        edited_decisions = [d for d in item.decisions if d.edited_content]
        if edited_decisions:
            parts.append(edited_decisions[-1].edited_content)
        else:
            parts.append(item.content)

    if not parts:
        return None

    content = "\n\n".join(parts) + "\n"

    # Derive output filename from source
    source_name = Path(session.source).stem
    out_path = approved_dir / f"{source_name}.md"
    out_path.write_text(content, encoding="utf-8")
    return out_path


# ── session factory ──────────────────────────────────────────────────

def create_draft_session(
    draft_path: Path,
    store: ReviewSessionStore,
    *,
    fresh: bool = False,
) -> ReviewSession:
    """Create or resume a review session for a draft file.

    If a session for this source already exists and *fresh* is False,
    resume it (unless the source content changed).
    """
    content = draft_path.read_text(encoding="utf-8")
    source_str = str(draft_path)
    content_hash = _source_hash(content)

    if not fresh:
        existing = store.find_by_source(source_str)
        if existing:
            if existing.source_hash == content_hash:
                reviewed, total = existing.progress
                print(f"Resuming review ({reviewed}/{total} done)")
                return existing
            else:
                print("Source changed since last review. Starting fresh.")
                store.remove(existing.session_id)

    items = parse_draft_sections(content)
    suffix = secrets.token_hex(4)
    session_id = f"draft_{draft_path.stem}_{_now_iso()[:19].replace(':', '-')}_{suffix}"

    session = ReviewSession(
        session_id=session_id,
        session_type="draft",
        source=source_str,
        source_hash=content_hash,
        started_at=_now_iso(),
        items=items,
    )
    store.save(session)
    return session


# ── adapter ──────────────────────────────────────────────────────────

def _register_in_publisher(session: ReviewSession, approved_path: Path) -> None:
    """Record the reviewed draft in publisher's state store.

    This bridges the review framework into publisher's document lifecycle
    so that ``axi pub status`` can show reviewed drafts and
    ``axi pub publish`` can pick them up for delivery.
    """
    try:
        from axiom.extensions.builtins.prt_agent.state import (
            DocumentState,
            PublicationRecord,
            StateStore,
        )

        state_path = _REPO_ROOT / ".neut" / ".publisher-state.json"
        store = StateStore(state_path)

        doc_id = approved_path.stem
        approved_path = approved_path.resolve()
        rel_path = str(approved_path.relative_to(_REPO_ROOT))

        _reviewed, total = session.progress
        accepted = sum(1 for i in session.items if i.status == "accepted")
        edited = sum(1 for i in session.items if i.status == "edited")
        rejected = sum(1 for i in session.items if i.status == "rejected")

        doc_state = store.get(doc_id) or DocumentState(
            doc_id=doc_id,
            source_path=rel_path,
        )
        doc_state.source_path = rel_path
        doc_state.status = "reviewed"
        doc_state.active_draft = PublicationRecord(
            storage_id=doc_id,
            url=str(approved_path),
            version="reviewed",
            published_at=_now_iso(),
            storage_provider="local",
        )
        doc_state.pending_comments = [
            {"review_session": session.session_id,
             "reviewed_at": session.last_reviewed_at,
             "accepted": accepted,
             "edited": edited,
             "rejected": rejected,
             "total": total},
        ]
        store.update(doc_state)
        print(f"Registered in publisher state: {doc_id}")
        print(f"  axi pub status {approved_path}")
    except Exception as e:
        # Non-fatal — review is still saved even if publisher bridge fails
        print(f"  (publisher registration skipped: {e})")


class DraftReviewAdapter:
    """ReviewAdapter implementation for markdown draft documents."""

    def display_item(self, item: ReviewItem, index: int, total: int) -> None:
        print(f"\n--- [{index + 1}/{total}] {item.heading} ---")
        print()
        print(item.content)

    def display_summary(self, items: list[ReviewItem]) -> None:
        """Print all sections for quick-mode overview."""
        for item in items:
            if item.status == "pending":
                print(item.content)
                print()

    def get_commands(self) -> dict[str, str]:
        return {
            "E": "Edit in $EDITOR",
            "D": "Drop section",
            "C": "Comment",
        }

    def handle_command(self, cmd: str, item: ReviewItem) -> str | None:
        if cmd == "E":
            return self._edit_section(item)
        elif cmd == "D":
            print("  Dropped")
            return "rejected"
        elif cmd == "C":
            return self._add_comment(item)
        return None

    def on_session_complete(self, session: ReviewSession) -> None:
        approved_path = write_approved_draft(session)
        if approved_path:
            print(f"\nApproved draft written to: {approved_path}")
            _register_in_publisher(session, approved_path)

    # ── command implementations ──────────────────────────────────────

    @staticmethod
    def _edit_section(item: ReviewItem) -> str | None:
        """Open section in $EDITOR and return 'edited' if changed."""
        editor = os.environ.get("EDITOR", "vi")
        tmp_path: str | None = None
        try:
            with tempfile.NamedTemporaryFile(
                mode="w", suffix=".md", delete=False
            ) as f:
                f.write(item.content)
                tmp_path = f.name

            result = subprocess.run([editor, tmp_path])
            if result.returncode != 0:
                print("  Editor exited with error")
                return None

            with open(tmp_path, encoding="utf-8") as f:
                edited = f.read()

            if edited.strip() == item.content.strip():
                print("  No changes made")
                return None

            item.content = edited.strip()
            print("  Edited")
            return "edited"
        except Exception as e:
            print(f"  Editor error: {e}")
            return None
        finally:
            try:
                if tmp_path:
                    os.unlink(tmp_path)
            except (OSError, UnboundLocalError):
                pass

    @staticmethod
    def _add_comment(item: ReviewItem) -> None:
        """Prompt for a comment and attach it to the item."""
        try:
            comment = input("  Comment: ").strip()
            if comment:
                item.context += f"\n[Comment] {comment}" if item.context else f"[Comment] {comment}"
                print("  Comment added")
        except (EOFError, KeyboardInterrupt):
            pass
        return None
