"""
Unit tests for platform/api/webhooks.py

Tests webhook delivery functions and signature generation.
"""

from unittest.mock import Mock, patch, MagicMock
import json

import pytest
from django.utils import timezone
import requests

from smoothschedule.platform.api.webhooks import (
    generate_signature,
    verify_signature,
    create_webhook_payload,
    deliver_webhook,
    queue_webhook_delivery,
    retry_failed_webhooks,
    send_test_webhook,
)


class TestGenerateSignature:
    """Tests for generate_signature function."""

    def test_generates_hmac_sha256_signature(self):
        """Should generate HMAC-SHA256 signature."""
        payload = '{"type":"test"}'
        secret = 'test_secret'
        timestamp = 1234567890

        result = generate_signature(payload, secret, timestamp)

        # Should be a hex string
        assert isinstance(result, str)
        assert len(result) == 64  # SHA256 produces 64 hex chars

    def test_same_inputs_produce_same_signature(self):
        """Should produce deterministic signatures."""
        payload = '{"data":"test"}'
        secret = 'secret123'
        timestamp = 9999999999

        sig1 = generate_signature(payload, secret, timestamp)
        sig2 = generate_signature(payload, secret, timestamp)

        assert sig1 == sig2

    def test_different_secrets_produce_different_signatures(self):
        """Should produce different signatures with different secrets."""
        payload = '{"data":"test"}'
        timestamp = 1234567890

        sig1 = generate_signature(payload, 'secret1', timestamp)
        sig2 = generate_signature(payload, 'secret2', timestamp)

        assert sig1 != sig2

    def test_different_timestamps_produce_different_signatures(self):
        """Should produce different signatures with different timestamps."""
        payload = '{"data":"test"}'
        secret = 'test_secret'

        sig1 = generate_signature(payload, secret, 1000000000)
        sig2 = generate_signature(payload, secret, 2000000000)

        assert sig1 != sig2


class TestVerifySignature:
    """Tests for verify_signature function."""

    def test_returns_true_for_valid_signature(self):
        """Should return True for valid signature."""
        payload = '{"type":"test"}'
        secret = 'test_secret'
        timestamp = 1234567890

        signature = generate_signature(payload, secret, timestamp)
        result = verify_signature(payload, secret, timestamp, signature)

        assert result is True

    def test_returns_false_for_invalid_signature(self):
        """Should return False for invalid signature."""
        payload = '{"type":"test"}'
        secret = 'test_secret'
        timestamp = 1234567890

        result = verify_signature(payload, secret, timestamp, 'invalid_signature')

        assert result is False

    def test_returns_false_for_wrong_secret(self):
        """Should return False when secret doesn't match."""
        payload = '{"type":"test"}'
        timestamp = 1234567890

        signature = generate_signature(payload, 'correct_secret', timestamp)
        result = verify_signature(payload, 'wrong_secret', timestamp, signature)

        assert result is False


class TestCreateWebhookPayload:
    """Tests for create_webhook_payload function."""

    def test_creates_payload_with_required_fields(self):
        """Should create payload with id, type, created_at, and data."""
        event_type = 'appointment.created'
        data = {'id': '123', 'title': 'Test'}

        result = create_webhook_payload(event_type, data)

        assert 'id' in result
        assert result['id'].startswith('evt_')
        assert result['type'] == 'appointment.created'
        assert 'created_at' in result
        assert result['data'] == data

    def test_id_is_unique(self):
        """Should generate unique IDs for each payload."""
        payload1 = create_webhook_payload('test', {})
        payload2 = create_webhook_payload('test', {})

        assert payload1['id'] != payload2['id']


class TestDeliverWebhook:
    """Tests for deliver_webhook function."""

    @patch('smoothschedule.platform.api.webhooks.requests.post')
    @patch('smoothschedule.platform.api.models.WebhookDelivery')
    def test_returns_true_on_success(self, MockDelivery, mock_post):
        """Should return True when delivery succeeds."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.text = 'OK'
        mock_post.return_value = mock_response

        mock_delivery = Mock()
        MockDelivery.objects.create.return_value = mock_delivery

        subscription = Mock()
        subscription.url = 'https://example.com/webhook'
        subscription.secret = 'test_secret'

        result = deliver_webhook(subscription, 'test', {'id': '123'})

        assert result is True
        mock_delivery.mark_success.assert_called_once()

    @patch('smoothschedule.platform.api.webhooks.requests.post')
    @patch('smoothschedule.platform.api.models.WebhookDelivery')
    def test_returns_false_on_http_error(self, MockDelivery, mock_post):
        """Should return False on HTTP error status."""
        mock_response = Mock()
        mock_response.status_code = 500
        mock_response.text = 'Internal Server Error'
        mock_post.return_value = mock_response

        mock_delivery = Mock()
        MockDelivery.objects.create.return_value = mock_delivery

        subscription = Mock()
        subscription.url = 'https://example.com/webhook'
        subscription.secret = 'test_secret'

        result = deliver_webhook(subscription, 'test', {'id': '123'})

        assert result is False
        mock_delivery.mark_failure.assert_called_once()

    @patch('smoothschedule.platform.api.webhooks.requests.post')
    @patch('smoothschedule.platform.api.models.WebhookDelivery')
    def test_returns_false_on_timeout(self, MockDelivery, mock_post):
        """Should return False on timeout."""
        mock_post.side_effect = requests.exceptions.Timeout()

        mock_delivery = Mock()
        MockDelivery.objects.create.return_value = mock_delivery

        subscription = Mock()
        subscription.url = 'https://example.com/webhook'
        subscription.secret = 'test_secret'

        result = deliver_webhook(subscription, 'test', {'id': '123'})

        assert result is False
        mock_delivery.mark_failure.assert_called_with("Request timed out after 30 seconds")

    @patch('smoothschedule.platform.api.webhooks.requests.post')
    @patch('smoothschedule.platform.api.models.WebhookDelivery')
    def test_returns_false_on_connection_error(self, MockDelivery, mock_post):
        """Should return False on connection error."""
        mock_post.side_effect = requests.exceptions.ConnectionError("Connection refused")

        mock_delivery = Mock()
        MockDelivery.objects.create.return_value = mock_delivery

        subscription = Mock()
        subscription.url = 'https://example.com/webhook'
        subscription.secret = 'test_secret'

        result = deliver_webhook(subscription, 'test', {'id': '123'})

        assert result is False
        mock_delivery.mark_failure.assert_called()

    @patch('smoothschedule.platform.api.webhooks.requests.post')
    @patch('smoothschedule.platform.api.models.WebhookDelivery')
    def test_returns_false_on_request_exception(self, MockDelivery, mock_post):
        """Should return False on other request exceptions."""
        mock_post.side_effect = requests.exceptions.RequestException("Unknown error")

        mock_delivery = Mock()
        MockDelivery.objects.create.return_value = mock_delivery

        subscription = Mock()
        subscription.url = 'https://example.com/webhook'
        subscription.secret = 'test_secret'

        result = deliver_webhook(subscription, 'test', {'id': '123'})

        assert result is False
        mock_delivery.mark_failure.assert_called()

    @patch('smoothschedule.platform.api.webhooks.requests.post')
    @patch('smoothschedule.platform.api.models.WebhookDelivery')
    def test_returns_false_on_unexpected_exception(self, MockDelivery, mock_post):
        """Should return False on unexpected exceptions."""
        mock_post.side_effect = Exception("Unexpected error")

        mock_delivery = Mock()
        MockDelivery.objects.create.return_value = mock_delivery

        subscription = Mock()
        subscription.url = 'https://example.com/webhook'
        subscription.secret = 'test_secret'

        result = deliver_webhook(subscription, 'test', {'id': '123'})

        assert result is False
        mock_delivery.mark_failure.assert_called()


class TestQueueWebhookDelivery:
    """Tests for queue_webhook_delivery function."""

    @patch('smoothschedule.platform.api.webhooks.deliver_webhook')
    def test_calls_deliver_webhook(self, mock_deliver):
        """Should call deliver_webhook synchronously."""
        mock_deliver.return_value = True

        subscription = Mock()
        queue_webhook_delivery(subscription, 'test', {'id': '123'})

        mock_deliver.assert_called_once_with(subscription, 'test', {'id': '123'})

    @patch('smoothschedule.platform.api.webhooks.deliver_webhook')
    def test_catches_exceptions_silently(self, mock_deliver):
        """Should catch exceptions without raising."""
        mock_deliver.side_effect = Exception("Delivery failed")

        subscription = Mock()

        # Should not raise
        queue_webhook_delivery(subscription, 'test', {'id': '123'})


class TestRetryFailedWebhooks:
    """Tests for retry_failed_webhooks function."""

    @patch('smoothschedule.platform.api.webhooks.requests.post')
    @patch('smoothschedule.platform.api.webhooks.generate_signature')
    @patch('smoothschedule.platform.api.models.WebhookDelivery')
    def test_retries_failed_deliveries(self, MockDelivery, mock_gen_sig, mock_post):
        """Should retry failed webhook deliveries."""
        mock_gen_sig.return_value = 'test_signature'

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.text = 'OK'
        mock_post.return_value = mock_response

        mock_subscription = Mock()
        mock_subscription.url = 'https://example.com/webhook'
        mock_subscription.secret = 'secret'

        mock_delivery = Mock()
        mock_delivery.subscription = mock_subscription
        mock_delivery.payload = {'type': 'test'}
        mock_delivery.event_id = 'evt_123'
        mock_delivery.retry_count = 1

        MockDelivery.objects.filter.return_value.select_related.return_value.__getitem__ = Mock(return_value=[mock_delivery])
        MockDelivery.objects.filter.return_value.select_related.return_value = [mock_delivery]

        retry_failed_webhooks()

        mock_post.assert_called_once()
        mock_delivery.mark_success.assert_called()

    @patch('smoothschedule.platform.api.webhooks.requests.post')
    @patch('smoothschedule.platform.api.models.WebhookDelivery')
    def test_marks_failure_on_http_error(self, MockDelivery, mock_post):
        """Should mark failure on HTTP error during retry."""
        mock_response = Mock()
        mock_response.status_code = 500
        mock_response.text = 'Error'
        mock_post.return_value = mock_response

        mock_subscription = Mock()
        mock_subscription.url = 'https://example.com/webhook'
        mock_subscription.secret = 'secret'

        mock_delivery = Mock()
        mock_delivery.subscription = mock_subscription
        mock_delivery.payload = {'type': 'test'}
        mock_delivery.event_id = 'evt_123'
        mock_delivery.retry_count = 1

        MockDelivery.objects.filter.return_value.select_related.return_value = [mock_delivery]

        retry_failed_webhooks()

        mock_delivery.mark_failure.assert_called()

    @patch('smoothschedule.platform.api.webhooks.requests.post')
    @patch('smoothschedule.platform.api.models.WebhookDelivery')
    def test_handles_timeout_during_retry(self, MockDelivery, mock_post):
        """Should handle timeout during retry."""
        mock_post.side_effect = requests.exceptions.Timeout()

        mock_subscription = Mock()
        mock_subscription.url = 'https://example.com/webhook'
        mock_subscription.secret = 'secret'

        mock_delivery = Mock()
        mock_delivery.subscription = mock_subscription
        mock_delivery.payload = {'type': 'test'}
        mock_delivery.event_id = 'evt_123'
        mock_delivery.retry_count = 1

        MockDelivery.objects.filter.return_value.select_related.return_value = [mock_delivery]

        retry_failed_webhooks()

        mock_delivery.mark_failure.assert_called_with("Request timed out after 30 seconds")

    @patch('smoothschedule.platform.api.models.WebhookDelivery')
    def test_does_nothing_when_no_deliveries(self, MockDelivery):
        """Should do nothing when no failed deliveries."""
        MockDelivery.objects.filter.return_value.select_related.return_value = []

        retry_failed_webhooks()

        # Should complete without error


class TestSendTestWebhook:
    """Tests for send_test_webhook function."""

    @patch('smoothschedule.platform.api.webhooks.requests.post')
    def test_returns_success_on_2xx_response(self, mock_post):
        """Should return success for 2xx status codes."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_post.return_value = mock_response

        subscription = Mock()
        subscription.id = 'sub_123'
        subscription.url = 'https://example.com/webhook'
        subscription.secret = 'secret'

        result = send_test_webhook(subscription)

        assert result['success'] is True
        assert result['status_code'] == 200
        assert 'successfully' in result['message']

    @patch('smoothschedule.platform.api.webhooks.requests.post')
    def test_returns_failure_on_4xx_response(self, mock_post):
        """Should return failure for 4xx status codes."""
        mock_response = Mock()
        mock_response.status_code = 404
        mock_post.return_value = mock_response

        subscription = Mock()
        subscription.id = 'sub_123'
        subscription.url = 'https://example.com/webhook'
        subscription.secret = 'secret'

        result = send_test_webhook(subscription)

        assert result['success'] is False
        assert result['status_code'] == 404
        assert 'HTTP 404' in result['message']

    @patch('smoothschedule.platform.api.webhooks.requests.post')
    def test_returns_failure_on_timeout(self, mock_post):
        """Should return failure on timeout."""
        mock_post.side_effect = requests.exceptions.Timeout()

        subscription = Mock()
        subscription.id = 'sub_123'
        subscription.url = 'https://example.com/webhook'
        subscription.secret = 'secret'

        result = send_test_webhook(subscription)

        assert result['success'] is False
        assert result['status_code'] is None
        assert 'timed out' in result['message']

    @patch('smoothschedule.platform.api.webhooks.requests.post')
    def test_returns_failure_on_connection_error(self, mock_post):
        """Should return failure on connection error."""
        mock_post.side_effect = requests.exceptions.ConnectionError()

        subscription = Mock()
        subscription.id = 'sub_123'
        subscription.url = 'https://example.com/webhook'
        subscription.secret = 'secret'

        result = send_test_webhook(subscription)

        assert result['success'] is False
        assert result['status_code'] is None
        assert 'connect' in result['message'].lower()

    @patch('smoothschedule.platform.api.webhooks.requests.post')
    def test_returns_failure_on_unexpected_error(self, mock_post):
        """Should return failure on unexpected error."""
        mock_post.side_effect = Exception("Unexpected error")

        subscription = Mock()
        subscription.id = 'sub_123'
        subscription.url = 'https://example.com/webhook'
        subscription.secret = 'secret'

        result = send_test_webhook(subscription)

        assert result['success'] is False
        assert result['status_code'] is None
        assert 'Error' in result['message']
