#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
PULSE API — Integration router. Write endpoints for external plugins (OpenClaw, etc).

Separated from pulse_api_brain.py (read endpoints) for SoC.
"""
from __future__ import annotations

import re
import sys
import unicodedata
from pathlib import Path

from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel, Field, field_validator

SCRIPT_DIR = Path(__file__).resolve().parent
ROOT_DIR = SCRIPT_DIR.parent.parent

if str(ROOT_DIR) not in sys.path:
    sys.path.insert(0, str(ROOT_DIR))

router = APIRouter(prefix="/v1/brain", tags=["integrations"])


# --- Input Sanitization ---
_QUERY_UNSAFE = re.compile(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]')


def _sanitize_query(q: str) -> str:
    """Remove control chars, normalize unicode, collapse whitespace."""
    q = _QUERY_UNSAFE.sub('', q)
    q = unicodedata.normalize('NFC', q)
    q = ' '.join(q.split())
    return q.strip()


# --- Pydantic Models (underscore-prefixed: internal to this module) ---

class _CaptureReq(BaseModel):
    user_prompt: str = Field(..., min_length=3, max_length=10_000, examples=["Fix the auth bug"])
    agent_response: str = Field("", max_length=50_000, examples=["Here's the fix..."])
    session_id: str = Field("", max_length=128, examples=["session_001"])


class _SaveReq(BaseModel):
    agent_name: str = Field(..., max_length=64, pattern=r'^[a-zA-Z0-9_-]+$', examples=["openclaw"])
    learnings: list[str] = Field(default_factory=list)
    failures: list[str] = Field(default_factory=list)
    domain: str = Field("general", max_length=64, examples=["security"])
    outcome: str = Field("", max_length=64, examples=["success"])
    task_id: str = Field("", max_length=128)

    @field_validator('learnings', 'failures')
    @classmethod
    def validate_items(cls, v: list[str]) -> list[str]:
        if len(v) > 50:
            raise ValueError('Max 50 items per save request.')
        for item in v:
            if len(item) > 2_000:
                raise ValueError('Each item must be 2000 chars or less.')
        return v


# --- Endpoints ---

@router.post("/capture")
async def capture_interaction(req: _CaptureReq, request: Request):
    """Capture a conversation turn into the PULSE brain pipeline.

    Runs wants_parser + knowledge_extractor + routes to Hippocampus.
    Used by external plugins (OpenClaw, etc.) to feed the brain.
    """
    try:
        from pulse.core.pii_filter import redact_pii
        from pulse.brain.capture.auto_capture import capture

        # Server-side PII redaction (defense-in-depth)
        clean_prompt = redact_pii(req.user_prompt)
        clean_response = redact_pii(req.agent_response)

        result = capture(
            user_prompt=clean_prompt,
            agent_response=clean_response,
            session_id=req.session_id or "external",
        )
        return {"status": "captured", **result}
    except Exception as e:
        print(f"[API] Capture error: {e}")
        raise HTTPException(status_code=500, detail="Capture failed. Check server logs.")


@router.post("/save")
async def save_knowledge(req: _SaveReq, request: Request):
    """Save learnings and failures to the PULSE brain.

    Wraps pulse_save.save() for external plugins.
    """
    try:
        from pulse.brain.pulse_save import save
        result = save(
            agent_name=req.agent_name,
            task_id=req.task_id,
            learnings=req.learnings,
            failures=req.failures,
            domain=req.domain,
            outcome=req.outcome,
        )
        return {"status": "saved", **result}
    except Exception as e:
        print(f"[API] Save error: {e}")
        raise HTTPException(status_code=500, detail="Save failed. Check server logs.")


@router.get("/briefing")
async def get_briefing(q: str = ""):
    """Get a formatted brain briefing for a query.

    Returns the same [BRAIN] formatted output that Claude/Gemini hooks receive.
    Includes predictions, anti-patterns, proven approaches, failures, etc.
    """
    q = _sanitize_query(q)
    if not q or len(q) < 3:
        raise HTTPException(status_code=400, detail="Query must be at least 3 characters.")
    if len(q) > 1_000:
        raise HTTPException(status_code=400, detail="Query too long (max 1000 chars).")
    try:
        from pulse.brain.brain_query import run_query
        briefing = run_query(q)
        return {"query": q, "briefing": briefing or "", "empty": not briefing}
    except Exception as e:
        print(f"[API] Briefing error: {e}")
        raise HTTPException(status_code=500, detail="Briefing unavailable. Check server logs.")
