"""
BBEye Python SDK — Elegant API for BBEye MITM proxy decrypt scripts.

Usage:
    from bbeye import Result

    def onRequest(request):
        return Result.showJson(request.json())

    def onResponse(response):
        return Result.showText(response.body)

BBEye calls your script via: python3 -m bbeye your_script.py
Self-test via: python3 your_script.py (using if __name__ == "__main__" block)
"""

import json
import base64
import sys

__all__ = ["Request", "Response", "Result", "run", "test", "console"]


def console(*args, **kwargs):
    """Print message to BBEye console panel (via stderr).

    Usage: console("decrypted", len(data), "bytes")
    """
    print(*args, file=sys.stderr, **kwargs)


class _Message:
    """Base class for Request and Response."""

    __slots__ = ("url", "headers", "body", "direction")

    def __init__(self, data: dict):
        self.url: str = data.get("url", "")
        self.headers: dict = data.get("headers", {})
        self.direction: str = data.get("direction", "")
        body_b64 = data.get("body_base64", "")
        self.body: bytes = base64.b64decode(body_b64) if body_b64 else b""

    def text(self, encoding: str = "utf-8") -> str:
        """Decode body bytes to string."""
        return self.body.decode(encoding, errors="replace")

    def json(self):
        """Parse body as JSON."""
        return json.loads(self.body)

    def header(self, name: str):
        """Get header value by name (case-insensitive). Returns None if not found."""
        lower = name.lower()
        for k, v in self.headers.items():
            if k.lower() == lower:
                return v
        return None


class Request(_Message):
    """HTTP request passed to onRequest callback."""
    pass


class Response(_Message):
    """HTTP response passed to onResponse callback."""
    pass


class Result:
    """Factory methods for building script output.

    All methods accept bytes or str. SDK handles encoding conversion automatically.
    """

    @staticmethod
    def showText(data) -> dict:
        """Plain text display. bytes→UTF-8 decode; str→use directly."""
        if isinstance(data, bytes):
            data = data.decode("utf-8", errors="replace")
        return {"type": "text", "data": str(data)}

    @staticmethod
    def showJson(data) -> dict:
        """JSON formatted display. bytes→UTF-8 decode; dict/list→json.dumps; str→use directly."""
        if isinstance(data, bytes):
            data = data.decode("utf-8", errors="replace")
        if isinstance(data, (dict, list)):
            return {"type": "json", "data": data}
        return {"type": "json", "data": str(data)}

    @staticmethod
    def showHex(data) -> dict:
        """Hex display for binary data. bytes→base64; str→encode then base64."""
        if isinstance(data, str):
            data = data.encode("utf-8")
        return {"type": "hex", "encoding": "base64", "data": base64.b64encode(data).decode()}

    @staticmethod
    def showProtobuf(data) -> dict:
        """Protobuf display for binary data. bytes→base64; str→encode then base64."""
        if isinstance(data, str):
            data = data.encode("utf-8")
        return {"type": "protobuf", "encoding": "base64", "data": base64.b64encode(data).decode()}

    @staticmethod
    def error(msg: str) -> dict:
        """Error result."""
        return {"error": str(msg)}


def run(on_request=None, on_response=None):
    """Main entry point. Reads stdin JSON, dispatches by direction, writes result to stdout.

    Args:
        on_request: Callback for request direction. Receives Request, returns Result dict.
        on_response: Callback for response direction. Receives Response, returns Result dict.
    """
    for line in sys.stdin:
        line = line.strip()
        if not line:
            continue
        try:
            data = json.loads(line)
            direction = data.get("direction", "")

            if direction == "request" and on_request:
                result = on_request(Request(data))
            elif direction == "response" and on_response:
                result = on_response(Response(data))
            else:
                result = Result.showText("")

            if result is None:
                result = Result.showText("")
            elif isinstance(result, (str, bytes)):
                result = Result.showText(result)

            print(json.dumps(result, ensure_ascii=False), flush=True)
        except Exception as e:
            print(json.dumps({"error": str(e)}), flush=True)


def test(func, url="http://localhost/test", headers=None, body=b"",
         direction="request"):
    """Test a callback function locally without BBEye.

    Args:
        func: The onRequest or onResponse function to test.
        url: Test URL string.
        headers: Dict of HTTP headers (default: empty).
        body: Raw body bytes (default: empty).
        direction: "request" or "response".
    """
    if headers is None:
        headers = {}
    data = {
        "url": url,
        "headers": headers,
        "body_base64": base64.b64encode(body).decode() if body else "",
        "direction": direction,
    }
    msg = Request(data) if direction == "request" else Response(data)
    func_name = getattr(func, "__name__", str(func))
    print(f"--- test {func_name} ---")
    print(f"  url:       {url}")
    print(f"  direction: {direction}")
    print(f"  body:      {body[:50]!r}{'...' if len(body) > 50 else ''}")
    try:
        result = func(msg)
        if result is None:
            result = Result.showText("")
        elif isinstance(result, (str, bytes)):
            result = Result.showText(result)
        print(f"  result:    {json.dumps(result, ensure_ascii=False, indent=2)}")
    except Exception as e:
        print(f"  error:     {e}")
    print()
