"""
Reputation System – Tracks agent reliability scores.

Score range: 0.0 to 5.0. New agents start at 3.0 (neutral).
Score = 50% success rate + 30% dispute rate + 20% response time.
"""
from __future__ import annotations
import json
import os
import time
from dataclasses import dataclass, asdict, field


@dataclass
class ReputationConfig:
    """Configuration for reputation scoring."""
    default_score: float = 3.0
    min_score: float = 0.0
    max_score: float = 5.0
    delivery_success_weight: float = 0.5
    dispute_rate_weight: float = 0.3
    response_time_weight: float = 0.2
    # How much reputation affects find_best (0.0 = price only, 1.0 = rep only)
    reputation_factor: float = 0.3


@dataclass
class AgentReputation:
    """Reputation record for a single agent."""
    wallet: str
    score: float = 3.0
    total_deliveries: int = 0
    successful_deliveries: int = 0
    failed_deliveries: int = 0
    disputes_as_provider: int = 0
    total_response_time_seconds: float = 0.0
    avg_response_time_seconds: float = 0.0
    last_updated: float = 0.0


class ReputationEngine:
    """
    Manages agent reputation scores.

    Usage:
        engine = ReputationEngine()
        engine.record_success("0xProvider...", response_time=5.0)
        score = engine.get_score("0xProvider...")
        best = engine.find_best(providers)
    """

    def __init__(self, config: ReputationConfig | None = None, storage_path: str = ""):
        self.config = config or ReputationConfig()
        if not storage_path:
            storage_path = os.path.join(
                os.path.dirname(__file__), "..", "reputation.json"
            )
        self._storage_path = os.path.abspath(storage_path)
        self._reputations: dict[str, dict] = self._load()

    def _load(self) -> dict:
        if os.path.exists(self._storage_path):
            with open(self._storage_path, "r") as f:
                return json.load(f)
        return {}

    def _save(self):
        with open(self._storage_path, "w") as f:
            json.dump(self._reputations, f, indent=2, ensure_ascii=False)

    def _ensure_agent(self, wallet: str) -> dict:
        """Create reputation record if it doesn't exist."""
        if wallet not in self._reputations:
            rep = AgentReputation(wallet=wallet, score=self.config.default_score)
            self._reputations[wallet] = asdict(rep)
            self._save()
        return self._reputations[wallet]

    def get_score(self, wallet: str) -> float:
        """Get current reputation score."""
        return self._ensure_agent(wallet)["score"]

    def get_reputation(self, wallet: str) -> AgentReputation:
        """Get full reputation record."""
        rep = self._ensure_agent(wallet)
        return AgentReputation(**{k: v for k, v in rep.items()
                                  if k in AgentReputation.__dataclass_fields__})

    def record_success(self, provider_wallet: str, response_time_seconds: float = 0.0):
        """Record a successful service delivery."""
        rep = self._ensure_agent(provider_wallet)
        rep["total_deliveries"] += 1
        rep["successful_deliveries"] += 1
        if response_time_seconds > 0:
            rep["total_response_time_seconds"] += response_time_seconds
            rep["avg_response_time_seconds"] = (
                rep["total_response_time_seconds"] / rep["total_deliveries"]
            )
        rep["last_updated"] = time.time()
        self._recalculate_score(rep)
        self._save()

    def record_failure(self, provider_wallet: str):
        """Record a failed delivery (timeout/refund)."""
        rep = self._ensure_agent(provider_wallet)
        rep["total_deliveries"] += 1
        rep["failed_deliveries"] += 1
        rep["last_updated"] = time.time()
        self._recalculate_score(rep)
        self._save()

    def record_dispute(self, provider_wallet: str):
        """Record a dispute against a provider."""
        rep = self._ensure_agent(provider_wallet)
        rep["disputes_as_provider"] += 1
        rep["last_updated"] = time.time()
        self._recalculate_score(rep)
        self._save()

    def _recalculate_score(self, rep: dict):
        """Recalculate score from metrics."""
        total = rep["total_deliveries"]
        if total == 0:
            rep["score"] = self.config.default_score
            return

        # Success rate component (0.0 - 5.0)
        success_rate = rep["successful_deliveries"] / total
        success_score = success_rate * 5.0

        # Dispute rate component (5.0 = no disputes, 0.0 = all disputed)
        dispute_rate = rep["disputes_as_provider"] / total
        dispute_score = (1.0 - min(dispute_rate, 1.0)) * 5.0

        # Response time component
        avg_time = rep["avg_response_time_seconds"]
        if avg_time <= 0:
            time_score = self.config.default_score
        elif avg_time <= 10:
            time_score = 5.0
        elif avg_time <= 60:
            time_score = 4.0
        elif avg_time <= 300:
            time_score = 3.0
        elif avg_time <= 900:
            time_score = 2.0
        else:
            time_score = 1.0

        # Weighted combination
        raw = (
            success_score * self.config.delivery_success_weight
            + dispute_score * self.config.dispute_rate_weight
            + time_score * self.config.response_time_weight
        )
        rep["score"] = round(
            max(self.config.min_score, min(self.config.max_score, raw)), 2
        )

    def find_best(self, providers: list[dict],
                  reputation_factor: float | None = None) -> dict | None:
        """
        Find best provider by combining price and reputation.

        reputation_factor: 0.0 = cheapest wins, 1.0 = highest rep wins.
        """
        if not providers:
            return None

        factor = reputation_factor if reputation_factor is not None else self.config.reputation_factor

        if factor <= 0.0:
            return min(providers, key=lambda p: p["price_usd"])

        # Normalize prices
        prices = [p["price_usd"] for p in providers]
        max_price = max(prices)
        min_price = min(prices)
        price_range = max_price - min_price if max_price != min_price else 1.0

        scored = []
        for p in providers:
            price_score = 1.0 - ((p["price_usd"] - min_price) / price_range)
            rep_score = self.get_score(p["wallet"]) / 5.0
            combined = (1.0 - factor) * price_score + factor * rep_score
            scored.append((combined, p))

        scored.sort(key=lambda x: x[0], reverse=True)
        return scored[0][1]
