"""Session init for IDE chat: one-shot check + agent prompt.

Run at chat start via `co session-init`. Emits a prompt block for the agent
to refresh project state (sync/adapt/read status) without manual user wording.
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from cokodo_agent.config import VERSION
from cokodo_agent.sync import diff_protocol
from cokodo_agent.updater import UpdateChecker, _NETWORK_TIMEOUT


def run_session_init(
    agent_dir: Path,
    *,
    offline: bool = False,
    timeout: float | None = None,
) -> dict[str, Any]:
    """
    Collect package update hint + protocol drift; build structured result.

    Returns a dict suitable for JSON serialization and for building the
    agent prompt. Keys:
    - package_current, package_latest (optional), package_update_available
    - protocol_local, protocol_remote, protocol_drift, drift_file_count
    - actions: list of suggested shell steps
    - prompt: full text for the agent
    """
    wait = float(timeout if timeout is not None else _NETWORK_TIMEOUT) + 1.0

    checker = UpdateChecker()
    checker.start()
    update_result = checker.result(timeout=wait)
    package_update_available = False
    package_latest: str | None = None
    if update_result:
        current, latest = update_result
        package_update_available = True
        package_latest = latest
    else:
        current = VERSION

    diff_results: list[Any] = []
    local_version = "unknown"
    remote_version = "unknown"
    drift_file_count = 0
    diff_error: str | None = None

    try:
        diff_results, local_version, remote_version = diff_protocol(
            agent_dir, offline=offline
        )
        changes = [
            r
            for r in diff_results
            if r.status != "unchanged" and not r.path.startswith("project/")
        ]
        drift_file_count = len(changes)
    except Exception as e:
        diff_error = str(e)

    version_mismatch = local_version != remote_version and not diff_error
    protocol_drift = drift_file_count > 0 or (
        version_mismatch and not diff_error
    )

    actions: list[str] = []
    if package_update_available and package_latest:
        actions.append(
            "User should close IDE and run: pip install --upgrade cokodo-agent "
            f"(current {current} → PyPI {package_latest})."
        )
    if diff_error:
        actions.append(
            f"Protocol diff failed ({diff_error}); continue with local .agent/ or retry with network."
        )
    elif protocol_drift:
        actions.append(
            "Run: co sync -y"
            + (" --online" if not offline else "")
            + "  # then co adapt cursor (and other IDEs as needed)"
        )
        actions.append(
            "Re-read .agent/project/status.md; update Quick Reference if templates changed."
        )
    else:
        actions.append(
            "Protocol in sync. Read .agent/project/status.md then proceed with user request."
        )

    # Pending refresh marker (set by co sync / co scaffold when content actually updated)
    project_refresh_pending: dict[str, Any] | None = None
    try:
        from cokodo_agent.pending_refresh import read_pending_refresh

        project_refresh_pending = read_pending_refresh(agent_dir)
    except Exception:
        pass
    if project_refresh_pending:
        actions.append(
            "Project template/sync updated — refresh project/ content (status.md Quick Reference, "
            "context if needed). Then run: co session-init --ack-refresh (or delete "
            ".agent/.cokodo_pending_refresh.json)."
        )

    prompt = _build_prompt(
        agent_dir=agent_dir,
        package_current=current,
        package_latest=package_latest,
        package_update_available=package_update_available,
        protocol_local=local_version,
        protocol_remote=remote_version,
        protocol_drift=protocol_drift,
        drift_file_count=drift_file_count,
        diff_error=diff_error,
        actions=actions,
        project_refresh_pending=project_refresh_pending,
    )

    return {
        "package_current": current,
        "package_latest": package_latest,
        "package_update_available": package_update_available,
        "protocol_local": local_version,
        "protocol_remote": remote_version,
        "protocol_drift": protocol_drift,
        "drift_file_count": drift_file_count,
        "diff_error": diff_error,
        "actions": actions,
        "prompt": prompt,
        "project_refresh_pending": project_refresh_pending,
    }


def _build_prompt(
    *,
    agent_dir: Path,
    package_current: str,
    package_latest: str | None,
    package_update_available: bool,
    protocol_local: str,
    protocol_remote: str,
    protocol_drift: bool,
    drift_file_count: int,
    diff_error: str | None,
    actions: list[str],
    project_refresh_pending: dict[str, Any] | None = None,
) -> str:
    lines = [
        "## Cokodo session-init (agent instructions)",
        "",
        "Execute the following before normal chat handling. Do not skip if drift or update is reported.",
        "",
        "### Package (cokodo-agent)",
        f"- Installed: **{package_current}**",
    ]
    if package_update_available and package_latest:
        lines.append(
            f"- **Update available**: PyPI has **{package_latest}**. "
            "Tell the user to close Cursor/IDE and run `pip install --upgrade cokodo-agent`, then reopen."
        )
    else:
        lines.append("- PyPI: no newer version reported (or check cached).")

    lines.extend(
        [
            "",
            "### Protocol (.agent)",
            f"- Local manifest version: **{protocol_local}**",
            f"- Remote/bundled target: **{protocol_remote}**",
        ]
    )
    if diff_error:
        lines.append(f"- **Diff error**: {diff_error}")
    elif protocol_drift:
        lines.append(
            f"- **Drift detected** ({drift_file_count} non-project file(s) changed or version mismatch). "
            "Sync before relying on protocol files."
        )
    else:
        lines.append("- **In sync** — no sync required for protocol core files.")

    lines.extend(["", "### Actions (in order)"])
    for i, a in enumerate(actions, 1):
        lines.append(f"{i}. {a}")

    lines.extend(
        [
            "",
            "### After actions",
            "- If you ran sync/adapt: re-load MCP context or re-read `.agent/project/status.md`.",
            "- Then follow `.agent/core/instructions.md` session init (Active change if any).",
        ]
    )

    if project_refresh_pending:
        src = project_refresh_pending.get("source", "unknown")
        at = project_refresh_pending.get("at", "")
        summary = project_refresh_pending.get("summary", "")
        lines.extend(
            [
                "",
                "### Project refresh required (pending marker)",
                "",
                f"A recent **{src}** updated protocol/template files ({at}).",
                f"**Summary:** {summary}",
                "",
                "**You must:**",
                "1. Re-read `.agent/project/status.md` and align **Quick Reference** (Protocol/CLI lines) with current manifest if needed.",
                "2. If scaffold created new `project/` files, fill in project-specific content (context, commands, etc.).",
                "3. After updating, clear the marker: MCP tool `ack_project_refresh`, or "
                "`co session-init --ack-refresh`, or delete `.agent/.cokodo_pending_refresh.json`.",
            ]
        )
        paths = project_refresh_pending.get("paths")
        if paths:
            lines.append("")
            lines.append("**Touched paths (sample):**")
            for p in paths[:15]:
                lines.append(f"- `{p}`")
            if len(paths) > 15:
                lines.append("- …")

    cross_hint = _cross_project_session_hint(agent_dir)
    if cross_hint:
        lines.extend(["", cross_hint])

    return "\n".join(lines)


def _cross_project_session_hint(agent_dir: Path) -> str:
    """Build a per-relation reminder about cross-project MCP tools."""
    lines: list[str] = []
    has_content = False

    try:
        from cokodo_agent.relations import (
            diff_collaboration,
            load_collaborations,
            load_references,
        )

        project_root = agent_dir.parent
        refs = load_references(agent_dir)
        collabs = load_collaborations(agent_dir)

        auto_refs = [r for r in refs if r.use_when in ("always", "session_start")]
        on_demand_refs = [r for r in refs if r.use_when == "on_demand"]

        if auto_refs:
            names = ", ".join(r.name for r in auto_refs)
            lines.append(
                f"- Auto-loaded refs ({names}) will be included by `get_project_context`."
            )
            has_content = True

        if on_demand_refs:
            for r in on_demand_refs:
                lines.append(
                    f"- ref:{r.name} (on_demand) → "
                    f"`get_related_context(relation=\"{r.name}\")` when needed."
                )
            has_content = True

        for c in collabs:
            try:
                diffs = diff_collaboration(c, project_root, agent_dir)
                drift = [f for f, s in diffs.items() if s in ("partner_only", "modified")]
                if drift:
                    lines.append(
                        f"- collab:{c.name} ({c.role}) — **{len(drift)} file(s) drifted**; "
                        "review with `get_collaboration_context` or run `co collab pull`."
                    )
                else:
                    lines.append(f"- collab:{c.name} ({c.role}) — in sync, auto-loaded.")
            except Exception:
                lines.append(f"- collab:{c.name} ({c.role}) — partner inaccessible.")
            has_content = True
    except Exception:
        pass

    try:
        from cokodo_agent.registry import list_projects

        projects = list_projects(include_invalid=False)
        if projects and len(projects) > 1:
            lines.append(
                f"- {len(projects)} projects in global registry → "
                "`list_global_projects` / `get_global_project_context`."
            )
            has_content = True
    except Exception:
        pass

    if not has_content:
        return ""

    return (
        "### Cross-project tools reminder\n\n"
        "When a task involves another project, prefer MCP tools over file-system search:\n"
        + "\n".join(lines)
    )


def format_json(result: dict[str, Any]) -> str:
    """Serialize result for stdout (UTF-8)."""
    return json.dumps(result, ensure_ascii=False, indent=2)
