"""Shared utilities for Obol interceptors."""
import datetime
import hashlib
import json
import os
import re
import threading
import urllib.request
import urllib.error

# Thread-local storage for session tracking
_local = threading.local()

# Module-level cache for agent IDs (avoids re-registering in same process)
_agent_id_cache = {}


def _load_config():
    """Load API key and settings from ~/.obol/config.json."""
    config_path = os.path.expanduser("~/.obol/config.json")
    try:
        with open(config_path) as f:
            return json.load(f)
    except Exception:
        return {}


def _save_config(config):
    """Save config to ~/.obol/config.json (atomic write)."""
    config_dir = os.path.expanduser("~/.obol")
    config_path = os.path.join(config_dir, "config.json")
    try:
        os.makedirs(config_dir, exist_ok=True)
        tmp_path = config_path + ".tmp"
        fd = os.open(tmp_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
        try:
            with os.fdopen(fd, "w") as f:
                json.dump(config, f, indent=2)
            os.replace(tmp_path, config_path)
        except Exception:
            try:
                os.unlink(tmp_path)
            except OSError:
                pass
            raise
    except Exception:
        pass


def _get_api_credentials():
    """Get API key and URL from env or config."""
    config = _load_config()
    api_key = os.environ.get("OBOL_API_KEY") or config.get("api_key", "")
    api_url = os.environ.get("OBOL_API_URL") or config.get("api_url", "https://obol.grayowlresearch.com/v1")
    return api_key, api_url, config


def _register_agent(platform, api_key, api_url):
    """Register a new agent for the given platform. Returns agent ID or None."""
    try:
        import socket
        hostname = socket.gethostname()
    except Exception:
        hostname = "unknown"

    agent_data = {
        "name": f"{platform}-{hostname}",
        "platform": platform,
        "model": "unknown",
        "capabilities": ["tools"],
    }

    req = urllib.request.Request(
        f"{api_url.rstrip('/')}/agents",
        data=json.dumps(agent_data).encode(),
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
            "User-Agent": "obol-sdk/0.1.0",
        },
        method="POST",
    )

    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            data = json.loads(resp.read().decode())
            return data.get("id")
    except Exception:
        return None


def _get_agent_id(platform):
    """Get agent ID for a platform, auto-registering if needed."""
    # Check module cache first
    if platform in _agent_id_cache:
        return _agent_id_cache[platform]

    # Check config file
    config = _load_config()
    key = f"{platform.replace('-', '_')}_agent_id"
    agent_id = config.get(key) or config.get("default_agent_id")

    if agent_id:
        _agent_id_cache[platform] = agent_id
        return agent_id

    # Auto-register new agent
    api_key, api_url, _ = _get_api_credentials()
    if not api_key:
        return None

    agent_id = _register_agent(platform, api_key, api_url)
    if agent_id:
        # Save to config for persistence
        config[key] = agent_id
        if "api_key" not in config:
            config["api_key"] = api_key
        if "api_url" not in config:
            config["api_url"] = api_url
        _save_config(config)
        _agent_id_cache[platform] = agent_id

    return agent_id


def _content_hash(data):
    """Hash content for audit trail without storing content."""
    if data is None:
        return None
    try:
        if isinstance(data, str):
            raw = data.encode()
        else:
            raw = json.dumps(data, sort_keys=True, default=str).encode()
        return hashlib.sha256(raw).hexdigest()[:16]
    except Exception:
        return None


def _scrub_secrets(text):
    """Redact sensitive patterns from text."""
    if not text or not isinstance(text, str):
        return text
    text = re.sub(r'Bearer\s+\S+', 'Bearer [REDACTED]', text)
    text = re.sub(r'ob_(live|test)_[a-f0-9]+', r'ob_\1_[REDACTED]', text)
    text = re.sub(r'sk-[a-zA-Z0-9]+', 'sk-[REDACTED]', text)  # OpenAI keys
    text = re.sub(
        r'(api[_-]?key|token|secret|password|authorization)\s*[=:]\s*\S+',
        r'\1=[REDACTED]',
        text,
        flags=re.IGNORECASE,
    )
    return text


def _get_session_id():
    """Get or create a session ID for the current thread."""
    if not hasattr(_local, "session_id"):
        import uuid
        _local.session_id = str(uuid.uuid4())[:36]
    return _local.session_id


def set_session_id(session_id):
    """Set a custom session ID for the current thread."""
    _local.session_id = session_id


def send_event(platform, action, target=None, result="success", metadata=None, detected_patterns=None):
    """Send an event to the Obol API.

    Args:
        platform: Framework name (langchain, crewai, autogen, openai)
        action: Tool/action name
        target: Target of the action (file path, URL, etc.)
        result: success|error|blocked
        metadata: Additional metadata dict
        detected_patterns: List of detected obfuscation patterns
    """
    api_key, api_url, config = _get_api_credentials()
    agent_id = _get_agent_id(platform)

    if not api_key or not agent_id:
        return  # Not configured

    event = {
        "agent_id": agent_id,
        "session_id": _get_session_id(),
        "action": action[:100] if action else "unknown",
        "target": _scrub_secrets(str(target)[:2000]) if target else None,
        "target_hash": _content_hash(target),
        "result": result,
        "detected_patterns": detected_patterns or [],
        "metadata": {
            "platform": platform,
            "timestamp": datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None).isoformat() + "Z",
            **(metadata or {}),
        },
    }

    body = json.dumps({"events": [event]}).encode()
    req = urllib.request.Request(
        f"{api_url.rstrip('/')}/events/batch",
        data=body,
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
            "User-Agent": "obol-sdk/0.1.0",
        },
        method="POST",
    )

    try:
        urllib.request.urlopen(req, timeout=5)
    except Exception:
        # Log to local fallback (non-blocking)
        try:
            failed_path = os.path.expanduser("~/.obol/failed_events.jsonl")
            event["attempted_at"] = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None).isoformat() + "Z"
            fd = os.open(failed_path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)
            with os.fdopen(fd, "a") as f:
                f.write(json.dumps(event) + "\n")
        except Exception:
            pass
