"""
Tests for GateWay Completion Webhooks Module.
"""

import time
from datetime import datetime
from unittest.mock import AsyncMock, patch

import pytest

from verlex_server.gateway.backend.completion_webhooks import (
    WebhookConfig,
    WebhookDelivery,
    WebhookEvent,
    WebhookManager,
    WebhookPayload,
    WebhookStatus,
    compute_signature,
    generate_webhook_headers,
    trigger_job_completed,
    trigger_job_failed,
    verify_signature,
)


class TestWebhookSignature:
    """Tests for webhook signature generation and verification."""

    def test_compute_signature(self):
        """Test signature computation."""
        payload = '{"event": "test"}'
        secret = "test_secret"

        signature = compute_signature(payload, secret)

        assert signature is not None
        assert len(signature) == 64  # SHA256 hex

    def test_generate_webhook_headers(self):
        """Test header generation."""
        payload = '{"event": "test"}'
        secret = "test_secret"
        webhook_id = "wh_12345"
        delivery_id = "dlv_12345"

        headers = generate_webhook_headers(payload, secret, webhook_id, delivery_id)

        assert "Content-Type" in headers
        assert headers["Content-Type"] == "application/json"
        assert "X-GateWay-Signature" in headers
        assert "X-GateWay-Timestamp" in headers
        assert headers["X-GateWay-Webhook-Id"] == webhook_id
        assert headers["X-GateWay-Delivery-Id"] == delivery_id

    def test_verify_signature_valid(self):
        """Test successful signature verification."""
        payload = '{"event": "test"}'
        secret = "test_secret"
        timestamp = str(int(time.time()))

        # Generate signature
        signed_content = f"{timestamp}.{payload}"
        signature = compute_signature(signed_content, secret)

        # Verify
        result = verify_signature(payload, f"sha256={signature}", timestamp, secret)

        assert result is True

    def test_verify_signature_invalid(self):
        """Test failed signature verification."""
        payload = '{"event": "test"}'
        secret = "test_secret"
        timestamp = str(int(time.time()))

        result = verify_signature(payload, "sha256=invalid", timestamp, secret)

        assert result is False

    def test_verify_signature_expired(self):
        """Test signature verification with expired timestamp."""
        payload = '{"event": "test"}'
        secret = "test_secret"
        # Timestamp from 10 minutes ago
        timestamp = str(int(time.time()) - 600)

        signed_content = f"{timestamp}.{payload}"
        signature = compute_signature(signed_content, secret)

        result = verify_signature(payload, f"sha256={signature}", timestamp, secret)

        assert result is False


class TestWebhookConfig:
    """Tests for WebhookConfig dataclass."""

    def test_create_config(self):
        """Test creating a webhook config."""
        config = WebhookConfig(
            id="wh_12345",
            user_id="user_123",
            url="https://example.com/webhook",
            events=["job.completed", "job.failed"],
            name="Test Webhook",
        )

        assert config.id == "wh_12345"
        assert config.user_id == "user_123"
        assert config.is_active is True
        assert "job.completed" in config.events

    def test_config_to_dict(self):
        """Test config serialization."""
        config = WebhookConfig(
            id="wh_12345",
            user_id="user_123",
            url="https://example.com/webhook",
            created_at=datetime.utcnow(),
        )

        data = config.to_dict()

        assert data["id"] == "wh_12345"
        assert "created_at" in data

    def test_config_from_dict(self):
        """Test config deserialization."""
        data = {
            "id": "wh_12345",
            "user_id": "user_123",
            "url": "https://example.com/webhook",
            "events": ["job.completed"],
            "is_active": True,
        }

        config = WebhookConfig.from_dict(data)

        assert config.id == "wh_12345"
        assert config.url == "https://example.com/webhook"


class TestWebhookPayload:
    """Tests for WebhookPayload dataclass."""

    def test_create_payload(self):
        """Test creating a webhook payload."""
        payload = WebhookPayload(
            event="job.completed",
            job_id="job_12345",
            user_id="user_123",
            timestamp=datetime.utcnow().isoformat(),
            status="completed",
            has_result=True,
        )

        assert payload.event == "job.completed"
        assert payload.has_result is True

    def test_payload_to_dict(self):
        """Test payload serialization."""
        payload = WebhookPayload(
            event="job.failed",
            job_id="job_12345",
            user_id="user_123",
            timestamp=datetime.utcnow().isoformat(),
            status="failed",
            error="Test error",
        )

        data = payload.to_dict()

        assert data["event"] == "job.failed"
        assert data["error"] == "Test error"
        assert "timestamp" in data


class TestWebhookManager:
    """Tests for WebhookManager class."""

    @pytest.fixture
    def manager(self):
        """Create a fresh webhook manager."""
        return WebhookManager()

    @pytest.mark.asyncio
    async def test_register_webhook(self, manager):
        """Test registering a webhook."""
        webhook = await manager.register_webhook(
            user_id="user_123",
            url="https://example.com/webhook",
            name="Test Webhook",
            events=["job.completed"],
        )

        assert webhook.id.startswith("wh_")
        assert webhook.user_id == "user_123"
        assert webhook.url == "https://example.com/webhook"
        assert webhook.secret is not None

    @pytest.mark.asyncio
    async def test_register_webhook_invalid_url(self, manager):
        """Test registering webhook with invalid URL."""
        with pytest.raises(ValueError):
            await manager.register_webhook(
                user_id="user_123",
                url="ftp://invalid.com",  # Not http/https
            )

    @pytest.mark.asyncio
    async def test_list_user_webhooks(self, manager):
        """Test listing user webhooks."""
        await manager.register_webhook(
            user_id="user_123",
            url="https://example.com/hook1",
        )
        await manager.register_webhook(
            user_id="user_123",
            url="https://example.com/hook2",
        )

        webhooks = await manager.list_user_webhooks("user_123")

        assert len(webhooks) == 2

    @pytest.mark.asyncio
    async def test_get_webhook(self, manager):
        """Test getting a specific webhook."""
        created = await manager.register_webhook(
            user_id="user_123",
            url="https://example.com/webhook",
        )

        webhook = await manager.get_webhook(created.id, "user_123")

        assert webhook is not None
        assert webhook.id == created.id

    @pytest.mark.asyncio
    async def test_get_webhook_wrong_user(self, manager):
        """Test getting webhook with wrong user ID."""
        created = await manager.register_webhook(
            user_id="user_123",
            url="https://example.com/webhook",
        )

        webhook = await manager.get_webhook(created.id, "user_wrong")

        assert webhook is None

    @pytest.mark.asyncio
    async def test_update_webhook(self, manager):
        """Test updating a webhook."""
        created = await manager.register_webhook(
            user_id="user_123",
            url="https://example.com/webhook",
        )

        updated = await manager.update_webhook(
            webhook_id=created.id,
            user_id="user_123",
            name="Updated Name",
            is_active=False,
        )

        assert updated.name == "Updated Name"
        assert updated.is_active is False

    @pytest.mark.asyncio
    async def test_delete_webhook(self, manager):
        """Test deleting a webhook."""
        created = await manager.register_webhook(
            user_id="user_123",
            url="https://example.com/webhook",
        )

        deleted = await manager.delete_webhook(created.id, "user_123")

        assert deleted is True

        # Verify it's gone
        webhook = await manager.get_webhook(created.id, "user_123")
        assert webhook is None

    @pytest.mark.asyncio
    async def test_trigger_webhook_no_match(self, manager):
        """Test triggering webhook with no matching events."""
        await manager.register_webhook(
            user_id="user_123",
            url="https://example.com/webhook",
            events=["job.failed"],  # Only failed events
        )

        # Trigger completed event
        delivery_ids = await manager.trigger_webhook(
            event=WebhookEvent.JOB_COMPLETED,  # Completed event
            job_id="job_123",
            user_id="user_123",
            job_data={"status": "completed"},
        )

        # Should not trigger because webhook only listens for failed events
        assert len(delivery_ids) == 0

    @pytest.mark.asyncio
    async def test_trigger_webhook_with_filter(self, manager):
        """Test triggering webhook with job filter."""
        await manager.register_webhook(
            user_id="user_123",
            url="https://example.com/webhook",
            events=["job.completed"],
            job_filter={"mode": "P"},  # Only priority jobs
        )

        # Trigger with non-matching filter
        delivery_ids = await manager.trigger_webhook(
            event=WebhookEvent.JOB_COMPLETED,
            job_id="job_123",
            user_id="user_123",
            job_data={"status": "completed", "mode": "Pa"},  # Patient mode
        )

        assert len(delivery_ids) == 0

    @pytest.mark.asyncio
    async def test_trigger_webhook_inactive(self, manager):
        """Test triggering inactive webhook."""
        created = await manager.register_webhook(
            user_id="user_123",
            url="https://example.com/webhook",
        )

        # Deactivate
        await manager.update_webhook(created.id, "user_123", is_active=False)

        # Trigger
        delivery_ids = await manager.trigger_webhook(
            event=WebhookEvent.JOB_COMPLETED,
            job_id="job_123",
            user_id="user_123",
            job_data={"status": "completed"},
        )

        assert len(delivery_ids) == 0


class TestWebhookDelivery:
    """Tests for WebhookDelivery dataclass."""

    def test_create_delivery(self):
        """Test creating a delivery record."""
        delivery = WebhookDelivery(
            id="dlv_12345",
            webhook_id="wh_12345",
            event="job.completed",
            payload={"test": True},
        )

        assert delivery.id == "dlv_12345"
        assert delivery.status == WebhookStatus.PENDING
        assert delivery.attempts == 0

    def test_delivery_to_dict(self):
        """Test delivery serialization."""
        delivery = WebhookDelivery(
            id="dlv_12345",
            webhook_id="wh_12345",
            event="job.completed",
            payload={"test": True},
            status=WebhookStatus.DELIVERED,
            response_status=200,
        )

        data = delivery.to_dict()

        assert data["status"] == "delivered"
        assert data["response_status"] == 200


class TestWebhookEvents:
    """Tests for webhook event types."""

    def test_event_values(self):
        """Test event enum values."""
        assert WebhookEvent.JOB_STARTED.value == "job.started"
        assert WebhookEvent.JOB_COMPLETED.value == "job.completed"
        assert WebhookEvent.JOB_FAILED.value == "job.failed"
        assert WebhookEvent.JOB_CANCELLED.value == "job.cancelled"


class TestHelperFunctions:
    """Tests for helper functions."""

    @pytest.mark.asyncio
    async def test_trigger_job_completed(self):
        """Test trigger_job_completed helper."""
        with patch("verlex_server.gateway.backend.completion_webhooks.get_webhook_manager") as mock_get:
            mock_manager = AsyncMock()
            mock_manager.trigger_webhook = AsyncMock(return_value=["dlv_123"])
            mock_get.return_value = mock_manager

            result = await trigger_job_completed(
                job_id="job_123",
                user_id="user_123",
                job_data={"status": "completed"},
            )

            mock_manager.trigger_webhook.assert_called_once()
            assert result == ["dlv_123"]

    @pytest.mark.asyncio
    async def test_trigger_job_failed(self):
        """Test trigger_job_failed helper."""
        with patch("verlex_server.gateway.backend.completion_webhooks.get_webhook_manager") as mock_get:
            mock_manager = AsyncMock()
            mock_manager.trigger_webhook = AsyncMock(return_value=["dlv_123"])
            mock_get.return_value = mock_manager

            await trigger_job_failed(
                job_id="job_123",
                user_id="user_123",
                job_data={"status": "failed", "error": "Test error"},
            )

            mock_manager.trigger_webhook.assert_called_once()


class TestWebhookIntegration:
    """Integration tests for webhooks."""

    @pytest.fixture
    def manager(self):
        """Create a manager with mocked HTTP client."""
        return WebhookManager()

    @pytest.mark.asyncio
    async def test_full_webhook_lifecycle(self, manager):
        """Test complete webhook lifecycle."""
        # Create webhook
        webhook = await manager.register_webhook(
            user_id="user_123",
            url="https://example.com/webhook",
            events=["job.completed", "job.failed"],
            name="Test Webhook",
        )

        assert webhook.id is not None

        # List webhooks
        webhooks = await manager.list_user_webhooks("user_123")
        assert len(webhooks) == 1

        # Update webhook
        updated = await manager.update_webhook(
            webhook_id=webhook.id,
            user_id="user_123",
            name="Updated Webhook",
        )
        assert updated.name == "Updated Webhook"

        # Get webhook
        fetched = await manager.get_webhook(webhook.id, "user_123")
        assert fetched.name == "Updated Webhook"

        # Delete webhook
        deleted = await manager.delete_webhook(webhook.id, "user_123")
        assert deleted is True

        # Verify deletion
        webhooks = await manager.list_user_webhooks("user_123")
        assert len(webhooks) == 0
