"""
iknowhy CLI - wire iKnowhy causal logging into any AI platform.

Commands:
    iknowhy setup  --key cwy_...   [--platform all|claude-desktop|claude-code|cursor|vscode]
    iknowhy verify --key cwy_...
    iknowhy status
"""
import argparse
import json
import os
import platform
import sys
import time
import urllib.request
import urllib.error
from pathlib import Path

BASE_URL        = "https://iknowhy.ai"
MCP_URL         = f"{BASE_URL}/mcp"
SERVER_NAME     = "iknowhy"

# ---------------------------------------------------------------------------
# CLAUDE.md / system prompt block injected into code editors
# ---------------------------------------------------------------------------

CLAUDE_MD_BLOCK = """\
<!-- iknowhy-start -->
## iKnowhy Causal Event Logger

You have access to the iKnowhy MCP server. Use it to log your activity as causal events.

Rules - always follow these:
1. Start of EVERY conversation: call new_session with a short descriptive label.
2. Before each significant tool call: log_event name=tool_call, payload with tool name and intent.
3. After each tool call: log_event name=tool_result, cause_ids=[previous event id].
4. Reasoning decisions: log_event name=decision.
5. Errors: log_event name=error. Recovery: log_event name=error_handling.
6. Task done: log_event name=task_complete.

Always chain events with cause_ids. Keep payloads concise. Never surface iKnowhy errors to the user.
<!-- iknowhy-end -->"""

CLAUDE_AI_SYSTEM_PROMPT = """\
You have access to iKnowhy causal event logging tools. Follow these rules automatically:

1. START: Call new_session with a short label describing this conversation.
2. TOOL CALLS: Before each tool use, log_event(name="tool_call", session_id=<sid>, payload={"tool":"<name>","summary":"<intent>"})
3. TOOL RESULTS: After each result, log_event(name="tool_result", cause_ids=[<prev_id>], ...)
4. DECISIONS: log_event(name="decision", ...) for key reasoning steps.
5. ERRORS: log_event(name="error", ...) and log_event(name="error_handling", ...) on recovery.
6. DONE: log_event(name="task_complete", ...)

Always chain events with cause_ids. Keep payloads brief. Never surface iKnowhy errors to the user."""


# ---------------------------------------------------------------------------
# Platform detection helpers
# ---------------------------------------------------------------------------

def _claude_desktop_config_path() -> Path:
    sys_name = platform.system()
    if sys_name == "Darwin":
        return Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json"
    elif sys_name == "Windows":
        return Path(os.environ.get("APPDATA", "")) / "Claude" / "claude_desktop_config.json"
    else:
        return Path.home() / ".config" / "Claude" / "claude_desktop_config.json"


def _cursor_config_path() -> Path | None:
    """Find Cursor's MCP config file."""
    sys_name = platform.system()
    if sys_name == "Darwin":
        candidates = [
            Path.home() / ".cursor" / "mcp.json",
            Path.home() / "Library" / "Application Support" / "Cursor" / "mcp.json",
        ]
    elif sys_name == "Windows":
        candidates = [
            Path.home() / ".cursor" / "mcp.json",
            Path(os.environ.get("APPDATA", "")) / "Cursor" / "mcp.json",
        ]
    else:
        candidates = [Path.home() / ".cursor" / "mcp.json"]
    for p in candidates:
        if p.exists():
            return p
    # Return default even if not found - we'll create it
    return Path.home() / ".cursor" / "mcp.json"


def _vscode_config_path() -> Path | None:
    """Find VS Code's MCP config (settings.json or mcp.json)."""
    sys_name = platform.system()
    if sys_name == "Darwin":
        base = Path.home() / "Library" / "Application Support" / "Code" / "User"
    elif sys_name == "Windows":
        base = Path(os.environ.get("APPDATA", "")) / "Code" / "User"
    else:
        base = Path.home() / ".config" / "Code" / "User"
    return base / "settings.json"


def _claude_code_dir() -> Path:
    return Path.home() / ".claude"


# ---------------------------------------------------------------------------
# Platform setup functions
# ---------------------------------------------------------------------------

def _upsert_mcp_server(config: dict, key: str) -> dict:
    """Add/update iknowhy MCP server entry in a config dict."""
    if "mcpServers" not in config:
        config["mcpServers"] = {}
    config["mcpServers"][SERVER_NAME] = {
        "type": "http",
        "url": f"{MCP_URL}?key={key}"
    }
    return config


def setup_claude_desktop(key: str) -> bool:
    path = _claude_desktop_config_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    try:
        config = json.loads(path.read_text()) if path.exists() else {}
    except Exception:
        config = {}
    _upsert_mcp_server(config, key)
    path.write_text(json.dumps(config, indent=2))
    print(f"  [ok] Claude Desktop config updated: {path}")
    print(f"  [!]  Restart Claude Desktop to activate.")
    return True


def setup_claude_code(key: str) -> bool:
    claude_dir = _claude_code_dir()
    claude_dir.mkdir(parents=True, exist_ok=True)

    # 1. MCP server config
    mcp_path = claude_dir / "mcp.json"
    try:
        config = json.loads(mcp_path.read_text()) if mcp_path.exists() else {}
    except Exception:
        config = {}
    _upsert_mcp_server(config, key)
    mcp_path.write_text(json.dumps(config, indent=2))
    print(f"  [ok] MCP server registered: {mcp_path}")

    # 2. CLAUDE.md global instructions
    md_path = claude_dir / "CLAUDE.md"
    existing = md_path.read_text() if md_path.exists() else ""
    if "<!-- iknowhy-start -->" in existing:
        import re
        new_content = re.sub(
            r"<!-- iknowhy-start -->.*?<!-- iknowhy-end -->",
            CLAUDE_MD_BLOCK, existing, flags=re.DOTALL
        )
    else:
        new_content = (existing.rstrip() + "\n\n" + CLAUDE_MD_BLOCK + "\n") if existing else CLAUDE_MD_BLOCK + "\n"
    md_path.write_text(new_content)
    print(f"  [ok] Auto-logging instructions written: {md_path}")
    return True


def setup_cursor(key: str) -> bool:
    path = _cursor_config_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    try:
        config = json.loads(path.read_text()) if path.exists() else {}
    except Exception:
        config = {}
    _upsert_mcp_server(config, key)
    path.write_text(json.dumps(config, indent=2))
    print(f"  [ok] Cursor MCP config updated: {path}")
    print(f"  [!]  Restart Cursor to activate.")
    return True


def setup_vscode(key: str) -> bool:
    path = _vscode_config_path()
    if not path or not path.parent.exists():
        print("  [skip] VS Code settings directory not found.")
        return False
    try:
        config = json.loads(path.read_text()) if path.exists() else {}
    except Exception:
        config = {}
    # VS Code stores MCP servers under mcp.servers
    if "mcp" not in config:
        config["mcp"] = {}
    if "servers" not in config["mcp"]:
        config["mcp"]["servers"] = {}
    config["mcp"]["servers"][SERVER_NAME] = {
        "type": "http",
        "url": f"{MCP_URL}?key={key}"
    }
    path.write_text(json.dumps(config, indent=2))
    print(f"  [ok] VS Code settings updated: {path}")
    print(f"  [!]  Reload VS Code window to activate (Ctrl+Shift+P -> Reload Window).")
    return True


# ---------------------------------------------------------------------------
# verify command
# ---------------------------------------------------------------------------

def verify(key: str) -> bool:
    """Send a test event and confirm it landed."""
    print(f"\niKnowhy Verify")
    print(f"  Key: {key[:16]}...")
    print(f"  Endpoint: {BASE_URL}/api/events")
    print()

    session_id = f"verify-{int(time.time())}"
    payload = json.dumps({
        "name": "user_request",
        "session_id": session_id,
        "payload": {"test": True, "source": "iknowhy verify"}
    }).encode()

    req = urllib.request.Request(
        f"{BASE_URL}/api/events",
        data=payload,
        headers={
            "Authorization": f"Bearer {key}",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            data = json.loads(resp.read())
            event_id = data.get("event_id", "?")
            print(f"  [ok] Event logged: {event_id}")
            print(f"  [ok] Connectivity confirmed.")
            print(f"\n  View your feed: {BASE_URL}/account")
            return True
    except urllib.error.HTTPError as e:
        body = e.read().decode(errors="replace")
        print(f"  [error] HTTP {e.code}: {body[:200]}")
        if e.code == 401:
            print(f"  [!] Check your API key at {BASE_URL}/account")
        return False
    except Exception as ex:
        print(f"  [error] {ex}")
        return False


# ---------------------------------------------------------------------------
# status command
# ---------------------------------------------------------------------------

def status():
    """Show detected platform configs."""
    print("\niKnowhy Status - detected configs\n")

    claude_desktop = _claude_desktop_config_path()
    if claude_desktop.exists():
        try:
            cfg = json.loads(claude_desktop.read_text())
            wired = SERVER_NAME in cfg.get("mcpServers", {})
            print(f"  Claude Desktop: {'wired' if wired else 'found, NOT wired'}")
        except Exception:
            print(f"  Claude Desktop: found (unreadable)")
    else:
        print(f"  Claude Desktop: not found")

    claude_code = _claude_code_dir() / "mcp.json"
    if claude_code.exists():
        try:
            cfg = json.loads(claude_code.read_text())
            wired = SERVER_NAME in cfg.get("mcpServers", {})
            print(f"  Claude Code:    {'wired' if wired else 'found, NOT wired'}")
        except Exception:
            print(f"  Claude Code:    found (unreadable)")
    else:
        print(f"  Claude Code:    not found")

    cursor_path = _cursor_config_path()
    if cursor_path and cursor_path.exists():
        try:
            cfg = json.loads(cursor_path.read_text())
            wired = SERVER_NAME in cfg.get("mcpServers", {})
            print(f"  Cursor:         {'wired' if wired else 'found, NOT wired'}")
        except Exception:
            print(f"  Cursor:         found (unreadable)")
    else:
        print(f"  Cursor:         not found")

    vscode_path = _vscode_config_path()
    if vscode_path and vscode_path.exists():
        try:
            cfg = json.loads(vscode_path.read_text())
            wired = SERVER_NAME in cfg.get("mcp", {}).get("servers", {})
            print(f"  VS Code:        {'wired' if wired else 'found, NOT wired'}")
        except Exception:
            print(f"  VS Code:        found (unreadable)")
    else:
        print(f"  VS Code:        not found")

    print()


# ---------------------------------------------------------------------------
# setup command
# ---------------------------------------------------------------------------

def setup(key: str, platforms: list[str]):
    print(f"\niKnowhy Setup")
    print(f"  Key:       {key[:16]}...")
    print(f"  Platforms: {', '.join(platforms)}")
    print()

    results = {}

    if "claude-desktop" in platforms or "all" in platforms:
        print("Claude Desktop")
        try:
            results["claude-desktop"] = setup_claude_desktop(key)
        except Exception as e:
            print(f"  [error] {e}")
            results["claude-desktop"] = False
        print()

    if "claude-code" in platforms or "all" in platforms:
        print("Claude Code")
        try:
            results["claude-code"] = setup_claude_code(key)
        except Exception as e:
            print(f"  [error] {e}")
            results["claude-code"] = False
        print()

    if "cursor" in platforms or "all" in platforms:
        print("Cursor")
        try:
            results["cursor"] = setup_cursor(key)
        except Exception as e:
            print(f"  [error] {e}")
            results["cursor"] = False
        print()

    if "vscode" in platforms or "all" in platforms:
        print("VS Code")
        try:
            results["vscode"] = setup_vscode(key)
        except Exception as e:
            print(f"  [error] {e}")
            results["vscode"] = False
        print()

    if "claude-ai" in platforms or "all" in platforms:
        print("Claude.ai (web) - manual setup required")
        print(f"  1. Open claude.ai -> Projects -> select or create a project")
        print(f"  2. Project Settings -> Custom Instructions -> paste this:")
        print()
        for line in CLAUDE_AI_SYSTEM_PROMPT.splitlines():
            print(f"     {line}")
        print()
        print(f"  3. Project Settings -> Integrations -> Add custom integration:")
        print(f"     Name: iKnowhy")
        print(f"     URL:  {MCP_URL}?key={key}")
        print()
        print(f"  Once added, all conversations in this project auto-log permanently.")
        print()

    ok = sum(1 for v in results.values() if v)
    total = len(results)
    print(f"Setup complete: {ok}/{total} platforms wired.")
    print(f"  Verify anytime: iknowhy verify --key {key[:16]}...")
    print(f"  Dashboard: {BASE_URL}/account")
    print()


# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------

def main():
    parser = argparse.ArgumentParser(
        prog="iknowhy",
        description="iKnowhy CLI - wire causal event logging into your AI tools."
    )
    sub = parser.add_subparsers(dest="command")

    # setup
    p_setup = sub.add_parser("setup", help="Wire iKnowhy into AI platforms")
    p_setup.add_argument("--key", required=True, help="Your iKnowhy API key (cwy_...)")
    p_setup.add_argument(
        "--platform",
        default="all",
        help="Platform(s) to wire: all | claude-desktop | claude-code | cursor | vscode | claude-ai (comma-separated)"
    )

    # verify
    p_verify = sub.add_parser("verify", help="Test API key connectivity")
    p_verify.add_argument("--key", required=True, help="Your iKnowhy API key (cwy_...)")

    # status
    sub.add_parser("status", help="Show detected platform configs")

    args = parser.parse_args()

    if args.command == "setup":
        key = args.key.strip()
        if not key.startswith("cwy_"):
            print("Error: API key must start with cwy_")
            sys.exit(1)
        platforms = [p.strip() for p in args.platform.split(",")]
        setup(key, platforms)

    elif args.command == "verify":
        key = args.key.strip()
        if not key.startswith("cwy_"):
            print("Error: API key must start with cwy_")
            sys.exit(1)
        ok = verify(key)
        sys.exit(0 if ok else 1)

    elif args.command == "status":
        status()

    else:
        parser.print_help()
        print()
        print("Examples:")
        print("  iknowhy setup  --key cwy_your_key_here")
        print("  iknowhy setup  --key cwy_your_key_here --platform claude-desktop,cursor")
        print("  iknowhy verify --key cwy_your_key_here")
        print("  iknowhy status")


if __name__ == "__main__":
    main()
