"""Pre-compaction memory flush: silently ask the model to persist important info before context overflow."""

from __future__ import annotations

from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING

from loguru import logger

from asedclaw.agent.token_utils import estimate_messages_tokens

if TYPE_CHECKING:
    from asedclaw.config.schema import MemoryFlushConfig
    from asedclaw.providers.base import LLMProvider
    from asedclaw.session.manager import Session

# Safety constraints shared with consolidation prompt.
MEMORY_FLUSH_SAFETY_HINTS = [
    "Store durable memories only in memory/YYYY-MM-DD.md (create memory/ dir if needed).",
    "If memory/YYYY-MM-DD.md already exists, APPEND new content — do NOT overwrite existing entries.",
    "Treat MEMORY.md, SOUL.md, TOOLS.md, and AGENTS.md as READ-ONLY during this flush.",
]


def build_flush_system_prompt() -> str:
    safety = "\n".join(f"- {h}" for h in MEMORY_FLUSH_SAFETY_HINTS)
    return (
        "Pre-compaction memory flush turn. "
        "You are about to lose older context due to compaction. "
        "Save any important information to the daily memory file now.\n\n"
        f"## Safety Rules\n{safety}"
    )


def build_flush_user_prompt(date_str: str) -> str:
    return (
        f"Write any lasting notes, decisions, or facts worth remembering to memory/{date_str}.md. "
        "Use the write_file tool to append content. "
        "If there is nothing important to save, reply with NO_REPLY."
    )


def should_flush(
    total_tokens: int,
    config: MemoryFlushConfig,
    flush_done: bool,
) -> bool:
    """Return True if a memory flush should be triggered."""
    if not config.enabled or flush_done:
        return False
    threshold = config.context_window - config.reserve_tokens_floor - config.soft_threshold_tokens
    return total_tokens >= threshold


def should_force_flush_by_size(
    transcript_bytes: int,
    config: MemoryFlushConfig,
    flush_done: bool,
) -> bool:
    """Return True if transcript size exceeds force-flush threshold."""
    if not config.enabled or flush_done:
        return False
    return transcript_bytes >= config.force_flush_transcript_bytes


async def run_memory_flush(
    session: Session,
    provider: LLMProvider,
    model: str,
    workspace: Path,
    config: MemoryFlushConfig,
) -> bool:
    """Execute a silent memory flush turn.

    Injects a flush prompt and runs an agno agent that can only write to the
    daily memory file.  Returns True on success.
    """
    try:
        from agno.agent.agent import Agent
        from agno.models.openai import OpenAILike
        from agno.tools import tool
    except Exception:
        logger.exception("Agno runtime unavailable for memory flush")
        return False

    memory_dir = workspace / "memory"
    memory_dir.mkdir(parents=True, exist_ok=True)
    date_str = datetime.now().strftime("%Y-%m-%d")
    daily_file = memory_dir / f"{date_str}.md"

    @tool(name="write_file", description="Append content to the daily memory file.")
    async def write_file(content: str) -> str:
        """Append *content* to today's daily memory log."""
        with open(daily_file, "a", encoding="utf-8") as f:
            f.write(content.rstrip() + "\n\n")
        return f"Appended to {daily_file.name}"

    # Build a concise conversation summary for the flush agent
    recent = session.messages[-40:] if len(session.messages) > 40 else session.messages
    summary_lines: list[str] = []
    for m in recent:
        content = m.get("content")
        if not content or not isinstance(content, str):
            continue
        role = m.get("role", "?").upper()
        ts = m.get("timestamp", "")[:16]
        preview = content[:200]
        summary_lines.append(f"[{ts}] {role}: {preview}")

    flush_prompt = build_flush_user_prompt(date_str)
    if summary_lines:
        flush_prompt += "\n\n## Recent conversation context\n" + "\n".join(summary_lines)

    model_kwargs: dict[str, str] = {"id": model}
    if api_key := getattr(provider, "api_key", None):
        model_kwargs["api_key"] = api_key
    if api_base := getattr(provider, "api_base", None):
        model_kwargs["base_url"] = api_base

    try:
        agent = Agent(
            model=OpenAILike(**model_kwargs),
            tools=[write_file],
            instructions=build_flush_system_prompt(),
            stream=False,
        )
        response = await agent.arun(flush_prompt, stream=False)
        content = getattr(response, "content", str(response)) if response else ""
        if "NO_REPLY" in str(content):
            logger.debug("Memory flush: model had nothing to save")
        else:
            logger.info("Memory flush completed, wrote to {}", daily_file.name)
        return True
    except Exception:
        logger.exception("Memory flush failed")
        return False
