"""Tests for resource modules — Audit, Compliance, Schemas, Assets, Keys, Webhooks.

Uses unittest.mock to patch the client's _request method so no real HTTP calls are made.
"""

from __future__ import annotations

import pytest
from unittest.mock import MagicMock, patch

from optropic import Optropic
from optropic.types import (
    ListAuditParams,
    ExportParams,
    CreateSchemaParams,
    UpdateSchemaParams,
    ListSchemasParams,
    CreateWebhookParams,
    CreateKeyParams,
    CreateAssetParams,
    ListAssetsParams,
)


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def client():
    """Create a client with a test key and mock the HTTP layer."""
    c = Optropic(api_key="optr_test_abcdefghijklmnopqrst")
    return c


@pytest.fixture
def mock_request(client):
    """Patch client._request so no real HTTP calls are made."""
    with patch.object(client, "_request") as m:
        yield m


# ---------------------------------------------------------------------------
# Audit
# ---------------------------------------------------------------------------


class TestAudit:
    def test_list_no_params(self, client, mock_request):
        mock_request.return_value = {"data": [], "total": 0}
        result = client.audit.list()
        mock_request.assert_called_once_with("GET", "/audit", params=None)
        assert result["data"] == []

    def test_list_with_params(self, client, mock_request):
        mock_request.return_value = {"data": []}
        params = ListAuditParams(event_type="asset.created", limit=10)
        client.audit.list(params)
        call_args = mock_request.call_args
        assert call_args[1]["params"]["event_type"] == "asset.created"
        assert call_args[1]["params"]["limit"] == 10

    def test_get(self, client, mock_request):
        mock_request.return_value = {"id": "evt-1", "event_type": "asset.created"}
        result = client.audit.get("evt-1")
        mock_request.assert_called_once_with("GET", "/audit/evt-1")
        assert result["id"] == "evt-1"

    def test_create(self, client, mock_request):
        mock_request.return_value = {"id": "evt-new", "event_type": "custom.event"}
        result = client.audit.create(
            event_type="custom.event",
            resource_id="asset-123",
            details={"action": "test"},
        )
        call_args = mock_request.call_args
        body = call_args[1]["json"]
        assert body["event_type"] == "custom.event"
        assert body["resource_id"] == "asset-123"
        assert body["details"]["action"] == "test"


# ---------------------------------------------------------------------------
# Compliance
# ---------------------------------------------------------------------------


class TestCompliance:
    def test_verify_chain(self, client, mock_request):
        mock_request.return_value = {
            "tenant_id": "t-1",
            "chain_valid": True,
            "events_checked": 42,
            "verified_at": "2026-03-27T10:00:00Z",
        }
        result = client.compliance.verify_chain()
        mock_request.assert_called_once_with("POST", "/compliance/verify-chain")
        assert result.chain_valid is True
        assert result.events_checked == 42

    def test_list_merkle_roots(self, client, mock_request):
        mock_request.return_value = [
            {
                "id": "mr-1",
                "root_hash": "abc123",
                "event_count": 100,
                "period_start": "2026-03-01",
                "period_end": "2026-03-31",
                "created_at": "2026-03-27T10:00:00Z",
            }
        ]
        result = client.compliance.list_merkle_roots()
        assert len(result) == 1
        assert result[0].root_hash == "abc123"

    def test_get_merkle_proof(self, client, mock_request):
        mock_request.return_value = {
            "event_id": "evt-1",
            "event_hash": "hash1",
            "merkle_root": "root1",
            "proof": ["h1", "h2"],
            "verified": True,
            "period": "2026-03",
        }
        result = client.compliance.get_merkle_proof("evt-1")
        assert result.verified is True
        assert len(result.proof) == 2

    def test_export_audit(self, client, mock_request):
        mock_request.return_value = {
            "filename": "audit.csv",
            "event_count": 50,
            "csv": "id,type,...",
        }
        result = client.compliance.export_audit(
            ExportParams(from_date="2026-03-01", to_date="2026-03-27")
        )
        assert result.event_count == 50

    def test_get_config(self, client, mock_request):
        mock_request.return_value = {
            "compliance_mode": "strict",
            "retention_policy": {"days": 365},
        }
        result = client.compliance.get_config()
        assert result.compliance_mode == "strict"

    def test_update_config(self, client, mock_request):
        mock_request.return_value = {
            "compliance_mode": "permissive",
            "retention_policy": {},
        }
        result = client.compliance.update_config("permissive")
        call_args = mock_request.call_args
        assert call_args[1]["json"]["compliance_mode"] == "permissive"


# ---------------------------------------------------------------------------
# Schemas
# ---------------------------------------------------------------------------


class TestSchemas:
    def test_create(self, client, mock_request):
        mock_request.return_value = {
            "id": "schema-1",
            "tenantId": "t-1",
            "verticalId": "pharma",
            "version": "1.0.0",
            "metadataSchema": {},
            "exportFormats": ["gs1"],
            "isActive": True,
            "createdAt": "2026-03-27T10:00:00Z",
            "updatedAt": "2026-03-27T10:00:00Z",
        }
        params = CreateSchemaParams(
            vertical_id="pharma",
            metadata_schema={"gtin": {"type": "string", "required": True}},
            version="1.0.0",
        )
        result = client.schemas.create(params)
        assert result.verticalId == "pharma"

    def test_list(self, client, mock_request):
        mock_request.return_value = {"data": [], "total": 0}
        client.schemas.list(ListSchemasParams(page=1, per_page=10))
        call_args = mock_request.call_args
        assert call_args[1]["params"]["page"] == 1

    def test_get(self, client, mock_request):
        mock_request.return_value = {
            "id": "schema-1",
            "tenantId": "t-1",
            "verticalId": "pharma",
            "version": "1.0.0",
            "metadataSchema": {},
            "exportFormats": [],
            "isActive": True,
            "createdAt": "2026-03-27T10:00:00Z",
            "updatedAt": "2026-03-27T10:00:00Z",
        }
        result = client.schemas.get("pharma")
        assert result.verticalId == "pharma"

    def test_validate_valid(self, client, mock_request):
        mock_request.return_value = {
            "id": "schema-1",
            "tenantId": "t-1",
            "verticalId": "pharma",
            "version": "1.0.0",
            "metadataSchema": {
                "gtin": {"type": "string", "required": True, "label": "GTIN"},
            },
            "exportFormats": [],
            "isActive": True,
            "createdAt": "2026-03-27T10:00:00Z",
            "updatedAt": "2026-03-27T10:00:00Z",
        }
        result = client.schemas.validate("pharma", {"gtin": "01234567890123"})
        assert result.valid is True
        assert len(result.errors) == 0

    def test_validate_missing_required(self, client, mock_request):
        mock_request.return_value = {
            "id": "schema-1",
            "tenantId": "t-1",
            "verticalId": "pharma",
            "version": "1.0.0",
            "metadataSchema": {
                "gtin": {"type": "string", "required": True, "label": "GTIN"},
            },
            "exportFormats": [],
            "isActive": True,
            "createdAt": "2026-03-27T10:00:00Z",
            "updatedAt": "2026-03-27T10:00:00Z",
        }
        result = client.schemas.validate("pharma", {})
        assert result.valid is False
        assert len(result.errors) == 1
        assert result.errors[0]["field"] == "gtin"

    def test_validate_wrong_type(self, client, mock_request):
        mock_request.return_value = {
            "id": "schema-1",
            "tenantId": "t-1",
            "verticalId": "pharma",
            "version": "1.0.0",
            "metadataSchema": {
                "quantity": {"type": "number", "required": False, "label": "Qty"},
            },
            "exportFormats": [],
            "isActive": True,
            "createdAt": "2026-03-27T10:00:00Z",
            "updatedAt": "2026-03-27T10:00:00Z",
        }
        result = client.schemas.validate("pharma", {"quantity": "not-a-number"})
        assert result.valid is False
        assert result.errors[0]["received"] == "str"


# ---------------------------------------------------------------------------
# Webhooks
# ---------------------------------------------------------------------------


class TestWebhooks:
    def test_create(self, client, mock_request):
        mock_request.return_value = {"id": "wh-1", "url": "https://example.com/hook"}
        params = CreateWebhookParams(url="https://example.com/hook", events=["asset.created"])
        client.webhooks.create(params)
        call_args = mock_request.call_args
        body = call_args[1]["json"]
        assert body["url"] == "https://example.com/hook"
        assert "asset.created" in body["events"]

    def test_list(self, client, mock_request):
        mock_request.return_value = []
        result = client.webhooks.list()
        mock_request.assert_called_once_with("GET", "/webhooks")

    def test_delete(self, client, mock_request):
        mock_request.return_value = {}
        client.webhooks.delete("wh-1")
        mock_request.assert_called_once_with("DELETE", "/webhooks/wh-1")

    def test_list_deliveries(self, client, mock_request):
        mock_request.return_value = []
        client.webhooks.list_deliveries("wh-1")
        mock_request.assert_called_once_with("GET", "/webhooks/wh-1/deliveries")


# ---------------------------------------------------------------------------
# Keys
# ---------------------------------------------------------------------------


class TestKeys:
    def test_create(self, client, mock_request):
        mock_request.return_value = {"id": "key-1", "key": "optr_test_xxx"}
        params = CreateKeyParams(
            environment="test", tenant_id="t-1", label="Dev Key"
        )
        client.keys.create(params)
        call_args = mock_request.call_args
        assert call_args[0] == ("POST", "/keys")

    def test_list(self, client, mock_request):
        mock_request.return_value = []
        client.keys.list()
        mock_request.assert_called_once_with("GET", "/keys")

    def test_revoke(self, client, mock_request):
        mock_request.return_value = {}
        client.keys.revoke("key-1")
        mock_request.assert_called_once_with("DELETE", "/keys/key-1")

    def test_update(self, client, mock_request):
        mock_request.return_value = {"id": "key-1", "label": "New Label"}
        client.keys.update("key-1", "New Label")
        call_args = mock_request.call_args
        assert call_args[1]["json"]["label"] == "New Label"


# ---------------------------------------------------------------------------
# Assets
# ---------------------------------------------------------------------------


class TestAssets:
    def test_create_with_dataclass(self, client, mock_request):
        mock_request.return_value = {"id": "asset-1", "status": "active"}
        params = CreateAssetParams(
            config={"product_name": "Widget"},
            vertical="consumer",
        )
        client.assets.create(params)
        call_args = mock_request.call_args
        body = call_args[1]["json"]
        assert body["config"]["product_name"] == "Widget"

    def test_create_with_kwargs(self, client, mock_request):
        mock_request.return_value = {"id": "asset-2", "status": "active"}
        client.assets.create(
            config={"product_name": "Gadget"},
            external_id="SKU-001",
        )
        call_args = mock_request.call_args
        body = call_args[1]["json"]
        assert body["external_id"] == "SKU-001"

    def test_list_sandbox_auto_filter(self, client, mock_request):
        """Sandbox client should auto-filter is_sandbox=True."""
        mock_request.return_value = {"data": []}
        client.assets.list()
        call_args = mock_request.call_args
        params = call_args[1]["params"]
        assert params["is_sandbox"] is True

    def test_get(self, client, mock_request):
        mock_request.return_value = {"id": "asset-1"}
        client.assets.get("asset-1")
        mock_request.assert_called_once_with("GET", "/assets/asset-1")

    def test_revoke(self, client, mock_request):
        mock_request.return_value = {"id": "asset-1", "status": "revoked"}
        client.assets.revoke("asset-1", "counterfeit")
        call_args = mock_request.call_args
        assert call_args[1]["json"]["reason"] == "counterfeit"

    def test_verify_online(self, client, mock_request):
        mock_request.return_value = {
            "signature_valid": True,
            "revocation_status": "clear",
            "filter_age_seconds": 3600,
            "verified_at": "2026-03-27T10:00:00Z",
            "verification_mode": "online",
        }
        result = client.assets.verify("asset-1")
        assert result.signature_valid is True
        assert result.verification_mode == "online"


# ---------------------------------------------------------------------------
# Webhook Signature Verification
# ---------------------------------------------------------------------------


class TestWebhookVerify:
    def test_valid_signature(self):
        import hashlib, hmac
        from optropic import verify_webhook_signature

        secret = "test-secret"
        payload = '{"event": "asset.created"}'
        expected = hmac.new(
            secret.encode(), payload.encode(), hashlib.sha256
        ).hexdigest()

        result = verify_webhook_signature(payload, f"sha256={expected}", secret)
        assert result.valid is True

    def test_invalid_signature(self):
        from optropic import verify_webhook_signature

        result = verify_webhook_signature("payload", "sha256=invalid", "secret")
        assert result.valid is False

    def test_missing_prefix(self):
        from optropic import verify_webhook_signature

        result = verify_webhook_signature("payload", "invalid", "secret")
        assert result.valid is False


# ---------------------------------------------------------------------------
# Error Classes
# ---------------------------------------------------------------------------


class TestErrors:
    def test_authentication_error(self):
        from optropic import AuthenticationError

        err = AuthenticationError()
        assert err.status_code == 401
        assert "Authentication" in err.message

    def test_not_found_error(self):
        from optropic import NotFoundError

        err = NotFoundError("Resource not found")
        assert err.status_code == 404

    def test_rate_limit_error(self):
        from optropic import RateLimitError

        err = RateLimitError()
        assert err.status_code == 429

    def test_validation_error(self):
        from optropic import ValidationError

        err = ValidationError("Bad field")
        assert err.status_code == 400
        assert "Bad field" in err.message

    def test_error_repr(self):
        from optropic import OptropicError

        err = OptropicError(message="test", status_code=500, code="INTERNAL")
        r = repr(err)
        assert "OptropicError" in r
        assert "test" in r
