"""
TrustChain — Core Module
The background check of the internet.

    from trustchain import verify
    result = verify("stripe.com")

One function. One answer. Trusted or not.

Under the hood, this calls the Market2Agent Trust API.
The developer sees TrustChain. The API sees Market2Agent.
"""
import os
import time
import functools
import logging
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Any, Callable
from urllib.parse import urlparse

import httpx


__version__ = "1.0.0"
_USER_AGENT = f"trustchain-python/{__version__}"
_DEFAULT_API = "https://api.market2agent.ai"

logger = logging.getLogger("trustchain")


# ═══════════════════════════════════════════════════
# RESULT
# ═══════════════════════════════════════════════════

@dataclass
class TrustResult:
    """
    The trust verdict for an entity.

    The only things most developers need:
        .trusted     → bool: safe to proceed?
        .score       → int: 0-1000
        .grade       → str: AAA through D
        .reason      → str: one-line explanation

    Everything else is available for those who want it.
    """
    target: str
    score: int = 0
    grade: str = "D"
    recommendation: str = "REJECT"
    confidence: float = 0.0

    # Category scores (paid tier)
    existence: float = 0
    security: float = 0
    reputation: float = 0
    maturity: float = 0

    # Infrastructure DNA (paid tier)
    infra: Dict[str, Any] = field(default_factory=dict)

    # Chain metadata
    sources: int = 0
    cap: Optional[str] = None

    @property
    def trusted(self) -> bool:
        """Safe to proceed? This is the answer to the question."""
        return self.recommendation in ("PROCEED", "PROCEED_WITH_CAUTION")

    @property
    def risky(self) -> bool:
        """Needs human review before proceeding."""
        return self.recommendation in ("MANUAL_REVIEW", "ENHANCED_DUE_DILIGENCE")

    @property
    def rejected(self) -> bool:
        """Do not proceed."""
        return self.recommendation == "REJECT"

    @property
    def reason(self) -> str:
        """One-line human-readable verdict."""
        if self.score >= 800:
            return f"{self.target} — highly trusted ({self.grade}, {self.score}/1000)"
        elif self.score >= 600:
            return f"{self.target} — acceptable ({self.grade}, {self.score}/1000)"
        elif self.score >= 400:
            return f"{self.target} — elevated risk ({self.grade}, {self.score}/1000)"
        else:
            return f"{self.target} — untrusted ({self.grade}, {self.score}/1000)"

    def __bool__(self) -> bool:
        """
        if verify("stripe.com"):
            proceed()

        TrustResult is truthy when the entity is trusted.
        """
        return self.trusted

    def __repr__(self) -> str:
        icon = "✓" if self.trusted else "✗"
        return f"TrustResult({icon} {self.target} {self.score}/1000 {self.grade})"


# ═══════════════════════════════════════════════════
# EXCEPTIONS
# ═══════════════════════════════════════════════════

class TrustCheckError(Exception):
    """Something went wrong checking trust."""
    def __init__(self, message: str, status_code: int = 0):
        self.status_code = status_code
        super().__init__(message)


class Untrusted(TrustCheckError):
    """Entity did not meet the trust threshold."""
    def __init__(self, result: TrustResult, min_score: int):
        self.result = result
        self.min_score = min_score
        super().__init__(
            f"{result.target} scored {result.score} (need {min_score}): {result.reason}"
        )


class RateLimited(TrustCheckError):
    """Too many requests."""
    def __init__(self, retry_after: int = 60):
        self.retry_after = retry_after
        super().__init__(f"Rate limited. Retry after {retry_after}s")


# ═══════════════════════════════════════════════════
# CONFIGURATION
# ═══════════════════════════════════════════════════

_config = {
    "api_key": None,
    "api_url": _DEFAULT_API,
    "timeout": 15.0,
}
_client_cache: Dict[str, Any] = {}


def configure(
    api_key: Optional[str] = None,
    api_url: Optional[str] = None,
    timeout: Optional[float] = None,
):
    """
    Configure TrustChain. Call once at startup, or just set the env var.

        # Option A: environment variable
        export TRUSTCHAIN_API_KEY=m2a_live_...

        # Option B: explicit
        import trustchain
        trustchain.configure(api_key="m2a_live_...")
    """
    if api_key is not None:
        _config["api_key"] = api_key
    if api_url is not None:
        _config["api_url"] = api_url
    if timeout is not None:
        _config["timeout"] = timeout
    _client_cache.clear()


def _get_key() -> str:
    key = (
        _config["api_key"]
        or os.environ.get("TRUSTCHAIN_API_KEY")
        or os.environ.get("MARKET2AGENT_API_KEY")  # backward compat
        or ""
    )
    if not key:
        raise TrustCheckError(
            "No API key. Set TRUSTCHAIN_API_KEY or call trustchain.configure(api_key=...)\n"
            "Get your key at https://market2agent.ai"
        )
    return key


def _sync_client() -> "TrustClient":
    if "sync" not in _client_cache:
        _client_cache["sync"] = TrustClient(
            api_key=_get_key(),
            api_url=_config["api_url"],
            timeout=_config["timeout"],
        )
    return _client_cache["sync"]


def _async_client() -> "AsyncTrustClient":
    if "async" not in _client_cache:
        _client_cache["async"] = AsyncTrustClient(
            api_key=_get_key(),
            api_url=_config["api_url"],
            timeout=_config["timeout"],
        )
    return _client_cache["async"]


# ═══════════════════════════════════════════════════
# THE HOOK — verify()
# ═══════════════════════════════════════════════════

def verify(target: str) -> TrustResult:
    """
    Run a background check on any entity.

        from trustchain import verify

        result = verify("stripe.com")
        result.trusted     # True
        result.score       # 875
        result.grade       # "AAA"
        result.reason      # "stripe.com — highly trusted (AAA, 875/1000)"

        # TrustResult is truthy when trusted:
        if verify("stripe.com"):
            proceed()

    Accepts: domain, URL, company name, email, API endpoint, agent ID.
    Returns: TrustResult with score, grade, and clear verdict.
    """
    return _sync_client().verify(target)


async def verify_async(target: str) -> TrustResult:
    """
    Async background check. For AI agents, async servers, pipelines.

        from trustchain import verify_async

        result = await verify_async("stripe.com")
        if result.trusted:
            await call_api()
    """
    return await _async_client().verify(target)


# ═══════════════════════════════════════════════════
# THE DECORATOR — @guard()
# ═══════════════════════════════════════════════════

def guard(target: str, min_score: int = 500, on_fail: str = "raise"):
    """
    Gate a function behind a trust check.

        from trustchain import guard

        @guard("partner-api.com", min_score=600)
        def sync_partner_data():
            # Only runs if partner-api.com scores >= 600
            ...

        @guard("vendor.io", min_score=400, on_fail="warn")
        def fetch_catalog():
            # Runs anyway but logs a warning if under 400
            ...

    Args:
        target: Entity to check
        min_score: Minimum trust score (0-1000)
        on_fail: "raise" (Untrusted exception), "skip" (return None), "warn" (log + proceed)
    """
    def decorator(func: Callable) -> Callable:
        _cached: Dict[str, TrustResult] = {}

        @functools.wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            if "result" not in _cached:
                _cached["result"] = verify(target)

            result = _cached["result"]
            if result.score >= min_score:
                return func(*args, **kwargs)

            if on_fail == "raise":
                raise Untrusted(result, min_score)
            elif on_fail == "skip":
                return None
            elif on_fail == "warn":
                logger.warning(f"[trustchain] {result.reason} (need {min_score})")
                return func(*args, **kwargs)
            return func(*args, **kwargs)

        return wrapper
    return decorator


# ═══════════════════════════════════════════════════
# CLIENT — TrustClient
# ═══════════════════════════════════════════════════

class TrustClient:
    """
    Full TrustChain client. For batch checks, comparisons, chain audits.

    Most users just need verify(). Use TrustClient when you need:
        - Batch verification (up to 25 at once)
        - Side-by-side entity comparison
        - TrustChain integrity auditing
        - Usage tracking
    """

    def __init__(
        self,
        api_key: str,
        api_url: str = _DEFAULT_API,
        timeout: float = 15.0,
    ):
        self._client = httpx.Client(
            base_url=api_url.rstrip("/"),
            headers={"X-API-Key": api_key, "User-Agent": _USER_AGENT},
            timeout=timeout,
        )

    def verify(self, target: str) -> TrustResult:
        """Check trust for a single entity."""
        resp = self._client.get("/v1/trust/score", params={"target": target})
        if resp.status_code == 200:
            return _parse(resp.json(), target)
        _raise_for(resp, target)

    def preview(self, target: str) -> TrustResult:
        """Free preview. Score + grade only. Rate limited."""
        resp = self._client.get("/v1/trust/preview", params={"target": target})
        if resp.status_code == 200:
            return _parse(resp.json(), target)
        _raise_for(resp, target)

    def batch(self, targets: List[str]) -> List[TrustResult]:
        """Check up to 25 entities in one call."""
        if len(targets) > 25:
            raise ValueError("Max 25 per batch")
        resp = self._client.post("/v1/trust/batch", json={"targets": targets})
        if resp.status_code == 200:
            return [_parse(r) for r in resp.json().get("results", [])]
        _raise_for(resp)

    def compare(self, a: str, b: str) -> Dict[str, Any]:
        """Compare two entities side-by-side."""
        resp = self._client.get("/v1/trust/compare", params={"entity_a": a, "entity_b": b})
        if resp.status_code == 200:
            return resp.json()
        _raise_for(resp)

    def audit(self, entity_id: str) -> Dict[str, Any]:
        """
        Verify the cryptographic integrity of an entity's observation chain.
        Every observation is SHA-256 hashed and chained. This checks for tampering.
        """
        resp = self._client.get(f"/v1/trust/chain/{entity_id}/verify")
        if resp.status_code == 200:
            return resp.json()
        _raise_for(resp)

    def history(self, entity_id: str, limit: int = 20) -> Dict[str, Any]:
        """Get the full observation history for an entity."""
        resp = self._client.get(f"/v1/trust/chain/{entity_id}/history", params={"limit": limit})
        if resp.status_code == 200:
            return resp.json()
        _raise_for(resp)

    def usage(self) -> Dict[str, int]:
        """Check API usage and remaining quota."""
        resp = self._client.get("/v1/trust/usage")
        if resp.status_code == 200:
            return resp.json()
        _raise_for(resp)

    def close(self):
        self._client.close()

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.close()


# ═══════════════════════════════════════════════════
# ASYNC CLIENT
# ═══════════════════════════════════════════════════

class AsyncTrustClient:
    """Async TrustChain client for AI agents and async frameworks."""

    def __init__(
        self,
        api_key: str,
        api_url: str = _DEFAULT_API,
        timeout: float = 15.0,
    ):
        self._client = httpx.AsyncClient(
            base_url=api_url.rstrip("/"),
            headers={"X-API-Key": api_key, "User-Agent": _USER_AGENT},
            timeout=timeout,
        )

    async def verify(self, target: str) -> TrustResult:
        resp = await self._client.get("/v1/trust/score", params={"target": target})
        if resp.status_code == 200:
            return _parse(resp.json(), target)
        _raise_for(resp, target)

    async def preview(self, target: str) -> TrustResult:
        resp = await self._client.get("/v1/trust/preview", params={"target": target})
        if resp.status_code == 200:
            return _parse(resp.json(), target)
        _raise_for(resp, target)

    async def batch(self, targets: List[str]) -> List[TrustResult]:
        resp = await self._client.post("/v1/trust/batch", json={"targets": targets})
        if resp.status_code == 200:
            return [_parse(r) for r in resp.json().get("results", [])]
        _raise_for(resp)

    async def compare(self, a: str, b: str) -> Dict[str, Any]:
        resp = await self._client.get("/v1/trust/compare", params={"entity_a": a, "entity_b": b})
        if resp.status_code == 200:
            return resp.json()
        _raise_for(resp)

    async def audit(self, entity_id: str) -> Dict[str, Any]:
        resp = await self._client.get(f"/v1/trust/chain/{entity_id}/verify")
        if resp.status_code == 200:
            return resp.json()
        _raise_for(resp)

    async def close(self):
        await self._client.aclose()

    async def __aenter__(self):
        return self

    async def __aexit__(self, *args):
        await self.close()


# ═══════════════════════════════════════════════════
# MIDDLEWARE — TrustGate
# ═══════════════════════════════════════════════════

class TrustGate:
    """
    ASGI middleware. One line to trust-gate your entire app.

        from trustchain import TrustGate
        app.add_middleware(TrustGate, min_score=500)

    Cross-origin requests from untrusted domains get blocked.
    Trusted requests pass through with X-Trust-Score headers.
    Scores are cached in-memory (default 1 hour).
    """

    def __init__(
        self,
        app,
        min_score: int = 500,
        cache_ttl: int = 3600,
        api_key: Optional[str] = None,
        on_fail: str = "block",
        exclude: Optional[list] = None,
    ):
        self.app = app
        self.min_score = int(os.environ.get("TRUSTCHAIN_MIN_SCORE", min_score))
        self.cache_ttl = int(os.environ.get("TRUSTCHAIN_CACHE_TTL", cache_ttl))
        self.api_key = api_key or os.environ.get("TRUSTCHAIN_API_KEY") or os.environ.get("MARKET2AGENT_API_KEY", "")
        self.on_fail = on_fail
        self.exclude = set(exclude or ["/health", "/docs", "/redoc", "/openapi.json"])
        self._cache: Dict[str, dict] = {}

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

        path = scope.get("path", "")
        if path in self.exclude:
            return await self.app(scope, receive, send)

        headers = dict(scope.get("headers", []))
        origin = self._extract_origin(headers)

        if not origin:
            return await self.app(scope, receive, send)

        trust_data = self._check_cache(origin)
        if trust_data is None:
            trust_data = await self._check_live(origin)

        if trust_data and trust_data["score"] >= self.min_score:
            return await self.app(scope, receive, send)

        if self.on_fail == "pass":
            return await self.app(scope, receive, send)

        score = trust_data["score"] if trust_data else 0
        grade = trust_data["grade"] if trust_data else "D"
        body = (
            f'{{"error":"untrusted_origin",'
            f'"origin":"{origin}",'
            f'"score":{score},"grade":"{grade}",'
            f'"required":{self.min_score},'
            f'"message":"Origin does not meet trust requirements."}}'
        ).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()],
            ],
        })
        await send({"type": "http.response.body", "body": body})

    def _extract_origin(self, headers: dict) -> Optional[str]:
        for h in [b"origin", b"referer"]:
            val = headers.get(h)
            if val:
                try:
                    parsed = urlparse(val.decode() if isinstance(val, bytes) else val)
                    domain = (parsed.netloc or parsed.path).split(":")[0].replace("www.", "")
                    if domain and "." in domain:
                        return domain
                except Exception:
                    pass
        return None

    def _check_cache(self, domain: str) -> Optional[dict]:
        cached = self._cache.get(domain)
        if cached and (time.time() - cached["ts"]) < self.cache_ttl:
            return cached
        return None

    async def _check_live(self, domain: str) -> Optional[dict]:
        if not self.api_key:
            return None
        try:
            client = TrustClient(api_key=self.api_key)
            result = client.verify(domain)
            client.close()
            entry = {"score": result.score, "grade": result.grade, "ts": time.time()}
            self._cache[domain] = entry
            return entry
        except Exception as e:
            logger.error(f"trustchain: check failed for {domain}: {e}")
            return None


# ═══════════════════════════════════════════════════
# HELPERS
# ═══════════════════════════════════════════════════

def _parse(data: dict, fallback: str = "") -> TrustResult:
    return TrustResult(
        target=data.get("target", fallback),
        score=data.get("score", 0),
        grade=data.get("grade", "D"),
        recommendation=data.get("recommendation", "REJECT"),
        confidence=data.get("confidence", 0),
        existence=data.get("existence_age_score", 0),
        security=data.get("security_integrity_score", 0),
        reputation=data.get("reputation_scale_score", 0),
        maturity=data.get("operational_maturity_score", 0),
        infra=data.get("infrastructure_dna", {}),
        sources=data.get("sources_used", 0),
        cap=data.get("cap_applied"),
    )


def _raise_for(resp: httpx.Response, target: str = ""):
    if resp.status_code == 404:
        raise TrustCheckError(f"Could not resolve '{target}'", status_code=404)
    elif resp.status_code == 429:
        raise RateLimited(retry_after=int(resp.headers.get("Retry-After", 60)))
    elif resp.status_code == 401:
        raise TrustCheckError("Invalid API key. Get yours at market2agent.ai", status_code=401)
    raise TrustCheckError(f"API error: {resp.status_code}", status_code=resp.status_code)
