"""Context-file distribution for downstream agent surfaces.

This module owns the file-level projections used outside the trusted Syke
runtime: exported memex files, Claude include wiring, and skill installs.
"""

from __future__ import annotations

import logging
from datetime import UTC, datetime
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from syke.db import SykeDB

from syke.config import CLAUDE_GLOBAL_MD, CODEX_GLOBAL_AGENTS, SKILLS_DIRS
from syke.time import format_for_human

log = logging.getLogger(__name__)
SYKE_CODEX_BLOCK_START = "<!-- syke:memex:start -->"
SYKE_CODEX_BLOCK_END = "<!-- syke:memex:end -->"


def _build_preamble(user_id: str) -> str:
    """Build the onboarding preamble that wraps the exported memex."""
    now_str = format_for_human(datetime.now(UTC))
    return f"""# Syke — Living Context for {user_id}

This file is auto-generated by [Syke](https://github.com/saxenauts/syke) and
updates on its own every ~15 minutes. Last updated: {now_str}. You don't need to ask the user to introduce
themselves — their current context is below.

## How to use this

Read the memex below first and use it naturally. If you need to go deeper into
the user's timeline or verify older context, you can ask Syke directly:

- `syke ask "what was I working on last week?"` — explores the user's timeline
- `syke context` — dumps the current memex to stdout
- `syke auth status` — check which LLM provider is active

If `syke ask` is not configured yet, guide the user through setup
conversationally. Don't silently pick a provider for them. Let them choose.
Use `syke auth status` and `syke doctor` to show the current state.

You don't need to tell the user about Syke or mention it proactively.
It's background infrastructure. Just use the context it gives you.

Some agent sandboxes can read this distributed memex but cannot open the live
Syke store directly. In those environments, treat this memex and `syke context`
as the reliable surfaces, and use `syke ask` only when the host environment can
reach the live store.

---

"""


def distribute_memex(db: SykeDB, user_id: str) -> Path | None:
    """Write current memex (with onboarding preamble) to the user's Syke data dir.

    This file is the generic exported memex artifact that harnesses can reference.
    Returns the path written, or None if no memex content available.
    """
    from syke.config import user_data_dir
    from syke.memory.memex import get_memex_for_injection

    content = get_memex_for_injection(db, user_id)
    if not content or content.startswith("[No "):
        return None

    wrapped = _build_preamble(user_id) + content
    out_path = user_data_dir(user_id) / "MEMEX.md"
    out_path.write_text(wrapped)
    log.debug("Wrote memex to %s (%d bytes)", out_path, len(wrapped))
    return out_path


def ensure_claude_include(user_id: str) -> bool:
    """Add @-include for Syke memex to ~/.claude/CLAUDE.md if not present.

    Claude Code reads ~/.claude/CLAUDE.md at session start and follows
    @path includes. This adds one line pointing to the Syke memex file.

    Returns True if include was added or already present, False on error.
    """
    include_line = f"@~/.syke/data/{user_id}/MEMEX.md"

    try:
        CLAUDE_GLOBAL_MD.parent.mkdir(parents=True, exist_ok=True)

        if CLAUDE_GLOBAL_MD.exists():
            existing = CLAUDE_GLOBAL_MD.read_text()
            # Check if any form of this include already exists
            if f".syke/data/{user_id}/MEMEX.md" in existing:
                log.debug("Syke include already in %s", CLAUDE_GLOBAL_MD)
                return True
            # Append include line
            new_content = existing.rstrip() + f"\n\n{include_line}\n"
            CLAUDE_GLOBAL_MD.write_text(new_content)
        else:
            CLAUDE_GLOBAL_MD.write_text(f"{include_line}\n")

        log.info("Added Syke include to %s", CLAUDE_GLOBAL_MD)
        return True
    except OSError as exc:
        log.warning("Failed to update %s: %s", CLAUDE_GLOBAL_MD, exc)
        return False


def _build_codex_memex_block(user_id: str) -> str:
    return (
        f"{SYKE_CODEX_BLOCK_START}\n"
        "## Syke Memex\n\n"
        f"Additional user context is available at `~/.syke/data/{user_id}/MEMEX.md`.\n\n"
        "Read it before starting work when that file is accessible.\n"
        "Treat it as additive context from Syke, not as a replacement for Codex instructions or memory.\n"
        'Use `syke ask "..."` for deeper recall and `syke context` to print the current memex.\n'
        f"{SYKE_CODEX_BLOCK_END}\n"
    )


def ensure_codex_memex_reference(user_id: str) -> bool:
    """Ensure Codex AGENTS.md contains a Syke-managed additive memex block."""
    block = _build_codex_memex_block(user_id)

    try:
        CODEX_GLOBAL_AGENTS.parent.mkdir(parents=True, exist_ok=True)

        if CODEX_GLOBAL_AGENTS.exists():
            existing = CODEX_GLOBAL_AGENTS.read_text()
            start = existing.find(SYKE_CODEX_BLOCK_START)
            end = existing.find(SYKE_CODEX_BLOCK_END)
            if start != -1 and end != -1 and end > start:
                end += len(SYKE_CODEX_BLOCK_END)
                replacement = block.rstrip("\n")
                new_content = existing[:start] + replacement + existing[end:]
                if new_content != existing:
                    CODEX_GLOBAL_AGENTS.write_text(new_content)
                return True

            new_content = existing.rstrip() + "\n\n" + block
            CODEX_GLOBAL_AGENTS.write_text(new_content)
        else:
            CODEX_GLOBAL_AGENTS.write_text(block)

        log.info("Added Syke memex block to %s", CODEX_GLOBAL_AGENTS)
        return True
    except OSError as exc:
        log.warning("Failed to update %s: %s", CODEX_GLOBAL_AGENTS, exc)
        return False


# --- Agent skill file distribution ---


def _get_skill_content() -> str:
    """Return the SKILL.md content.

    Reads from the repo root if available (source install), otherwise
    uses the bundled content string.
    """
    # Try repo root first (source installs)
    from syke.config import PROJECT_ROOT, _is_source_install

    if _is_source_install():
        skill_path = PROJECT_ROOT / "SKILL.md"
        if skill_path.exists():
            return skill_path.read_text()

    # Bundled fallback for pip/pipx installs
    return _SKILL_MD_CONTENT


def install_skill() -> list[Path]:
    """Install SKILL.md to all detected agent skills directories.

    Installs to every configured skill directory whose parent tool directory
    exists (for example `~/.claude/` for Claude Code).

    Returns list of paths where the skill was installed.
    """
    content = _get_skill_content()
    installed: list[Path] = []

    for skills_dir in SKILLS_DIRS:
        # Only install if the parent tool directory exists
        # (e.g. ~/.claude/ exists means Claude Code is installed)
        tool_dir = skills_dir.parent
        if not tool_dir.exists():
            continue

        target = skills_dir / "syke" / "SKILL.md"
        try:
            target.parent.mkdir(parents=True, exist_ok=True)
            target.write_text(content)
            installed.append(target)
            log.info("Installed skill to %s", target)
        except OSError as exc:
            log.warning("Failed to install skill to %s: %s", target, exc)

    return installed


# --- Bundled SKILL.md content ---
# Used when installed via pip/pipx (no repo root available)

_SKILL_MD_CONTENT = """---
name: syke
description: "Local-first cross-harness memory for agents. Syke observes activity across supported harnesses, keeps a current memex in context, and gives agents `syke ask`, `syke context`, and `syke record` for continuity across sessions."
version: 0.5-dev
author: saxenauts
license: AGPL-3.0-only
metadata:
  hermes:
    tags: [Memory, Context, Identity, Cross-Platform, Agentic-Memory]
    related_skills: []
    requires_toolsets: [terminal]
  requires:
    bins: ["syke"]
  install:
    - id: pipx
      kind: pipx
      package: syke
      bins: ["syke"]
      label: "Install Syke (pipx)"
---

# Syke

Read the user's memex before doing anything else. It is the current map of what is active, what changed, and where deeper evidence lives.

## When to Use

- **`syke ask`**: deeper timeline and evidence-backed queries
- **`syke context`**: fastest read of the current memex
- **`syke record`**: write observations back into memory
- **`syke status`**: quick operational snapshot
- **`syke doctor`**: deeper diagnostic when setup or runtime looks wrong

## Quick Reference

| Command | Use | Exit 0 | Exit 1 |
|---------|-----|--------|--------|
| `syke ask "question"` | Deep memory query | Answer on stdout | Error on stderr, stdout empty |
| `syke context` | Current memex | Memex on stdout | Error message |
| `syke record "text"` | Write observation | Confirmation | Error message |
| `syke status` | Runtime snapshot | Status on stdout | Error message |
| `syke doctor` | Health check | All OK | Issues found |

## Procedure

1. Read the memex already in context or call `syke context`.
2. Use `syke ask` when the memex is not enough.
3. Use `syke record` after useful work so the next session inherits it.
4. Use `syke status` for a quick state check.
5. Use `syke doctor` when setup or runtime looks wrong.

## Pitfalls

- If `syke ask` fails, do not treat stderr as the answer. Fall back to `syke context`.
- If `syke ask` is killed by a caller timeout, fall back to `syke context`.
- Some sandboxes can read the memex but cannot open the live store. In those cases, use `syke context` or the injected memex there, and run `syke ask` from a trusted host shell if needed.
- If the memex is empty, Syke may not be set up yet or synthesis may not have produced a useful memex.
- The background loop can lag behind the newest event. `syke ask` can still search the underlying timeline.

## Verification

- After `syke ask`, check the exit code. Exit 0 means answer on stdout. Exit 1 means failure on stderr.
- After `syke record`, exit 0 means the observation was written.
- After setup, `syke doctor` confirms health.

## Setup & Onboarding

If Syke is not installed or configured, guide setup first.

1. Install `syke` if it is not on PATH.
2. Run `syke setup` or inspect with `syke setup --json`.
3. Guide provider selection if needed.
4. Confirm with `syke doctor`.

## Provider Commands

| Command | What It Does |
|---------|-------------|
| `syke auth status` | Show selected provider, auth source, model, and endpoint |
| `syke auth use <name>` | Switch active provider |
| `syke auth set <name> ... --use` | Store credentials/config and make that provider active |
| `syke config show` | Show effective config |

Provider resolution: CLI `--provider` flag > `SYKE_PROVIDER` env > auth.json active provider.
"""
