"""
Market2Agent — Trust Client
Full-featured client for the Market2Agent Trust API.

Most users should use the hook:
    from market2agent import trust
    result = trust("stripe.com")

Power users who need batch scoring, comparison, chain verification,
or fine-grained control use the client directly:

    from market2agent import TrustClient
    client = TrustClient(api_key="m2a_live_...")
    result = client.score("stripe.com")
"""
import httpx
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Any


__version__ = "3.0.0"
SDK_USER_AGENT = f"market2agent-python/{__version__}"


# ═══ RESULT TYPES ═══

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

    This is what you're buying. Every field is backed by
    independently collected, SHA-256 hash-chained observations.

    Properties:
        .is_safe        → True if PROCEED or PROCEED_WITH_CAUTION
        .needs_review   → True if MANUAL_REVIEW or ENHANCED_DUE_DILIGENCE
        .should_reject  → True if REJECT
        .risk_summary   → Human-readable one-liner
    """
    target: str
    score: int = 0                  # 0-1000
    grade: str = "D"                # AAA, AA, A, BBB, BB, B, CCC, D
    recommendation: str = "REJECT"  # PROCEED, PROCEED_WITH_CAUTION, MANUAL_REVIEW, ENHANCED_DUE_DILIGENCE, REJECT
    is_verified: bool = False
    is_registered: bool = False
    confidence: float = 0.0
    confidence_label: str = "unknown"

    # Category breakdowns (paid tier)
    existence_age_score: float = 0
    security_integrity_score: float = 0
    reputation_scale_score: float = 0
    operational_maturity_score: float = 0

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

    # Full signal breakdown (paid tier)
    breakdown: Dict[str, Any] = field(default_factory=dict)

    # TrustChain metadata
    sources_used: int = 0
    sources_total: int = 0
    cap_applied: Optional[str] = None

    # Legacy 5-pillar fields (v2 compat)
    risk_level: str = "unknown"
    entity_type: str = "unknown"
    identity_score: float = 0
    competence_score: float = 0
    solvency_score: float = 0
    reputation_score: float = 0
    network_score: float = 0
    data_freshness: str = "unknown"
    data_sources: List[str] = field(default_factory=list)
    signal_count: int = 0

    # Metadata
    engine_version: str = "3.1.0"

    @property
    def is_safe(self) -> bool:
        """Quick check: safe to transact with?"""
        return self.recommendation in ("PROCEED", "PROCEED_WITH_CAUTION")

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

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

    @property
    def is_high_confidence(self) -> bool:
        """Score backed by substantial data?"""
        return self.confidence >= 0.6

    @property
    def risk_summary(self) -> str:
        """Human-readable one-liner."""
        if self.score >= 800:
            return f"{self.target}: Highly trusted ({self.grade}, score {self.score})"
        elif self.score >= 600:
            return f"{self.target}: Acceptable trust ({self.grade}, score {self.score})"
        elif self.score >= 400:
            return f"{self.target}: Elevated risk ({self.grade}, score {self.score})"
        else:
            return f"{self.target}: HIGH RISK ({self.grade}, score {self.score})"


@dataclass
class CompareResult:
    """Side-by-side comparison of two entities."""
    entity_a: TrustResult
    entity_b: TrustResult
    safer_entity: str
    score_difference: int = 0
    analysis: Dict[str, Any] = field(default_factory=dict)


@dataclass
class ChainVerification:
    """Result of verifying an entity's TrustChain integrity."""
    entity_id: str
    verified: bool
    blocks_checked: int
    breaks: List[Dict[str, Any]] = field(default_factory=list)
    chain_head: str = ""


@dataclass
class UsageInfo:
    """API key usage and quota."""
    calls_today: int = 0
    calls_this_month: int = 0
    monthly_quota: int = 0
    remaining: int = 0


# ═══ EXCEPTIONS ═══

class TrustCheckError(Exception):
    """Base exception for trust check failures."""
    def __init__(self, message: str, status_code: int = 0, detail: dict = None):
        self.status_code = status_code
        self.detail = detail or {}
        super().__init__(message)


class EntityNotFound(TrustCheckError):
    """Entity could not be resolved."""
    pass


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


class QuotaExceeded(TrustCheckError):
    """Monthly quota exhausted. Upgrade plan."""
    pass


# ═══ SYNC CLIENT ═══

class TrustClient:
    """
    Market2Agent Trust Client (synchronous).

    The full-featured client for power users. Supports:
        - Single entity scoring
        - Batch scoring (up to 25)
        - Entity comparison
        - TrustChain verification
        - Usage tracking

    For simple use cases, prefer the hook:
        from market2agent import trust
        result = trust("stripe.com")

    Args:
        api_key: Your API key (starts with m2a_live_ or m2a_test_)
        base_url: API endpoint (default: https://api.market2agent.ai)
        timeout: Request timeout in seconds
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = "https://api.market2agent.ai",
        timeout: float = 15.0,
    ):
        if not api_key.startswith("m2a_"):
            raise ValueError(
                "Invalid API key. Keys start with 'm2a_live_' or 'm2a_test_'. "
                "Get yours at https://market2agent.ai"
            )
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self._client = httpx.Client(
            base_url=self.base_url,
            headers={"X-API-Key": self.api_key, "User-Agent": SDK_USER_AGENT},
            timeout=timeout,
        )

    # ── Core: Score ───────────────────────────────

    def score(self, target: str) -> TrustResult:
        """
        Score any entity on the internet.

            result = client.score("stripe.com")
            result = client.score("openai")
            result = client.score("random-bot.ai")
            result = client.score("ceo@company.com")

        Each call queries 9 independent sensors, records observations
        to the TrustChain, and returns a scored result.

        This is a metered API call. Each call counts against your quota.
        """
        resp = self._client.get("/v1/trust/score", params={"target": target})
        return self._parse_result(resp, target)

    def preview(self, target: str) -> TrustResult:
        """
        Free preview score. Returns score + grade but no breakdown.
        Rate-limited to 10/min per IP. No API key required on the
        raw endpoint, but using the SDK is convenient.
        """
        resp = self._client.get("/v1/trust/preview", params={"target": target})
        return self._parse_result(resp, target)

    # ── Batch ─────────────────────────────────────

    def batch_score(self, targets: List[str], max_targets: int = 25) -> List[TrustResult]:
        """
        Score up to 25 entities in a single API call.

            results = client.batch_score(["stripe.com", "shopify.com", "unknown.xyz"])
            for r in results:
                print(f"{r.target}: {r.score} ({r.grade})")
        """
        if len(targets) > max_targets:
            raise ValueError(f"Maximum {max_targets} targets per batch. Got {len(targets)}.")

        resp = self._client.post("/v1/trust/batch", json={"targets": targets})
        if resp.status_code != 200:
            self._handle_error(resp)

        data = resp.json()
        return [self._map_result(r) for r in data.get("results", [])]

    # ── Compare ───────────────────────────────────

    def compare(self, entity_a: str, entity_b: str) -> CompareResult:
        """
        Compare two entities side-by-side.

            cmp = client.compare("stripe.com", "sketchy-payments.xyz")
            print(f"Safer: {cmp.safer_entity}")
            print(f"Score gap: {cmp.score_difference}")
        """
        resp = self._client.get(
            "/v1/trust/compare",
            params={"entity_a": entity_a, "entity_b": entity_b},
        )
        if resp.status_code != 200:
            self._handle_error(resp)

        data = resp.json()
        return CompareResult(
            entity_a=self._map_result(data.get("entity_a", {})),
            entity_b=self._map_result(data.get("entity_b", {})),
            safer_entity=data.get("winner", "unknown"),
            score_difference=data.get("score_difference", 0),
            analysis=data.get("analysis", {}),
        )

    # ── TrustChain ────────────────────────────────

    def verify_chain(self, entity_id: str) -> ChainVerification:
        """
        Verify the cryptographic integrity of an entity's observation chain.

        This is the audit endpoint. It re-computes every block hash and
        confirms the chain is unbroken. If any observation was tampered
        with, this will catch it.

            verification = client.verify_chain("stripe.com")
            assert verification.verified  # Chain is intact
        """
        resp = self._client.get(f"/v1/trust/chain/{entity_id}/verify")
        if resp.status_code != 200:
            self._handle_error(resp)
        data = resp.json()
        return ChainVerification(
            entity_id=entity_id,
            verified=data.get("verified", False),
            blocks_checked=data.get("blocks_checked", 0),
            breaks=data.get("breaks", []),
            chain_head=data.get("chain_head", ""),
        )

    def chain_history(self, entity_id: str, limit: int = 20) -> Dict[str, Any]:
        """
        Get the observation history for an entity.
        Every block is a timestamped, SHA-256 hashed sensor reading.
        """
        resp = self._client.get(
            f"/v1/trust/chain/{entity_id}/history",
            params={"limit": limit},
        )
        if resp.status_code != 200:
            self._handle_error(resp)
        return resp.json()

    # ── Usage ─────────────────────────────────────

    def usage(self) -> UsageInfo:
        """Check API key usage and remaining quota."""
        resp = self._client.get("/v1/trust/usage")
        if resp.status_code != 200:
            self._handle_error(resp)
        data = resp.json()
        return UsageInfo(
            calls_today=data.get("calls_today", 0),
            calls_this_month=data.get("calls_this_month", 0),
            monthly_quota=data.get("monthly_quota", 0),
            remaining=data.get("remaining", 0),
        )

    # ── Response Parsing ──────────────────────────

    def _parse_result(self, resp: httpx.Response, target: str) -> TrustResult:
        if resp.status_code == 200:
            return self._map_result(resp.json(), target)
        self._handle_error(resp, target)

    def _map_result(self, data: dict, fallback_target: str = "") -> TrustResult:
        return TrustResult(
            target=data.get("target", fallback_target),
            score=data.get("score", 0),
            grade=data.get("grade", "D"),
            recommendation=data.get("recommendation", "REJECT"),
            is_verified=data.get("is_verified", False),
            is_registered=data.get("is_registered", False),
            confidence=data.get("confidence", 0),
            confidence_label=data.get("confidence_label", "unknown"),
            existence_age_score=data.get("existence_age_score", 0),
            security_integrity_score=data.get("security_integrity_score", 0),
            reputation_scale_score=data.get("reputation_scale_score", 0),
            operational_maturity_score=data.get("operational_maturity_score", 0),
            infra_dna=data.get("infrastructure_dna", {}),
            breakdown=data.get("breakdown", {}),
            sources_used=data.get("sources_used", 0),
            sources_total=data.get("sources_total", 0),
            cap_applied=data.get("cap_applied"),
            risk_level=data.get("risk_level", "unknown"),
            entity_type=data.get("entity_type", "unknown"),
            identity_score=data.get("identity_score", 0),
            competence_score=data.get("competence_score", 0),
            solvency_score=data.get("solvency_score", 0),
            reputation_score=data.get("reputation_score", 0),
            network_score=data.get("network_score", 0),
            data_freshness=data.get("data_freshness", "unknown"),
            data_sources=data.get("data_sources", []),
            signal_count=data.get("signal_count", 0),
            engine_version=data.get("engine_version", "3.1.0"),
        )

    def _handle_error(self, resp: httpx.Response, target: str = ""):
        if resp.status_code == 404:
            raise EntityNotFound(f"Entity '{target}' not found", status_code=404)
        elif resp.status_code == 429:
            detail = {}
            try:
                detail = resp.json()
            except Exception:
                pass
            if isinstance(detail, dict) and detail.get("error") == "quota_exceeded":
                raise QuotaExceeded(
                    "Monthly quota exceeded. Upgrade at market2agent.ai/pricing",
                    status_code=429, detail=detail,
                )
            retry_after = int(resp.headers.get("Retry-After", 60))
            raise RateLimited(retry_after=retry_after, status_code=429)
        elif resp.status_code == 401:
            raise TrustCheckError(
                "Invalid or missing API key. Get yours at market2agent.ai",
                status_code=401,
            )
        else:
            raise TrustCheckError(f"API error: {resp.status_code}", status_code=resp.status_code)

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

    def __enter__(self):
        return self

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


# ═══ ASYNC CLIENT ═══

class AsyncTrustClient:
    """
    Async version of TrustClient for AI agent frameworks,
    async web servers, and high-throughput pipelines.

        async with AsyncTrustClient(api_key="m2a_live_...") as client:
            result = await client.score("stripe.com")
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = "https://api.market2agent.ai",
        timeout: float = 15.0,
    ):
        if not api_key.startswith("m2a_"):
            raise ValueError("Invalid API key. Get yours at https://market2agent.ai")
        self.api_key = api_key
        self._client = httpx.AsyncClient(
            base_url=base_url.rstrip("/"),
            headers={"X-API-Key": api_key, "User-Agent": SDK_USER_AGENT},
            timeout=timeout,
        )

    async def score(self, target: str) -> TrustResult:
        """Score any entity (async)."""
        resp = await self._client.get("/v1/trust/score", params={"target": target})
        if resp.status_code == 200:
            return _map(resp.json(), target)
        _raise(resp, target)

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

    async def batch_score(self, targets: List[str]) -> List[TrustResult]:
        """Batch score up to 25 entities (async)."""
        resp = await self._client.post("/v1/trust/batch", json={"targets": targets})
        if resp.status_code == 200:
            return [_map(r) for r in resp.json().get("results", [])]
        _raise(resp)

    async def compare(self, entity_a: str, entity_b: str) -> CompareResult:
        """Compare two entities (async)."""
        resp = await self._client.get(
            "/v1/trust/compare",
            params={"entity_a": entity_a, "entity_b": entity_b},
        )
        if resp.status_code == 200:
            data = resp.json()
            return CompareResult(
                entity_a=_map(data.get("entity_a", {})),
                entity_b=_map(data.get("entity_b", {})),
                safer_entity=data.get("winner", "unknown"),
                score_difference=data.get("score_difference", 0),
                analysis=data.get("analysis", {}),
            )
        _raise(resp)

    async def verify_chain(self, entity_id: str) -> ChainVerification:
        """Verify chain integrity (async)."""
        resp = await self._client.get(f"/v1/trust/chain/{entity_id}/verify")
        if resp.status_code == 200:
            data = resp.json()
            return ChainVerification(
                entity_id=entity_id,
                verified=data.get("verified", False),
                blocks_checked=data.get("blocks_checked", 0),
                breaks=data.get("breaks", []),
                chain_head=data.get("chain_head", ""),
            )
        _raise(resp)

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

    async def __aenter__(self):
        return self

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


# ── Shared helpers ────────────────────────────────

def _map(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"),
        is_verified=data.get("is_verified", False),
        is_registered=data.get("is_registered", False),
        confidence=data.get("confidence", 0),
        confidence_label=data.get("confidence_label", "unknown"),
        existence_age_score=data.get("existence_age_score", 0),
        security_integrity_score=data.get("security_integrity_score", 0),
        reputation_scale_score=data.get("reputation_scale_score", 0),
        operational_maturity_score=data.get("operational_maturity_score", 0),
        infra_dna=data.get("infrastructure_dna", {}),
        breakdown=data.get("breakdown", {}),
        sources_used=data.get("sources_used", 0),
        sources_total=data.get("sources_total", 0),
        cap_applied=data.get("cap_applied"),
        engine_version=data.get("engine_version", "3.1.0"),
    )


def _raise(resp: httpx.Response, target: str = ""):
    if resp.status_code == 404:
        raise EntityNotFound(f"Entity '{target}' not found", status_code=404)
    elif resp.status_code == 429:
        raise RateLimited(retry_after=int(resp.headers.get("Retry-After", 60)), status_code=429)
    elif resp.status_code == 401:
        raise TrustCheckError("Invalid API key", status_code=401)
    raise TrustCheckError(f"API error: {resp.status_code}", status_code=resp.status_code)
