"""Response models for the Kevros Governance SDK."""

from __future__ import annotations

import math
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, List, Optional


class Decision(str, Enum):
    ALLOW = "ALLOW"
    CLAMP = "CLAMP"
    DENY = "DENY"


class IntentType(str, Enum):
    NAVIGATION = "NAVIGATION"
    MANIPULATION = "MANIPULATION"
    SENSING = "SENSING"
    COMMUNICATION = "COMMUNICATION"
    MAINTENANCE = "MAINTENANCE"
    EMERGENCY = "EMERGENCY"
    OPERATOR_COMMAND = "OPERATOR_COMMAND"
    AI_GENERATED = "AI_GENERATED"
    AUTOMATED = "AUTOMATED"


class IntentSource(str, Enum):
    HUMAN_OPERATOR = "HUMAN_OPERATOR"
    AI_PLANNER = "AI_PLANNER"
    MISSION_SCRIPT = "MISSION_SCRIPT"
    REMOTE_API = "REMOTE_API"
    SENSOR_TRIGGER = "SENSOR_TRIGGER"
    INTERNAL = "INTERNAL"


class OutcomeStatus(str, Enum):
    ACHIEVED = "ACHIEVED"
    PARTIALLY_ACHIEVED = "PARTIALLY_ACHIEVED"
    FAILED = "FAILED"
    BLOCKED = "BLOCKED"
    TIMEOUT = "TIMEOUT"


class ModelParseError(Exception):
    """Raised when a response dictionary cannot be parsed into a model."""

    def __init__(self, model_name: str, detail: str):
        self.model_name = model_name
        self.detail = detail
        super().__init__(f"{model_name}: {detail}")


def _require_str(d: Dict[str, Any], key: str, model: str) -> str:
    """Extract a required string field, raising ModelParseError on failure."""
    if key not in d:
        raise ModelParseError(model, f"missing required field '{key}'")
    val = d[key]
    if not isinstance(val, str):
        raise ModelParseError(model, f"field '{key}' must be a string, got {type(val).__name__}")
    return val


def _require_int(d: Dict[str, Any], key: str, model: str) -> int:
    """Extract a required integer field, raising ModelParseError on failure."""
    if key not in d:
        raise ModelParseError(model, f"missing required field '{key}'")
    val = d[key]
    if not isinstance(val, int) or isinstance(val, bool):
        raise ModelParseError(model, f"field '{key}' must be an integer, got {type(val).__name__}")
    return val


def _require_float(d: Dict[str, Any], key: str, model: str) -> float:
    """Extract a required float field, raising ModelParseError on failure."""
    if key not in d:
        raise ModelParseError(model, f"missing required field '{key}'")
    val = d[key]
    if isinstance(val, bool) or not isinstance(val, (int, float)):
        raise ModelParseError(model, f"field '{key}' must be a number, got {type(val).__name__}")
    fval = float(val)
    if math.isnan(fval) or math.isinf(fval):
        raise ModelParseError(model, f"field '{key}' must be a finite number")
    return fval


def _require_bool(d: Dict[str, Any], key: str, model: str) -> bool:
    """Extract a required boolean field, raising ModelParseError on failure."""
    if key not in d:
        raise ModelParseError(model, f"missing required field '{key}'")
    val = d[key]
    if not isinstance(val, bool):
        raise ModelParseError(model, f"field '{key}' must be a boolean, got {type(val).__name__}")
    return val


def _require_dict(d: Dict[str, Any], key: str, model: str) -> Dict[str, Any]:
    """Extract a required dict field, raising ModelParseError on failure."""
    if key not in d:
        raise ModelParseError(model, f"missing required field '{key}'")
    val = d[key]
    if not isinstance(val, dict):
        raise ModelParseError(model, f"field '{key}' must be a dict, got {type(val).__name__}")
    return val


def _require_list(d: Dict[str, Any], key: str, model: str) -> List[Any]:
    """Extract a required list field, raising ModelParseError on failure."""
    if key not in d:
        raise ModelParseError(model, f"missing required field '{key}'")
    val = d[key]
    if not isinstance(val, list):
        raise ModelParseError(model, f"field '{key}' must be a list, got {type(val).__name__}")
    return val


@dataclass
class VerifyResponse:
    decision: Decision
    verification_id: str
    reason: str
    epoch: int
    provenance_hash: str
    timestamp_utc: str
    release_token: Optional[str] = None
    applied_action: Optional[Dict[str, Any]] = None
    policy_applied: Optional[Dict[str, Any]] = None
    hash_prev: Optional[str] = None

    @classmethod
    def from_dict(cls, d: Dict[str, Any]) -> VerifyResponse:
        if not isinstance(d, dict):
            raise ModelParseError("VerifyResponse", "expected a dict")
        raw_decision = _require_str(d, "decision", "VerifyResponse")
        try:
            decision = Decision(raw_decision)
        except ValueError:
            raise ModelParseError(
                "VerifyResponse",
                f"invalid decision value '{raw_decision}'",
            )
        return cls(
            decision=decision,
            verification_id=_require_str(d, "verification_id", "VerifyResponse"),
            release_token=d.get("release_token"),
            applied_action=d.get("applied_action"),
            policy_applied=d.get("policy_applied"),
            reason=d.get("reason", ""),
            epoch=_require_int(d, "epoch", "VerifyResponse"),
            provenance_hash=_require_str(d, "provenance_hash", "VerifyResponse"),
            timestamp_utc=_require_str(d, "timestamp_utc", "VerifyResponse"),
            hash_prev=d.get("hash_prev"),
        )


@dataclass
class AttestResponse:
    attestation_id: str
    epoch: int
    hash_prev: str
    hash_curr: str
    timestamp_utc: str
    chain_length: int
    pqc_block_ref: Optional[str] = None

    @classmethod
    def from_dict(cls, d: Dict[str, Any]) -> AttestResponse:
        if not isinstance(d, dict):
            raise ModelParseError("AttestResponse", "expected a dict")
        return cls(
            attestation_id=_require_str(d, "attestation_id", "AttestResponse"),
            epoch=_require_int(d, "epoch", "AttestResponse"),
            hash_prev=_require_str(d, "hash_prev", "AttestResponse"),
            hash_curr=_require_str(d, "hash_curr", "AttestResponse"),
            pqc_block_ref=d.get("pqc_block_ref"),
            timestamp_utc=_require_str(d, "timestamp_utc", "AttestResponse"),
            chain_length=_require_int(d, "chain_length", "AttestResponse"),
        )


@dataclass
class BindIntentResponse:
    intent_id: str
    intent_hash: str
    binding_id: str
    binding_hmac: str
    command_hash: str
    epoch: int
    timestamp_utc: str

    @classmethod
    def from_dict(cls, d: Dict[str, Any]) -> BindIntentResponse:
        if not isinstance(d, dict):
            raise ModelParseError("BindIntentResponse", "expected a dict")
        return cls(
            intent_id=_require_str(d, "intent_id", "BindIntentResponse"),
            intent_hash=_require_str(d, "intent_hash", "BindIntentResponse"),
            binding_id=_require_str(d, "binding_id", "BindIntentResponse"),
            binding_hmac=_require_str(d, "binding_hmac", "BindIntentResponse"),
            command_hash=_require_str(d, "command_hash", "BindIntentResponse"),
            epoch=_require_int(d, "epoch", "BindIntentResponse"),
            timestamp_utc=_require_str(d, "timestamp_utc", "BindIntentResponse"),
        )


@dataclass
class VerifyOutcomeResponse:
    verification_id: str
    intent_id: str
    status: OutcomeStatus
    achieved_percentage: float
    evidence_hash: str
    timestamp_utc: str
    discrepancy: Optional[Dict[str, Any]] = None

    @classmethod
    def from_dict(cls, d: Dict[str, Any]) -> VerifyOutcomeResponse:
        if not isinstance(d, dict):
            raise ModelParseError("VerifyOutcomeResponse", "expected a dict")
        raw_status = _require_str(d, "status", "VerifyOutcomeResponse")
        try:
            status = OutcomeStatus(raw_status)
        except ValueError:
            raise ModelParseError(
                "VerifyOutcomeResponse",
                f"invalid status value '{raw_status}'",
            )
        return cls(
            verification_id=_require_str(d, "verification_id", "VerifyOutcomeResponse"),
            intent_id=_require_str(d, "intent_id", "VerifyOutcomeResponse"),
            status=status,
            achieved_percentage=_require_float(d, "achieved_percentage", "VerifyOutcomeResponse"),
            discrepancy=d.get("discrepancy"),
            evidence_hash=_require_str(d, "evidence_hash", "VerifyOutcomeResponse"),
            timestamp_utc=_require_str(d, "timestamp_utc", "VerifyOutcomeResponse"),
        )


@dataclass
class BundleResponse:
    bundle_id: str
    agent_id: str
    record_count: int
    chain_integrity: bool
    time_range: Dict[str, str]
    records: List[Dict[str, Any]]
    bundle_hash: str
    timestamp_utc: str
    intent_chains: Optional[List[Dict[str, Any]]] = None
    pqc_signatures: Optional[List[Dict[str, Any]]] = None
    verification_instructions: Optional[str] = None

    @classmethod
    def from_dict(cls, d: Dict[str, Any]) -> BundleResponse:
        if not isinstance(d, dict):
            raise ModelParseError("BundleResponse", "expected a dict")
        return cls(
            bundle_id=_require_str(d, "bundle_id", "BundleResponse"),
            agent_id=_require_str(d, "agent_id", "BundleResponse"),
            record_count=_require_int(d, "record_count", "BundleResponse"),
            chain_integrity=_require_bool(d, "chain_integrity", "BundleResponse"),
            time_range=_require_dict(d, "time_range", "BundleResponse"),
            records=_require_list(d, "records", "BundleResponse"),
            intent_chains=d.get("intent_chains"),
            pqc_signatures=d.get("pqc_signatures"),
            verification_instructions=d.get("verification_instructions"),
            bundle_hash=_require_str(d, "bundle_hash", "BundleResponse"),
            timestamp_utc=_require_str(d, "timestamp_utc", "BundleResponse"),
        )


@dataclass
class GatewayHealth:
    service: str
    status: str

    @classmethod
    def from_dict(cls, d: Dict[str, Any]) -> GatewayHealth:
        if not isinstance(d, dict):
            raise ModelParseError("GatewayHealth", "expected a dict")
        return cls(
            service=_require_str(d, "service", "GatewayHealth"),
            status=_require_str(d, "status", "GatewayHealth"),
        )
