"""
Market2Agent — Trust Gate Middleware
Drop-in ASGI middleware that gates incoming requests behind trust checks.

    from market2agent import TrustGate

    # FastAPI
    app.add_middleware(TrustGate, min_score=500)

    # Starlette
    app = TrustGate(app, min_score=500)

Every request with an Origin or Referer header gets its domain
trust-scored. Requests from untrusted origins are rejected.

This is how you sell trust scores at scale:
    1. Developer installs market2agent
    2. Adds one line of middleware
    3. Every request triggers a trust check
    4. Every trust check is a paid API call
    5. Flywheel turns

Config via environment:
    MARKET2AGENT_API_KEY=m2a_live_...
    MARKET2AGENT_MIN_SCORE=500          # minimum trust score to allow
    MARKET2AGENT_CACHE_TTL=3600         # cache scores for 1 hour
"""
import os
import time
import logging
from typing import Optional, Dict, Callable
from urllib.parse import urlparse

logger = logging.getLogger("market2agent.middleware")


class TrustGate:
    """
    ASGI middleware that checks trust scores for incoming request origins.

    Caches scores in-memory to avoid per-request API calls.
    Untrusted origins get a 403 with the trust score details.

    Args:
        app: ASGI application
        min_score: Minimum trust score to allow (default 500)
        cache_ttl: Seconds to cache a score (default 3600 = 1 hour)
        api_key: API key (or set MARKET2AGENT_API_KEY env var)
        on_fail: "block" (403) or "header" (pass through with X-Trust-Score header)
        exclude_paths: Paths to skip (e.g. ["/health", "/docs"])
    """

    def __init__(
        self,
        app,
        min_score: int = 500,
        cache_ttl: int = 3600,
        api_key: Optional[str] = None,
        on_fail: str = "block",
        exclude_paths: Optional[list] = None,
    ):
        self.app = app
        self.min_score = int(os.environ.get("MARKET2AGENT_MIN_SCORE", min_score))
        self.cache_ttl = int(os.environ.get("MARKET2AGENT_CACHE_TTL", cache_ttl))
        self.api_key = api_key or os.environ.get("MARKET2AGENT_API_KEY", "")
        self.on_fail = on_fail
        self.exclude_paths = set(exclude_paths or ["/health", "/docs", "/redoc", "/openapi.json"])
        self._cache: Dict[str, dict] = {}  # domain → {score, grade, ts}

    async def __call__(self, scope, receive, send):
        if scope["type"] != "http":
            return await self.app(scope, receive, send)

        # Skip excluded paths
        path = scope.get("path", "")
        if path in self.exclude_paths:
            return await self.app(scope, receive, send)

        # Extract origin domain from headers
        headers = dict(scope.get("headers", []))
        origin = self._extract_origin(headers)

        if not origin:
            # No origin header — pass through (same-origin or server-to-server)
            return await self.app(scope, receive, send)

        # Check trust
        trust_data = await self._check_trust(origin)

        if trust_data and trust_data["score"] >= self.min_score:
            # Trusted — add headers and pass through
            scope = self._inject_trust_headers(scope, trust_data)
            return await self.app(scope, receive, send)

        # Untrusted
        if self.on_fail == "header":
            # Pass through but add warning headers
            scope = self._inject_trust_headers(scope, trust_data or {"score": 0, "grade": "D"})
            return await self.app(scope, receive, send)

        # Block (default)
        score = trust_data["score"] if trust_data else 0
        grade = trust_data["grade"] if trust_data else "D"
        body = (
            f'{{"error":"origin_untrusted",'
            f'"origin":"{origin}",'
            f'"trust_score":{score},'
            f'"trust_grade":"{grade}",'
            f'"min_required":{self.min_score},'
            f'"message":"Origin domain does not meet minimum trust requirements.",'
            f'"upgrade":"Contact the origin domain owner to improve their trust score at market2agent.ai"}}'
        ).encode()

        await send({
            "type": "http.response.start",
            "status": 403,
            "headers": [
                [b"content-type", b"application/json"],
                [b"x-trust-score", str(score).encode()],
                [b"x-trust-grade", grade.encode()],
                [b"x-blocked-by", b"market2agent-trust-gate"],
            ],
        })
        await send({"type": "http.response.body", "body": body})

    def _extract_origin(self, headers: dict) -> Optional[str]:
        """Extract domain from Origin or Referer header."""
        for header_name in [b"origin", b"referer"]:
            value = headers.get(header_name)
            if value:
                try:
                    parsed = urlparse(value.decode() if isinstance(value, bytes) else value)
                    domain = parsed.netloc or parsed.path
                    domain = domain.split(":")[0]  # strip port
                    domain = domain.replace("www.", "")
                    if domain and "." in domain:
                        return domain
                except Exception:
                    pass
        return None

    async def _check_trust(self, domain: str) -> Optional[dict]:
        """Check trust score with in-memory caching."""
        # Cache hit?
        cached = self._cache.get(domain)
        if cached and (time.time() - cached["ts"]) < self.cache_ttl:
            return cached

        # Cache miss — call API
        if not self.api_key:
            logger.warning("market2agent: No API key configured, skipping trust check")
            return None

        try:
            from market2agent.client import TrustClient
            client = TrustClient(api_key=self.api_key)
            result = client.score(domain)
            client.close()

            entry = {
                "score": result.score,
                "grade": result.grade,
                "recommendation": result.recommendation,
                "confidence": result.confidence,
                "ts": time.time(),
            }
            self._cache[domain] = entry
            return entry

        except Exception as e:
            logger.error(f"market2agent: Trust check failed for {domain}: {e}")
            return None

    def _inject_trust_headers(self, scope: dict, trust_data: dict) -> dict:
        """Add trust score headers to the request scope (visible to the app)."""
        # We can't modify ASGI scope headers easily, so we store in scope state
        if "state" not in scope:
            scope["state"] = {}
        scope["state"]["trust_score"] = trust_data.get("score", 0)
        scope["state"]["trust_grade"] = trust_data.get("grade", "D")
        return scope
