"""Unit tests for Novyx SDK client."""
import pytest
from unittest.mock import Mock, patch
from novyx import (
    Novyx,
    NovyxError,
    NovyxAuthError,
    NovyxForbiddenError,
    NovyxRateLimitError,
    NovyxNotFoundError,
)


@pytest.fixture
def nx():
    """Create Novyx client with test API key."""
    return Novyx(api_key="nram_test_12345678", api_url="https://test.example.com")


@pytest.fixture
def mock_response():
    """Create mock response object."""
    response = Mock()
    response.status_code = 200
    response.text = '{"success": true}'
    response.json.return_value = {"success": True}
    response.content = b"test content"
    return response


# ============================================================================
# Initialization & Auth Tests
# ============================================================================

def test_init_valid_key():
    """Test initialization with valid API key."""
    nx = Novyx(api_key="nram_tenant_signature")
    assert nx.api_key == "nram_tenant_signature"
    assert nx.api_url == "https://novyx-ram-api.fly.dev"


def test_init_invalid_key():
    """Test initialization with invalid API key format."""
    with pytest.raises(NovyxAuthError):
        Novyx(api_key="invalid_key")


def test_headers(nx):
    """Test request headers include auth token."""
    headers = nx._headers()
    assert headers["Authorization"] == "Bearer nram_test_12345678"
    assert headers["Content-Type"] == "application/json"


# ============================================================================
# Error Handling Tests
# ============================================================================

@patch('novyx.client.requests.request')
def test_error_401_raises_auth_error(mock_request, nx):
    """Test 401 response raises NovyxAuthError."""
    mock_response = Mock()
    mock_response.status_code = 401
    mock_request.return_value = mock_response

    with pytest.raises(NovyxAuthError):
        nx._request("GET", "/test")


@patch('novyx.client.requests.request')
def test_error_403_raises_forbidden_error(mock_request, nx):
    """Test 403 response raises NovyxForbiddenError."""
    mock_response = Mock()
    mock_response.status_code = 403
    mock_response.text = '{"error": "Pro tier required"}'
    mock_response.json.return_value = {"error": "Pro tier required"}
    mock_request.return_value = mock_response

    with pytest.raises(NovyxForbiddenError) as exc_info:
        nx._request("GET", "/test")

    assert "Pro tier required" in str(exc_info.value)


@patch('novyx.client.requests.request')
def test_error_404_raises_not_found_error(mock_request, nx):
    """Test 404 response raises NovyxNotFoundError."""
    mock_response = Mock()
    mock_response.status_code = 404
    mock_response.text = '{"error": "Not found"}'
    mock_response.json.return_value = {"error": "Not found"}
    mock_request.return_value = mock_response

    with pytest.raises(NovyxNotFoundError):
        nx._request("GET", "/test")


@patch('novyx.client.requests.request')
def test_error_429_raises_rate_limit_error(mock_request, nx):
    """Test 429 response raises NovyxRateLimitError."""
    mock_response = Mock()
    mock_response.status_code = 429
    mock_response.text = '{"error": "Rate limit exceeded", "current": 100, "limit": 100}'
    mock_response.json.return_value = {
        "error": "Rate limit exceeded",
        "current": 100,
        "limit": 100
    }
    mock_request.return_value = mock_response

    with pytest.raises(NovyxRateLimitError) as exc_info:
        nx._request("GET", "/test")

    assert exc_info.value.current == 100
    assert exc_info.value.limit == 100


# ============================================================================
# Memory Methods Tests
# ============================================================================

@patch('novyx.client.requests.request')
def test_remember(mock_request, nx, mock_response):
    """Test remember() stores a memory."""
    mock_response.json.return_value = {"uuid": "test-uuid", "message": "Stored"}
    mock_request.return_value = mock_response

    result = nx.remember("Test memory", tags=["test"], importance=7)

    assert result["uuid"] == "test-uuid"
    mock_request.assert_called_once()
    call_kwargs = mock_request.call_args[1]
    assert call_kwargs["json"]["observation"] == "Test memory"
    assert call_kwargs["json"]["tags"] == ["test"]
    assert call_kwargs["json"]["importance"] == 7


@patch('novyx.client.requests.request')
def test_remember_with_metadata(mock_request, nx, mock_response):
    """Test remember() with metadata dict."""
    mock_response.json.return_value = {"uuid": "test-uuid"}
    mock_request.return_value = mock_response

    result = nx.remember("Test", metadata={"key": "value"})

    call_kwargs = mock_request.call_args[1]
    assert '{"key": "value"}' in call_kwargs["json"]["context"]


@patch('novyx.client.requests.request')
def test_recall(mock_request, nx, mock_response):
    """Test recall() searches memories."""
    mock_response.json.return_value = [
        {"observation": "Test memory", "score": 0.95, "uuid": "test-uuid"}
    ]
    mock_request.return_value = mock_response

    result = nx.recall("test query", limit=5)

    assert len(result) == 1
    assert result.memories[0].observation == "Test memory"
    assert result.query == "test query"
    mock_request.assert_called_once()


@patch('novyx.client.requests.request')
def test_memories(mock_request, nx, mock_response):
    """Test memories() lists all memories."""
    mock_response.json.return_value = {
        "memories": [
            {"uuid": "test-1", "observation": "Memory 1"},
            {"uuid": "test-2", "observation": "Memory 2"}
        ]
    }
    mock_request.return_value = mock_response

    memories = nx.memories(limit=100)

    assert len(memories) == 2
    assert memories[0]["uuid"] == "test-1"


@patch('novyx.client.requests.request')
def test_memory(mock_request, nx, mock_response):
    """Test memory() gets specific memory by ID."""
    mock_response.json.return_value = {"uuid": "test-uuid", "observation": "Test"}
    mock_request.return_value = mock_response

    memory = nx.memory("test-uuid")

    assert memory["uuid"] == "test-uuid"
    assert "/v1/memories/test-uuid" in mock_request.call_args.kwargs["url"]


@patch('novyx.client.requests.request')
def test_forget(mock_request, nx, mock_response):
    """Test forget() deletes a memory."""
    mock_response.json.return_value = {"success": True}
    mock_request.return_value = mock_response

    result = nx.forget("test-uuid")

    assert result is True
    assert mock_request.call_args.kwargs["method"] == "DELETE"


@patch('novyx.client.requests.request')
def test_stats(mock_request, nx, mock_response):
    """Test stats() returns memory statistics."""
    mock_response.json.return_value = {"total_memories": 42, "avg_importance": 5.5}
    mock_request.return_value = mock_response

    stats = nx.stats()

    assert stats["total_memories"] == 42
    assert "/v1/memories/stats" in mock_request.call_args.kwargs["url"]


# ============================================================================
# Rollback Methods Tests
# ============================================================================

@patch('novyx.client.requests.request')
def test_rollback(mock_request, nx, mock_response):
    """Test rollback() executes rollback."""
    mock_response.json.return_value = {
        "success": True,
        "rolled_back_to": "2026-01-15T10:00:00Z",
        "artifacts_restored": 5,
        "operations_undone": 10
    }
    mock_request.return_value = mock_response

    result = nx.rollback("2 hours ago")

    assert result["success"] is True
    assert result["artifacts_restored"] == 5
    assert "/v1/rollback" in mock_request.call_args.kwargs["url"]
    assert mock_request.call_args.kwargs["json"]["target"] == "2 hours ago"


@patch('novyx.client.requests.request')
def test_rollback_preview(mock_request, nx, mock_response):
    """Test rollback_preview() shows preview."""
    mock_response.json.return_value = {
        "target_timestamp": "2026-01-15T10:00:00Z",
        "artifacts_modified": 3,
        "safe_rollback": True
    }
    mock_request.return_value = mock_response

    preview = nx.rollback_preview("1 hour ago")

    assert preview["artifacts_modified"] == 3
    assert "/v1/rollback/preview" in mock_request.call_args.kwargs["url"]


@patch('novyx.client.requests.request')
def test_rollback_history(mock_request, nx, mock_response):
    """Test rollback_history() lists past rollbacks."""
    mock_response.json.return_value = [
        {"rollback_id": "rb-1", "executed_at": "2026-01-15T10:00:00Z"}
    ]
    mock_request.return_value = mock_response

    history = nx.rollback_history(limit=10)

    assert len(history) == 1
    assert history[0]["rollback_id"] == "rb-1"


# ============================================================================
# Audit Methods Tests
# ============================================================================

@patch('novyx.client.requests.request')
def test_audit(mock_request, nx, mock_response):
    """Test audit() retrieves audit entries."""
    mock_response.json.return_value = {
        "entries": [
            {"timestamp": "2026-01-15T10:00:00Z", "operation": "CREATE"}
        ]
    }
    mock_request.return_value = mock_response

    audit = nx.audit(limit=50, operation="CREATE")

    assert len(audit) == 1
    assert audit[0]["operation"] == "CREATE"
    assert "/v1/audit" in mock_request.call_args.kwargs["url"]


@patch('novyx.client.requests.request')
def test_audit_export(mock_request, nx, mock_response):
    """Test audit_export() exports audit log."""
    mock_response.content = b"timestamp,operation\n2026-01-15,CREATE"
    mock_request.return_value = mock_response

    data = nx.audit_export(format="csv")

    assert b"timestamp" in data
    assert mock_request.call_args[1]["params"]["format"] == "csv"


@patch('novyx.client.requests.request')
def test_audit_verify(mock_request, nx, mock_response):
    """Test audit_verify() verifies integrity."""
    mock_response.json.return_value = {"valid": True, "errors": []}
    mock_request.return_value = mock_response

    verification = nx.audit_verify()

    assert verification["valid"] is True
    assert "/v1/audit/verify" in mock_request.call_args.kwargs["url"]


# ============================================================================
# Trace Methods Tests
# ============================================================================

@patch('novyx.client.requests.request')
def test_trace_create(mock_request, nx, mock_response):
    """Test trace_create() creates trace session."""
    mock_response.json.return_value = {
        "trace_id": "trace-123",
        "agent_id": "my-agent",
        "created_at": "2026-01-15T10:00:00Z"
    }
    mock_request.return_value = mock_response

    trace = nx.trace_create("my-agent", session_id="session-123")

    assert trace["trace_id"] == "trace-123"
    assert mock_request.call_args.kwargs["json"]["agent_id"] == "my-agent"
    assert "/v1/traces" in mock_request.call_args.kwargs["url"]


@patch('novyx.client.requests.request')
def test_trace_step(mock_request, nx, mock_response):
    """Test trace_step() adds step to trace."""
    mock_response.json.return_value = {
        "step_id": "step-1",
        "circuit_breaker_triggered": False
    }
    mock_request.return_value = mock_response

    result = nx.trace_step("trace-123", "action", "send_email", attributes={"to": "test@example.com"})

    assert result["circuit_breaker_triggered"] is False
    assert "/v1/traces/trace-123/steps" in mock_request.call_args.kwargs["url"]


@patch('novyx.client.requests.request')
def test_trace_complete(mock_request, nx, mock_response):
    """Test trace_complete() finalizes trace."""
    mock_response.json.return_value = {
        "trace_id": "trace-123",
        "status": "completed",
        "signature": "abc123",
        "total_steps": 5
    }
    mock_request.return_value = mock_response

    result = nx.trace_complete("trace-123")

    assert result["status"] == "completed"
    assert result["total_steps"] == 5
    assert "/v1/traces/trace-123/complete" in mock_request.call_args.kwargs["url"]


@patch('novyx.client.requests.request')
def test_trace_verify(mock_request, nx, mock_response):
    """Test trace_verify() verifies trace integrity."""
    mock_response.json.return_value = {
        "trace_id": "trace-123",
        "valid": True,
        "steps_verified": 5
    }
    mock_request.return_value = mock_response

    verification = nx.trace_verify("trace-123")

    assert verification["valid"] is True
    assert "/v1/traces/trace-123/verify" in mock_request.call_args.kwargs["url"]


# ============================================================================
# Usage & Plans Tests
# ============================================================================

@patch('novyx.client.requests.request')
def test_usage(mock_request, nx, mock_response):
    """Test usage() returns usage stats."""
    mock_response.json.return_value = {
        "tier": "pro",
        "api_calls": {"current": 100, "limit": 100_000, "unlimited": False},
        "memories": {"current": 50, "limit": None, "unlimited": True}
    }
    mock_request.return_value = mock_response

    usage = nx.usage()

    assert usage["tier"] == "pro"
    assert usage["api_calls"]["current"] == 100
    assert usage["memories"]["unlimited"] is True


@patch('novyx.client.requests.request')
def test_plans(mock_request, nx, mock_response):
    """Test plans() returns available plans."""
    mock_response.json.return_value = [
        {
            "plan_id": "free",
            "name": "Free",
            "price_cents": 0,
            "price_display": "Free",
            "memory_limit": 5_000
        },
        {
            "plan_id": "pro",
            "name": "Pro",
            "price_cents": 3900,
            "price_display": "$39/mo",
            "memory_limit": None
        }
    ]
    mock_request.return_value = mock_response

    plans = nx.plans()

    assert len(plans) == 2
    assert plans[0]["plan_id"] == "free"
    assert plans[1]["memory_limit"] is None


# ============================================================================
# Health Check Test
# ============================================================================

@patch('novyx.client.requests.get')
def test_health(mock_get, nx):
    """Test health() checks API status."""
    mock_response = Mock()
    mock_response.json.return_value = {"status": "healthy", "version": "1.0.0"}
    mock_get.return_value = mock_response

    health = nx.health()

    assert health["status"] == "healthy"
    mock_get.assert_called_once()


# ============================================================================
# Integration-style Test
# ============================================================================

@patch('novyx.client.requests.request')
def test_full_workflow(mock_request, nx, mock_response):
    """Test a full workflow: remember, recall, audit, rollback."""
    # Mock remember
    mock_response.json.return_value = {"uuid": "mem-123"}
    mock_request.return_value = mock_response

    mem_result = nx.remember("Test memory")
    assert mem_result["uuid"] == "mem-123"

    # Mock recall
    mock_response.json.return_value = [{"uuid": "mem-123", "score": 0.95}]
    memories = nx.recall("test")
    assert len(memories) == 1

    # Mock audit
    mock_response.json.return_value = {"entries": [{"operation": "CREATE"}]}
    audit = nx.audit()
    assert len(audit) == 1

    # Mock rollback
    mock_response.json.return_value = {"success": True, "artifacts_restored": 1}
    rb_result = nx.rollback("1 hour ago")
    assert rb_result["success"] is True
