"""LLM-rewrite privacy cascade — true right-to-erasure for prose.

The line-strip cascade in `privacy.py:forget_entity` is correct but
destructive: it removes every line mentioning a redacted name, which
often leaves the article with broken sentences or dangling references.
This module does the same job properly — it sends each affected article
to the LLM with instructions to rewrite the prose preserving meaning and
flow while removing ALL references to the forgotten entity and its
aliases.

The rewrite runs through the work queue so a bulk forget (e.g. "purge
everything about Alice") doesn't block the HTTP request. Each article is
one queue job; failures are retried by the queue with exponential
backoff. The result is a NEW version of the article with the same title
and taxonomy path but stripped prose. The original is saved to `.history/`
via `VaultWriter`'s normal versioning.

Contract note: `write_vault` contract action is checked before every
rewrite. If the contract blocks it (e.g. "never auto-write during
business hours"), the job is quarantined and the user is notified.
"""

from __future__ import annotations

import logging
from pathlib import Path

log = logging.getLogger(__name__)


_REWRITE_PROMPT = """You are performing a right-to-erasure operation on a personal knowledge base.

A user has invoked their right-to-erasure for a specific entity. Your task is
to REWRITE the following article to remove ALL references to the entity and its
aliases while preserving:
  - The article's title, structure, and overall topic
  - Factual content that is INDEPENDENT of the forgotten entity
  - Coherent sentences and flow (no dangling references, no orphan pronouns)

You MUST:
  - Remove every mention of the forgotten entity, by any name or alias
  - Remove any sentence or paragraph whose meaning depends entirely on the entity
  - Rewrite sentences where the entity appears incidentally (e.g. "Alice and Bob
    met at the conference" → "Bob attended the conference")
  - Preserve frontmatter (the YAML block between the two `---` markers) EXACTLY
    unless a field value literally contains a forgotten name
  - Preserve Markdown structure: headings, lists, code blocks, [[wikilinks]]

You MUST NOT:
  - Invent new facts
  - Change the article's topic or point of view
  - Delete the whole article (return an empty body) — if the entity is the
    entire topic, return the article with a single line: "[Content redacted
    at user request]"
  - Add any commentary, explanation, or note that an erasure happened

Return ONLY the rewritten article body (same format as input)."""


def build_rewrite_system_prompt(
    redacted_names: list[str],
    entity_type: str = "",
) -> str:
    """Build the system prompt for an LLM rewrite job."""
    names_block = "\n".join(f"  - {n}" for n in redacted_names if n)
    type_note = f" (type: {entity_type})" if entity_type else ""
    return (
        _REWRITE_PROMPT
        + f"\n\nForgotten entity names{type_note}:\n{names_block}\n"
    )


async def rewrite_article(
    llm: object,
    article_path: Path,
    redacted_names: list[str],
    entity_type: str = "",
    *,
    timeout_s: float = 60.0,
) -> dict:
    """Send one article to the LLM for privacy-preserving rewrite.

    Returns a dict:
        {
            "path": str,
            "rewritten": bool,
            "before_chars": int,
            "after_chars": int,
            "reason": str,
        }

    Never raises — caller handles retry scheduling.
    """
    result: dict = {
        "path": str(article_path),
        "rewritten": False,
        "before_chars": 0,
        "after_chars": 0,
        "reason": "",
    }

    try:
        original = article_path.read_text(encoding="utf-8")
    except OSError as e:
        result["reason"] = f"read failed: {e}"
        return result

    result["before_chars"] = len(original)

    # Short-circuit if no redacted names actually appear
    if not any(name in original for name in redacted_names if name):
        result["reason"] = "no matching names in article"
        return result

    system = build_rewrite_system_prompt(redacted_names, entity_type)

    try:
        import asyncio

        from agntspace.llm.base import Message

        response = await asyncio.wait_for(
            llm.complete(  # type: ignore[attr-defined]
                messages=[Message(role="user", content=original)],
                system=system,
                max_tokens=4000,
                temperature=0.2,
            ),
            timeout=timeout_s,
        )
        new_content = response.content.strip()
    except Exception as e:
        result["reason"] = f"llm failed: {e}"
        return result

    if not new_content:
        result["reason"] = "empty rewrite"
        return result

    # Sanity check: any redacted name STILL present? Fall back to the
    # line-strip cascade on top of the LLM output to guarantee cleanliness.
    remaining = [n for n in redacted_names if n and n in new_content]
    if remaining:
        log.warning(
            "LLM rewrite of %s still contains %d forgotten names — stripping",
            article_path, len(remaining),
        )
        new_content = "\n".join(
            line for line in new_content.splitlines()
            if not any(n in line for n in remaining)
        )

    try:
        article_path.write_text(new_content, encoding="utf-8")
    except OSError as e:
        result["reason"] = f"write failed: {e}"
        return result

    result["rewritten"] = True
    result["after_chars"] = len(new_content)
    result["reason"] = "rewritten via LLM"
    return result
