"""Tests for safety framework models."""

from datetime import datetime, timedelta

from app.services.safety.models import (
    ActionMetadata,
    ActionRequest,
    ActionResult,
    ActionType,
    ApprovalRequest,
    ApprovalResponse,
    ApprovalStatus,
    AuditEvent,
    AuditEventType,
    AutonomyLevel,
    BudgetScope,
    BudgetStatus,
    Checkpoint,
    CheckpointType,
    GuardianResult,
    PermissionLevel,
    RateLimitConfig,
    RollbackResult,
    SafetyDecision,
    SafetyPolicy,
    ValidationResult,
    ValidationRule,
)


class TestActionMetadata:
    """Tests for ActionMetadata model."""

    def test_create_with_defaults(self) -> None:
        """Test creating metadata with default values."""
        metadata = ActionMetadata(
            agent_id="agent-1",
        )

        assert metadata.agent_id == "agent-1"
        assert metadata.autonomy_level == AutonomyLevel.MILESTONE
        assert metadata.project_id is None
        assert metadata.session_id is None

    def test_create_with_all_fields(self) -> None:
        """Test creating metadata with all fields."""
        metadata = ActionMetadata(
            agent_id="agent-1",
            session_id="session-1",
            project_id="project-1",
            user_id="user-1",
            autonomy_level=AutonomyLevel.AUTONOMOUS,
        )

        assert metadata.project_id == "project-1"
        assert metadata.user_id == "user-1"
        assert metadata.autonomy_level == AutonomyLevel.AUTONOMOUS


class TestActionRequest:
    """Tests for ActionRequest model."""

    def test_create_basic_action(self) -> None:
        """Test creating a basic action request."""
        metadata = ActionMetadata(agent_id="agent-1", session_id="session-1")
        action = ActionRequest(
            action_type=ActionType.FILE_READ,
            tool_name="file_read",
            metadata=metadata,
        )

        assert action.action_type == ActionType.FILE_READ
        assert action.tool_name == "file_read"
        assert action.id is not None
        assert action.metadata.agent_id == "agent-1"

    def test_action_with_arguments(self) -> None:
        """Test action with arguments."""
        test_path = "/tmp/test.txt"  # noqa: S108
        metadata = ActionMetadata(agent_id="agent-1", session_id="session-1")
        action = ActionRequest(
            action_type=ActionType.FILE_WRITE,
            tool_name="file_write",
            arguments={"path": test_path, "content": "hello"},
            resource=test_path,
            metadata=metadata,
        )

        assert action.arguments["path"] == test_path
        assert action.resource == test_path


class TestActionResult:
    """Tests for ActionResult model."""

    def test_successful_result(self) -> None:
        """Test creating a successful result."""
        result = ActionResult(
            action_id="action-1",
            success=True,
            data={"output": "done"},
        )

        assert result.success is True
        assert result.data["output"] == "done"
        assert result.error is None

    def test_failed_result(self) -> None:
        """Test creating a failed result."""
        result = ActionResult(
            action_id="action-1",
            success=False,
            error="Permission denied",
        )

        assert result.success is False
        assert result.error == "Permission denied"


class TestValidationRule:
    """Tests for ValidationRule model."""

    def test_create_rule(self) -> None:
        """Test creating a validation rule."""
        rule = ValidationRule(
            name="deny_shell",
            description="Deny shell commands",
            priority=100,
            tool_patterns=["shell_*"],
            decision=SafetyDecision.DENY,
            reason="Shell commands are not allowed",
        )

        assert rule.name == "deny_shell"
        assert rule.priority == 100
        assert rule.decision == SafetyDecision.DENY
        assert rule.enabled is True

    def test_rule_defaults(self) -> None:
        """Test rule default values."""
        rule = ValidationRule(name="test_rule", decision=SafetyDecision.ALLOW)

        assert rule.id is not None
        assert rule.priority == 0
        assert rule.enabled is True
        assert rule.decision == SafetyDecision.ALLOW


class TestValidationResult:
    """Tests for ValidationResult model."""

    def test_allow_result(self) -> None:
        """Test an allow result."""
        result = ValidationResult(
            action_id="action-1",
            decision=SafetyDecision.ALLOW,
            applied_rules=["rule-1"],
            reasons=["Action is allowed"],
        )

        assert result.decision == SafetyDecision.ALLOW
        assert len(result.applied_rules) == 1

    def test_deny_result(self) -> None:
        """Test a deny result."""
        result = ValidationResult(
            action_id="action-1",
            decision=SafetyDecision.DENY,
            applied_rules=["deny_rule"],
            reasons=["Action is not permitted"],
        )

        assert result.decision == SafetyDecision.DENY


class TestBudgetStatus:
    """Tests for BudgetStatus model."""

    def test_under_budget(self) -> None:
        """Test status when under budget."""
        status = BudgetStatus(
            scope=BudgetScope.SESSION,
            scope_id="session-1",
            tokens_used=5000,
            tokens_limit=10000,
            tokens_remaining=5000,
        )

        assert status.tokens_remaining == 5000
        assert status.tokens_used == 5000

    def test_over_budget(self) -> None:
        """Test status when over budget."""
        status = BudgetStatus(
            scope=BudgetScope.SESSION,
            scope_id="session-1",
            cost_used_usd=15.0,
            cost_limit_usd=10.0,
            cost_remaining_usd=0.0,
            is_exceeded=True,
        )

        assert status.is_exceeded is True
        assert status.cost_remaining_usd == 0.0


class TestRateLimitConfig:
    """Tests for RateLimitConfig model."""

    def test_create_config(self) -> None:
        """Test creating rate limit config."""
        config = RateLimitConfig(
            name="actions",
            limit=60,
            window_seconds=60,
        )

        assert config.name == "actions"
        assert config.limit == 60
        assert config.window_seconds == 60


class TestApprovalRequest:
    """Tests for ApprovalRequest model."""

    def test_create_request(self) -> None:
        """Test creating an approval request."""
        metadata = ActionMetadata(agent_id="agent-1", session_id="session-1")
        action = ActionRequest(
            action_type=ActionType.DATABASE_MUTATE,
            tool_name="db_delete",
            metadata=metadata,
        )

        request = ApprovalRequest(
            id="approval-1",
            action=action,
            reason="Database mutation requires approval",
            urgency="high",
            timeout_seconds=300,
        )

        assert request.id == "approval-1"
        assert request.urgency == "high"
        assert request.timeout_seconds == 300


class TestApprovalResponse:
    """Tests for ApprovalResponse model."""

    def test_approved_response(self) -> None:
        """Test an approved response."""
        response = ApprovalResponse(
            request_id="approval-1",
            status=ApprovalStatus.APPROVED,
            decided_by="admin",
            reason="Looks safe",
        )

        assert response.status == ApprovalStatus.APPROVED
        assert response.decided_by == "admin"

    def test_denied_response(self) -> None:
        """Test a denied response."""
        response = ApprovalResponse(
            request_id="approval-1",
            status=ApprovalStatus.DENIED,
            decided_by="admin",
            reason="Too risky",
        )

        assert response.status == ApprovalStatus.DENIED


class TestCheckpoint:
    """Tests for Checkpoint model."""

    def test_create_checkpoint(self) -> None:
        """Test creating a checkpoint."""
        test_path = "/tmp/test.txt"  # noqa: S108
        checkpoint = Checkpoint(
            id="checkpoint-1",
            checkpoint_type=CheckpointType.FILE,
            action_id="action-1",
            created_at=datetime.utcnow(),
            data={"path": test_path},
            description="File checkpoint",
        )

        assert checkpoint.id == "checkpoint-1"
        assert checkpoint.is_valid is True

    def test_expired_checkpoint(self) -> None:
        """Test an expired checkpoint."""
        checkpoint = Checkpoint(
            id="checkpoint-1",
            checkpoint_type=CheckpointType.FILE,
            action_id="action-1",
            created_at=datetime.utcnow() - timedelta(hours=2),
            expires_at=datetime.utcnow() - timedelta(hours=1),
            data={},
        )

        # is_valid is a simple bool, not computed from expires_at
        # The RollbackManager handles expiration logic
        assert checkpoint.is_valid is True  # Default value


class TestRollbackResult:
    """Tests for RollbackResult model."""

    def test_successful_rollback(self) -> None:
        """Test a successful rollback."""
        result = RollbackResult(
            checkpoint_id="checkpoint-1",
            success=True,
            actions_rolled_back=["file:/tmp/test.txt"],
            failed_actions=[],
        )

        assert result.success is True
        assert len(result.actions_rolled_back) == 1

    def test_partial_rollback(self) -> None:
        """Test a partial rollback."""
        result = RollbackResult(
            checkpoint_id="checkpoint-1",
            success=False,
            actions_rolled_back=["file:/tmp/a.txt"],
            failed_actions=["file:/tmp/b.txt"],
            error="Failed to rollback 1 item",
        )

        assert result.success is False
        assert len(result.failed_actions) == 1


class TestAuditEvent:
    """Tests for AuditEvent model."""

    def test_create_event(self) -> None:
        """Test creating an audit event."""
        event = AuditEvent(
            id="event-1",
            event_type=AuditEventType.ACTION_EXECUTED,
            timestamp=datetime.utcnow(),
            agent_id="agent-1",
            action_id="action-1",
            data={"tool": "file_read"},
        )

        assert event.event_type == AuditEventType.ACTION_EXECUTED
        assert event.agent_id == "agent-1"


class TestSafetyPolicy:
    """Tests for SafetyPolicy model."""

    def test_default_policy(self) -> None:
        """Test creating a default policy."""
        policy = SafetyPolicy(
            name="default",
            description="Default safety policy",
        )

        assert policy.name == "default"
        assert policy.enabled is True

    def test_restrictive_policy(self) -> None:
        """Test creating a restrictive policy."""
        policy = SafetyPolicy(
            name="restrictive",
            description="Restrictive policy",
            denied_tools=["shell_*", "exec_*"],
            require_approval_for=["database_*", "git_push"],
        )

        assert len(policy.denied_tools) == 2
        assert len(policy.require_approval_for) == 2


class TestGuardianResult:
    """Tests for GuardianResult model."""

    def test_allowed_result(self) -> None:
        """Test an allowed result."""
        result = GuardianResult(
            action_id="action-1",
            allowed=True,
            decision=SafetyDecision.ALLOW,
            reasons=["All checks passed"],
        )

        assert result.decision == SafetyDecision.ALLOW
        assert result.allowed is True
        assert result.approval_id is None

    def test_approval_required_result(self) -> None:
        """Test a result requiring approval."""
        result = GuardianResult(
            action_id="action-1",
            allowed=False,
            decision=SafetyDecision.REQUIRE_APPROVAL,
            reasons=["Action requires human approval"],
            approval_id="approval-123",
        )

        assert result.decision == SafetyDecision.REQUIRE_APPROVAL
        assert result.approval_id == "approval-123"


class TestEnums:
    """Tests for enum values."""

    def test_action_types(self) -> None:
        """Test action type enum values."""
        assert ActionType.FILE_READ.value == "file_read"
        assert ActionType.SHELL_COMMAND.value == "shell_command"

    def test_autonomy_levels(self) -> None:
        """Test autonomy level enum values."""
        assert AutonomyLevel.FULL_CONTROL.value == "full_control"
        assert AutonomyLevel.MILESTONE.value == "milestone"
        assert AutonomyLevel.AUTONOMOUS.value == "autonomous"

    def test_permission_levels(self) -> None:
        """Test permission level enum values."""
        assert PermissionLevel.NONE.value == "none"
        assert PermissionLevel.READ.value == "read"
        assert PermissionLevel.WRITE.value == "write"
        assert PermissionLevel.ADMIN.value == "admin"

    def test_safety_decisions(self) -> None:
        """Test safety decision enum values."""
        assert SafetyDecision.ALLOW.value == "allow"
        assert SafetyDecision.DENY.value == "deny"
        assert SafetyDecision.REQUIRE_APPROVAL.value == "require_approval"
