"""
Tests for arp_sdk.models module.

Covers Pydantic model validation from camelCase and snake_case data,
enum values, generic PaginatedResponse, and all key domain models.
"""

import pytest
from datetime import datetime, timezone

from arp_sdk.models import (
    Agent,
    AgentStatus,
    DomainReputation,
    Guild,
    PaginatedResponse,
    PaginationInfo,
    ReputationScore,
    StakeInfo,
    Transaction,
    TransactionStatus,
    Webhook,
    WebhookEvent,
)


# ──────────────────────────────────────────────
# Shared fixtures
# ──────────────────────────────────────────────

TIMESTAMP_STR = "2025-06-15T10:30:00Z"
TIMESTAMP_DT = datetime(2025, 6, 15, 10, 30, 0, tzinfo=timezone.utc)


def _agent_camel_case_data() -> dict:
    return {
        "id": "agent-001",
        "address": "0xABCDEF1234567890",
        "name": "TestAgent",
        "description": "A test agent",
        "metadata": {"version": "1.0"},
        "status": "active",
        "domains": ["defi", "nft"],
        "createdAt": TIMESTAMP_STR,
        "updatedAt": TIMESTAMP_STR,
    }


def _agent_snake_case_data() -> dict:
    return {
        "id": "agent-002",
        "address": "0x1234567890ABCDEF",
        "name": "SnakeAgent",
        "description": "A snake_case agent",
        "metadata": {},
        "status": "suspended",
        "domains": [],
        "created_at": TIMESTAMP_STR,
        "updated_at": TIMESTAMP_STR,
    }


# ──────────────────────────────────────────────
# Agent model
# ──────────────────────────────────────────────


class TestAgentModel:
    """Test Agent model validation and field mapping."""

    def test_validate_from_camel_case(self):
        agent = Agent.model_validate(_agent_camel_case_data())
        assert agent.id == "agent-001"
        assert agent.name == "TestAgent"
        assert agent.address == "0xABCDEF1234567890"
        assert agent.description == "A test agent"
        assert agent.metadata == {"version": "1.0"}
        assert agent.status == AgentStatus.ACTIVE
        assert agent.domains == ["defi", "nft"]
        assert agent.created_at == TIMESTAMP_DT
        assert agent.updated_at == TIMESTAMP_DT

    def test_validate_from_snake_case(self):
        agent = Agent.model_validate(_agent_snake_case_data())
        assert agent.id == "agent-002"
        assert agent.name == "SnakeAgent"
        assert agent.status == AgentStatus.SUSPENDED
        assert agent.domains == []
        assert agent.created_at == TIMESTAMP_DT

    def test_metadata_defaults_to_empty_dict(self):
        data = _agent_camel_case_data()
        del data["metadata"]
        agent = Agent.model_validate(data)
        assert agent.metadata == {}

    def test_domains_defaults_to_empty_list(self):
        data = _agent_camel_case_data()
        del data["domains"]
        agent = Agent.model_validate(data)
        assert agent.domains == []

    def test_invalid_status_raises(self):
        data = _agent_camel_case_data()
        data["status"] = "nonexistent_status"
        with pytest.raises(Exception):
            Agent.model_validate(data)


# ──────────────────────────────────────────────
# Enum values
# ──────────────────────────────────────────────


class TestEnums:
    """Test that enum members have expected string values."""

    def test_agent_status_values(self):
        assert AgentStatus.ACTIVE == "active"
        assert AgentStatus.SUSPENDED == "suspended"
        assert AgentStatus.DEREGISTERED == "deregistered"

    def test_agent_status_is_str(self):
        assert isinstance(AgentStatus.ACTIVE, str)

    def test_transaction_status_values(self):
        assert TransactionStatus.PENDING == "pending"
        assert TransactionStatus.ACCEPTED == "accepted"
        assert TransactionStatus.COMPLETED == "completed"
        assert TransactionStatus.DISPUTED == "disputed"
        assert TransactionStatus.CANCELLED == "cancelled"

    def test_transaction_status_is_str(self):
        assert isinstance(TransactionStatus.PENDING, str)

    def test_webhook_event_values(self):
        assert WebhookEvent.TRANSACTION_CREATED == "transaction.created"
        assert WebhookEvent.REPUTATION_UPDATED == "reputation.updated"
        assert WebhookEvent.AGENT_REGISTERED == "agent.registered"
        assert WebhookEvent.STAKING_DEPOSITED == "staking.deposited"


# ──────────────────────────────────────────────
# PaginationInfo
# ──────────────────────────────────────────────


class TestPaginationInfo:
    """Test PaginationInfo model with camelCase alias."""

    def test_from_camel_case(self):
        data = {"page": 1, "limit": 25, "total": 100, "totalPages": 4}
        info = PaginationInfo.model_validate(data)
        assert info.page == 1
        assert info.limit == 25
        assert info.total == 100
        assert info.total_pages == 4

    def test_from_snake_case(self):
        data = {"page": 2, "limit": 10, "total": 50, "total_pages": 5}
        info = PaginationInfo.model_validate(data)
        assert info.total_pages == 5


# ──────────────────────────────────────────────
# PaginatedResponse
# ──────────────────────────────────────────────


class TestPaginatedResponse:
    """Test the generic PaginatedResponse[T] type."""

    def test_paginated_response_with_agents(self):
        pagination_data = {"page": 1, "limit": 10, "total": 2, "totalPages": 1}
        agents = [
            Agent.model_validate(_agent_camel_case_data()),
        ]
        pagination = PaginationInfo.model_validate(pagination_data)
        response = PaginatedResponse[Agent](data=agents, pagination=pagination)

        assert len(response.data) == 1
        assert response.data[0].id == "agent-001"
        assert response.pagination.page == 1
        assert response.pagination.total == 2

    def test_paginated_response_empty(self):
        pagination_data = {"page": 1, "limit": 10, "total": 0, "totalPages": 0}
        pagination = PaginationInfo.model_validate(pagination_data)
        response = PaginatedResponse[Agent](data=[], pagination=pagination)

        assert response.data == []
        assert response.pagination.total == 0


# ──────────────────────────────────────────────
# ReputationScore with nested DomainReputation
# ──────────────────────────────────────────────


class TestReputationScore:
    """Test ReputationScore model including nested DomainReputation."""

    def _reputation_data(self) -> dict:
        return {
            "agentId": "agent-001",
            "overall": 0.85,
            "domains": {
                "defi": {
                    "domain": "defi",
                    "score": 0.9,
                    "transactionCount": 42,
                    "successRate": 0.95,
                    "averageRating": 4.5,
                },
                "nft": {
                    "domain": "nft",
                    "score": 0.7,
                    "transactionCount": 10,
                    "successRate": 0.8,
                    "averageRating": 3.8,
                },
            },
            "totalTransactions": 52,
            "successRate": 0.92,
            "lastUpdated": TIMESTAMP_STR,
        }

    def test_validate_reputation_score(self):
        rep = ReputationScore.model_validate(self._reputation_data())
        assert rep.agent_id == "agent-001"
        assert rep.overall == 0.85
        assert rep.total_transactions == 52
        assert rep.success_rate == 0.92
        assert rep.last_updated == TIMESTAMP_DT

    def test_nested_domain_reputation(self):
        rep = ReputationScore.model_validate(self._reputation_data())
        assert "defi" in rep.domains
        defi = rep.domains["defi"]
        assert isinstance(defi, DomainReputation)
        assert defi.domain == "defi"
        assert defi.score == 0.9
        assert defi.transaction_count == 42
        assert defi.success_rate == 0.95
        assert defi.average_rating == 4.5

    def test_domains_default_empty(self):
        data = self._reputation_data()
        del data["domains"]
        rep = ReputationScore.model_validate(data)
        assert rep.domains == {}


# ──────────────────────────────────────────────
# DomainReputation standalone
# ──────────────────────────────────────────────


class TestDomainReputation:
    """Test DomainReputation model validation."""

    def test_from_camel_case(self):
        data = {
            "domain": "defi",
            "score": 0.88,
            "transactionCount": 100,
            "successRate": 0.95,
            "averageRating": 4.2,
        }
        dr = DomainReputation.model_validate(data)
        assert dr.domain == "defi"
        assert dr.score == 0.88
        assert dr.transaction_count == 100
        assert dr.success_rate == 0.95
        assert dr.average_rating == 4.2

    def test_from_snake_case(self):
        data = {
            "domain": "nft",
            "score": 0.75,
            "transaction_count": 50,
            "success_rate": 0.80,
            "average_rating": 3.9,
        }
        dr = DomainReputation.model_validate(data)
        assert dr.transaction_count == 50
        assert dr.success_rate == 0.80


# ──────────────────────────────────────────────
# Transaction model
# ──────────────────────────────────────────────


class TestTransaction:
    """Test Transaction model with required and optional fields."""

    def _transaction_data(self, **overrides) -> dict:
        base = {
            "id": "tx-001",
            "fromAgentId": "agent-a",
            "toAgentId": "agent-b",
            "domain": "defi",
            "amount": "1000000000000000000",
            "description": "Test transaction",
            "metadata": {"key": "value"},
            "status": "pending",
            "rating": None,
            "feedback": None,
            "completedAt": None,
            "disputeId": None,
            "createdAt": TIMESTAMP_STR,
            "updatedAt": TIMESTAMP_STR,
        }
        base.update(overrides)
        return base

    def test_validate_pending_transaction(self):
        tx = Transaction.model_validate(self._transaction_data())
        assert tx.id == "tx-001"
        assert tx.from_agent_id == "agent-a"
        assert tx.to_agent_id == "agent-b"
        assert tx.domain == "defi"
        assert tx.amount == "1000000000000000000"
        assert tx.status == TransactionStatus.PENDING
        assert tx.rating is None
        assert tx.feedback is None
        assert tx.completed_at is None
        assert tx.dispute_id is None

    def test_validate_completed_transaction(self):
        tx = Transaction.model_validate(
            self._transaction_data(
                status="completed",
                rating=5,
                feedback="Great service",
                completedAt=TIMESTAMP_STR,
            )
        )
        assert tx.status == TransactionStatus.COMPLETED
        assert tx.rating == 5
        assert tx.feedback == "Great service"
        assert tx.completed_at == TIMESTAMP_DT

    def test_validate_disputed_transaction(self):
        tx = Transaction.model_validate(
            self._transaction_data(
                status="disputed",
                disputeId="dispute-001",
            )
        )
        assert tx.status == TransactionStatus.DISPUTED
        assert tx.dispute_id == "dispute-001"

    def test_metadata_defaults_to_empty_dict(self):
        data = self._transaction_data()
        del data["metadata"]
        tx = Transaction.model_validate(data)
        assert tx.metadata == {}


# ──────────────────────────────────────────────
# StakeInfo model
# ──────────────────────────────────────────────


class TestStakeInfo:
    """Test StakeInfo model with optional timestamp fields."""

    def test_validate_full_stake(self):
        data = {
            "agentId": "agent-001",
            "amount": "5000000000000000000",
            "lockedUntil": TIMESTAMP_STR,
            "lastDepositAt": TIMESTAMP_STR,
            "lastWithdrawAt": None,
        }
        stake = StakeInfo.model_validate(data)
        assert stake.agent_id == "agent-001"
        assert stake.amount == "5000000000000000000"
        assert stake.locked_until == TIMESTAMP_DT
        assert stake.last_deposit_at == TIMESTAMP_DT
        assert stake.last_withdraw_at is None

    def test_validate_minimal_stake(self):
        data = {
            "agentId": "agent-002",
            "amount": "0",
        }
        stake = StakeInfo.model_validate(data)
        assert stake.agent_id == "agent-002"
        assert stake.amount == "0"
        assert stake.locked_until is None
        assert stake.last_deposit_at is None
        assert stake.last_withdraw_at is None

    def test_from_snake_case(self):
        data = {
            "agent_id": "agent-003",
            "amount": "100",
            "locked_until": TIMESTAMP_STR,
            "last_deposit_at": TIMESTAMP_STR,
            "last_withdraw_at": TIMESTAMP_STR,
        }
        stake = StakeInfo.model_validate(data)
        assert stake.agent_id == "agent-003"
        assert stake.locked_until == TIMESTAMP_DT


# ──────────────────────────────────────────────
# Guild model
# ──────────────────────────────────────────────


class TestGuild:
    """Test Guild model validation."""

    def _guild_data(self) -> dict:
        return {
            "id": "guild-001",
            "name": "DeFi Masters",
            "description": "Top DeFi agents",
            "domain": "defi",
            "ownerAgentId": "agent-001",
            "memberCount": 42,
            "metadata": {"tier": "gold"},
            "createdAt": TIMESTAMP_STR,
            "updatedAt": TIMESTAMP_STR,
        }

    def test_validate_guild(self):
        guild = Guild.model_validate(self._guild_data())
        assert guild.id == "guild-001"
        assert guild.name == "DeFi Masters"
        assert guild.description == "Top DeFi agents"
        assert guild.domain == "defi"
        assert guild.owner_agent_id == "agent-001"
        assert guild.member_count == 42
        assert guild.metadata == {"tier": "gold"}
        assert guild.created_at == TIMESTAMP_DT
        assert guild.updated_at == TIMESTAMP_DT

    def test_metadata_defaults_to_empty_dict(self):
        data = self._guild_data()
        del data["metadata"]
        guild = Guild.model_validate(data)
        assert guild.metadata == {}


# ──────────────────────────────────────────────
# Webhook model
# ──────────────────────────────────────────────


class TestWebhook:
    """Test Webhook model validation."""

    def test_validate_webhook(self):
        data = {
            "id": "wh-001",
            "url": "https://example.com/webhook",
            "events": ["transaction.created", "reputation.updated"],
            "secret": "whsec_abc123",
            "active": True,
            "createdAt": TIMESTAMP_STR,
            "updatedAt": TIMESTAMP_STR,
        }
        wh = Webhook.model_validate(data)
        assert wh.id == "wh-001"
        assert wh.url == "https://example.com/webhook"
        assert wh.events == [
            WebhookEvent.TRANSACTION_CREATED,
            WebhookEvent.REPUTATION_UPDATED,
        ]
        assert wh.secret == "whsec_abc123"
        assert wh.active is True
        assert wh.created_at == TIMESTAMP_DT

    def test_webhook_inactive(self):
        data = {
            "id": "wh-002",
            "url": "https://example.com/hook2",
            "events": ["agent.registered"],
            "secret": "whsec_xyz",
            "active": False,
            "createdAt": TIMESTAMP_STR,
            "updatedAt": TIMESTAMP_STR,
        }
        wh = Webhook.model_validate(data)
        assert wh.active is False
        assert wh.events == [WebhookEvent.AGENT_REGISTERED]
