"""
SealVera autoload — zero-touch Python intercept.

Monkeypatches OpenAI and Anthropic clients at import time so every LLM call
is logged to SealVera without any code changes.

Activation (two options):

  Option A — environment variable (recommended):
    export SEALVERA_API_KEY=sv_...
    export SEALVERA_AUTOLOAD=1
    python your_agent.py

  Option B — sitecustomize / .pth (process-wide, affects all Python):
    # Find your site-packages:
    python -c "import site; print(site.getsitepackages()[0])"
    # Add a .pth file there:
    echo "import sealvera.autoload" > <site-packages>/sealvera-autoload.pth

  Option C — explicit import at top of entry point:
    import sealvera.autoload  # before importing openai / anthropic

Required env vars:
  SEALVERA_API_KEY    — your API key (sv_...)

Optional env vars:
  SEALVERA_AGENT      — agent name on dashboard (default: python-agent)
  SEALVERA_ENDPOINT   — server URL (default: https://app.sealvera.com)
  SEALVERA_SILENT     — set to '1' to suppress startup message
  SEALVERA_AUTOLOAD   — set to '1' to activate (required for Option A)
"""

import os
import sys
import time
import importlib
import importlib.abc
import importlib.machinery
from typing import Optional

# ── Config ────────────────────────────────────────────────────────────────────
_API_KEY   = os.environ.get("SEALVERA_API_KEY", "")
_AGENT     = os.environ.get("SEALVERA_AGENT", "python-agent")
_ENDPOINT  = os.environ.get("SEALVERA_ENDPOINT", "https://app.sealvera.com").rstrip("/")
_SILENT    = os.environ.get("SEALVERA_SILENT", "") == "1"
_AUTOLOAD  = os.environ.get("SEALVERA_AUTOLOAD", "") == "1"

# Try loading config file (.sealvera.json in cwd)
def _load_file_config():
    try:
        import json, pathlib
        cfg_path = pathlib.Path.cwd() / ".sealvera.json"
        if cfg_path.exists():
            return json.loads(cfg_path.read_text())
    except Exception:
        pass
    return {}

_file_cfg   = _load_file_config()
_API_KEY    = _API_KEY    or _file_cfg.get("apiKey", "")
_AGENT      = _AGENT      or _file_cfg.get("agent", "python-agent")
_ENDPOINT   = _ENDPOINT   or _file_cfg.get("endpoint", "https://app.sealvera.com")

# Bail if not activated or no key
if not _API_KEY:
    if not _SILENT:
        sys.stderr.write("[SealVera] autoload: SEALVERA_API_KEY not set — skipping\n")
elif not _AUTOLOAD and __name__ != "__main__":
    # Only auto-activate if SEALVERA_AUTOLOAD=1 or explicitly imported
    # (explicit import = __name__ check won't stop it)
    pass

_patched = set()


def _get_client():
    """Lazy-load the SealVera client (avoids circular import)."""
    from sealvera.client import _default_client
    if not _default_client._configured:
        _default_client.configure(
            api_key=_API_KEY,
            agent=_AGENT,
            endpoint=_ENDPOINT,
        )
    return _default_client


def _log(action: str, decision: str, input_data: dict, output_data: dict,
         reasoning: list, model: str = None):
    """Fire-and-forget log — never blocks, never raises."""
    if not _API_KEY:
        return
    try:
        client = _get_client()
        client.log(
            action=action,
            decision=decision,
            input=input_data,
            output=output_data,
            reasoning=reasoning,
            model=model,
            blocking=False,
        )
    except Exception:
        pass


# ── OpenAI patcher ────────────────────────────────────────────────────────────

def _patch_openai(openai_module):
    """Patch openai.OpenAI / openai.AsyncOpenAI to log all completions."""
    if "openai" in _patched:
        return
    _patched.add("openai")

    try:
        original_chat_create       = None
        original_async_chat_create = None

        # Sync client
        try:
            from openai.resources.chat.completions import Completions
            original_chat_create = Completions.create

            def _patched_create(self, *args, **kwargs):
                start   = time.time()
                model   = kwargs.get("model", "unknown")
                messages = kwargs.get("messages", [])
                input_preview = str(messages[-1].get("content", ""))[:300] if messages else ""
                try:
                    response = original_chat_create(self, *args, **kwargs)
                    elapsed  = round(time.time() - start, 3)
                    output_text = ""
                    try:
                        output_text = response.choices[0].message.content or ""
                    except Exception:
                        pass
                    usage = {}
                    try:
                        u = response.usage
                        usage = {"prompt_tokens": u.prompt_tokens,
                                 "completion_tokens": u.completion_tokens,
                                 "total_tokens": u.total_tokens}
                    except Exception:
                        pass
                    _log(
                        action="openai_chat_completion",
                        decision="COMPLETED",
                        input_data={"model": model, "prompt": input_preview, "n_messages": len(messages)},
                        output_data={"text": output_text[:500], "latency_s": elapsed, **usage},
                        reasoning=[{"factor": "latency_s", "value": str(elapsed),
                                    "signal": "safe", "explanation": "OpenAI call completed"}],
                        model=model,
                    )
                    return response
                except Exception as e:
                    elapsed = round(time.time() - start, 3)
                    _log(
                        action="openai_chat_completion",
                        decision="ERROR",
                        input_data={"model": model, "prompt": input_preview},
                        output_data={"error": str(e)[:300], "latency_s": elapsed},
                        reasoning=[{"factor": "exception", "value": type(e).__name__,
                                    "signal": "block", "explanation": str(e)[:200]}],
                    )
                    raise

            Completions.create = _patched_create
        except Exception:
            pass

        # Async client
        try:
            from openai.resources.chat.completions import AsyncCompletions
            original_async_chat_create = AsyncCompletions.create

            async def _patched_async_create(self, *args, **kwargs):
                start    = time.time()
                model    = kwargs.get("model", "unknown")
                messages = kwargs.get("messages", [])
                input_preview = str(messages[-1].get("content", ""))[:300] if messages else ""
                try:
                    response = await original_async_chat_create(self, *args, **kwargs)
                    elapsed  = round(time.time() - start, 3)
                    output_text = ""
                    try:
                        output_text = response.choices[0].message.content or ""
                    except Exception:
                        pass
                    usage = {}
                    try:
                        u = response.usage
                        usage = {"prompt_tokens": u.prompt_tokens,
                                 "completion_tokens": u.completion_tokens,
                                 "total_tokens": u.total_tokens}
                    except Exception:
                        pass
                    _log(
                        action="openai_chat_completion",
                        decision="COMPLETED",
                        input_data={"model": model, "prompt": input_preview, "n_messages": len(messages)},
                        output_data={"text": output_text[:500], "latency_s": elapsed, **usage},
                        reasoning=[{"factor": "latency_s", "value": str(elapsed),
                                    "signal": "safe", "explanation": "OpenAI async call completed"}],
                        model=model,
                    )
                    return response
                except Exception as e:
                    elapsed = round(time.time() - start, 3)
                    _log(
                        action="openai_chat_completion",
                        decision="ERROR",
                        input_data={"model": model, "prompt": input_preview},
                        output_data={"error": str(e)[:300], "latency_s": elapsed},
                        reasoning=[{"factor": "exception", "value": type(e).__name__,
                                    "signal": "block", "explanation": str(e)[:200]}],
                    )
                    raise

            AsyncCompletions.create = _patched_async_create
        except Exception:
            pass

        if not _SILENT:
            sys.stderr.write(f'[SealVera] autoload: patched openai — agent="{_AGENT}"\n')

    except Exception as e:
        if not _SILENT:
            sys.stderr.write(f"[SealVera] autoload: openai patch failed — {e}\n")


# ── Anthropic patcher ─────────────────────────────────────────────────────────

def _patch_anthropic(anthropic_module):
    """Patch anthropic.Anthropic to log all message creations."""
    if "anthropic" in _patched:
        return
    _patched.add("anthropic")

    try:
        from anthropic.resources.messages import Messages
        original_create = Messages.create

        def _patched_anthropic_create(self, *args, **kwargs):
            start  = time.time()
            model  = kwargs.get("model", "unknown")
            msgs   = kwargs.get("messages", [])
            input_preview = str(msgs[-1].get("content", ""))[:300] if msgs else ""
            try:
                response = original_create(self, *args, **kwargs)
                elapsed  = round(time.time() - start, 3)
                output_text = ""
                try:
                    output_text = response.content[0].text[:500]
                except Exception:
                    pass
                usage = {}
                try:
                    u = response.usage
                    usage = {"input_tokens": u.input_tokens, "output_tokens": u.output_tokens}
                except Exception:
                    pass
                _log(
                    action="anthropic_message",
                    decision="COMPLETED",
                    input_data={"model": model, "prompt": input_preview, "n_messages": len(msgs)},
                    output_data={"text": output_text, "latency_s": elapsed, **usage},
                    reasoning=[{"factor": "latency_s", "value": str(elapsed),
                                "signal": "safe", "explanation": "Anthropic call completed"}],
                    model=model,
                )
                return response
            except Exception as e:
                elapsed = round(time.time() - start, 3)
                _log(
                    action="anthropic_message",
                    decision="ERROR",
                    input_data={"model": model, "prompt": input_preview},
                    output_data={"error": str(e)[:300], "latency_s": elapsed},
                    reasoning=[{"factor": "exception", "value": type(e).__name__,
                                "signal": "block", "explanation": str(e)[:200]}],
                )
                raise

        Messages.create = _patched_anthropic_create

        # Async
        try:
            from anthropic.resources.messages import AsyncMessages
            original_async_create = AsyncMessages.create

            async def _patched_anthropic_async_create(self, *args, **kwargs):
                start  = time.time()
                model  = kwargs.get("model", "unknown")
                msgs   = kwargs.get("messages", [])
                input_preview = str(msgs[-1].get("content", ""))[:300] if msgs else ""
                try:
                    response = await original_async_create(self, *args, **kwargs)
                    elapsed  = round(time.time() - start, 3)
                    output_text = ""
                    try:
                        output_text = response.content[0].text[:500]
                    except Exception:
                        pass
                    usage = {}
                    try:
                        u = response.usage
                        usage = {"input_tokens": u.input_tokens, "output_tokens": u.output_tokens}
                    except Exception:
                        pass
                    _log(
                        action="anthropic_message",
                        decision="COMPLETED",
                        input_data={"model": model, "prompt": input_preview, "n_messages": len(msgs)},
                        output_data={"text": output_text, "latency_s": elapsed, **usage},
                        reasoning=[{"factor": "latency_s", "value": str(elapsed),
                                    "signal": "safe", "explanation": "Anthropic async call completed"}],
                        model=model,
                    )
                    return response
                except Exception as e:
                    elapsed = round(time.time() - start, 3)
                    _log(
                        action="anthropic_message",
                        decision="ERROR",
                        input_data={"model": model, "prompt": input_preview},
                        output_data={"error": str(e)[:300], "latency_s": elapsed},
                        reasoning=[{"factor": "exception", "value": type(e).__name__,
                                    "signal": "block", "explanation": str(e)[:200]}],
                    )
                    raise

            AsyncMessages.create = _patched_anthropic_async_create
        except Exception:
            pass

        if not _SILENT:
            sys.stderr.write(f'[SealVera] autoload: patched anthropic — agent="{_AGENT}"\n')

    except Exception as e:
        if not _SILENT:
            sys.stderr.write(f"[SealVera] autoload: anthropic patch failed — {e}\n")


# ── Import hook — fires when openai/anthropic are imported ────────────────────

class _SealVeraImportHook(importlib.abc.MetaPathFinder):
    """
    Intercepts imports of openai and anthropic and patches them immediately.
    Installed into sys.meta_path so it fires before the module is first used.
    """
    _TARGETS = {"openai", "anthropic"}

    def find_spec(self, fullname, path, target=None):
        if fullname in self._TARGETS and fullname not in _patched:
            # Let the normal import proceed, then patch
            import threading
            def _do_patch():
                try:
                    mod = sys.modules.get(fullname)
                    if mod is None:
                        mod = importlib.import_module(fullname)
                    if fullname == "openai":
                        _patch_openai(mod)
                    elif fullname == "anthropic":
                        _patch_anthropic(mod)
                except Exception:
                    pass
            # Use a tiny delay so the module finishes loading first
            threading.Timer(0.01, _do_patch).start()
        return None  # always let normal import proceed


def _patch_already_loaded():
    """Patch any modules already imported before autoload ran."""
    if "openai" in sys.modules and "openai" not in _patched:
        _patch_openai(sys.modules["openai"])
    if "anthropic" in sys.modules and "anthropic" not in _patched:
        _patch_anthropic(sys.modules["anthropic"])


# ── Activate ──────────────────────────────────────────────────────────────────

def activate():
    """Install the import hook and patch already-loaded modules."""
    if not _API_KEY:
        if not _SILENT:
            sys.stderr.write("[SealVera] autoload: no API key — set SEALVERA_API_KEY\n")
        return

    # Install import hook
    hook = _SealVeraImportHook()
    if not any(isinstance(h, _SealVeraImportHook) for h in sys.meta_path):
        sys.meta_path.insert(0, hook)

    # Patch anything already loaded
    _patch_already_loaded()

    if not _SILENT:
        sys.stderr.write(
            f'[SealVera] autoload: active — agent="{_AGENT}" endpoint="{_ENDPOINT}"\n'
        )


# Auto-activate if SEALVERA_AUTOLOAD=1 or if this module is explicitly imported
if _AUTOLOAD or __name__ == "sealvera.autoload":
    activate()
