"""
Webhooks client-side validation tests.

Tests that validation happens in the resource layer (before transport)
using a stub transport.
"""

from __future__ import annotations

from typing import Any

import pytest

from timeback_common import InputValidationError, WebhookPaths
from timeback_webhooks.resources.webhook_filters import WebhookFiltersResource
from timeback_webhooks.resources.webhooks import WebhooksResource


class _StubTransport:
    """Minimal stub transport that records requests without making HTTP calls."""

    def __init__(self) -> None:
        self.request_count = 0
        self.paths = WebhookPaths()

    async def get(self, _path: str, **_kwargs: Any) -> dict[str, Any]:
        self.request_count += 1
        return {"webhooks": [], "filters": []}

    async def post(self, _path: str, _data: Any = None, **_kwargs: Any) -> dict[str, Any]:
        self.request_count += 1
        return {
            "webhook": _valid_webhook_response(),
            "filter": _valid_filter_response(),
        }

    async def put(self, _path: str, _data: Any = None, **_kwargs: Any) -> dict[str, Any]:
        self.request_count += 1
        return {
            "webhook": _valid_webhook_response(),
            "filter": _valid_filter_response(),
        }

    async def request(self, _method: str, _path: str, **_kwargs: Any) -> dict[str, Any]:
        self.request_count += 1
        return {"message": "deleted"}

    async def close(self) -> None:
        pass


def _valid_webhook_response() -> dict[str, Any]:
    return {
        "id": "test-id",
        "sensor": None,
        "name": "Test Webhook",
        "description": None,
        "targetUrl": "https://example.com/webhook",
        "secret": "test-secret",
        "active": True,
        "created_at": "2024-01-01T00:00:00Z",
        "updated_at": None,
        "deleted_at": None,
    }


def _valid_filter_response() -> dict[str, Any]:
    return {
        "id": "filter-id",
        "webhookId": "webhook-id",
        "filterKey": "type",
        "filterValue": "Event",
        "filterType": "string",
        "filterOperator": "eq",
        "active": True,
        "created_at": "2024-01-01T00:00:00Z",
        "updated_at": None,
        "deleted_at": None,
    }


def _valid_webhook_input() -> dict[str, Any]:
    return {
        "name": "Test Webhook",
        "targetUrl": "https://example.com/webhook",
        "secret": "test-secret",
        "active": True,
    }


def _valid_filter_input() -> dict[str, Any]:
    return {
        "webhookId": "webhook-id",
        "filterKey": "type",
        "filterValue": "Event",
        "filterType": "string",
        "filterOperator": "eq",
        "active": True,
    }


class TestWebhookCreateValidation:
    """Tests that invalid webhook create inputs are rejected before reaching the transport."""

    @pytest.mark.asyncio
    async def test_rejects_empty_name(self) -> None:
        transport = _StubTransport()
        resource = WebhooksResource(transport)  # type: ignore[arg-type]
        input_data = _valid_webhook_input()
        input_data["name"] = ""
        with pytest.raises(InputValidationError):
            await resource.create(input_data)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_empty_secret(self) -> None:
        transport = _StubTransport()
        resource = WebhooksResource(transport)  # type: ignore[arg-type]
        input_data = _valid_webhook_input()
        input_data["secret"] = ""
        with pytest.raises(InputValidationError):
            await resource.create(input_data)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_empty_target_url(self) -> None:
        transport = _StubTransport()
        resource = WebhooksResource(transport)  # type: ignore[arg-type]
        input_data = _valid_webhook_input()
        input_data["targetUrl"] = ""
        with pytest.raises(InputValidationError):
            await resource.create(input_data)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_valid_input_reaches_transport(self) -> None:
        transport = _StubTransport()
        resource = WebhooksResource(transport)  # type: ignore[arg-type]
        result = await resource.create(_valid_webhook_input())
        assert transport.request_count == 1
        assert result.name == "Test Webhook"


class TestWebhookIdValidation:
    """Tests that empty IDs are rejected."""

    @pytest.mark.asyncio
    async def test_get_rejects_empty_id(self) -> None:
        transport = _StubTransport()
        resource = WebhooksResource(transport)  # type: ignore[arg-type]
        with pytest.raises(InputValidationError):
            await resource.get("")
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_delete_rejects_empty_id(self) -> None:
        transport = _StubTransport()
        resource = WebhooksResource(transport)  # type: ignore[arg-type]
        with pytest.raises(InputValidationError):
            await resource.delete("")
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_activate_rejects_empty_id(self) -> None:
        transport = _StubTransport()
        resource = WebhooksResource(transport)  # type: ignore[arg-type]
        with pytest.raises(InputValidationError):
            await resource.activate("")
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_deactivate_rejects_empty_id(self) -> None:
        transport = _StubTransport()
        resource = WebhooksResource(transport)  # type: ignore[arg-type]
        with pytest.raises(InputValidationError):
            await resource.deactivate("")
        assert transport.request_count == 0


class TestWebhookFilterCreateValidation:
    """Tests that invalid filter create inputs are rejected before reaching the transport."""

    @pytest.mark.asyncio
    async def test_rejects_empty_webhook_id(self) -> None:
        transport = _StubTransport()
        resource = WebhookFiltersResource(transport)  # type: ignore[arg-type]
        input_data = _valid_filter_input()
        input_data["webhookId"] = ""
        with pytest.raises(InputValidationError):
            await resource.create(input_data)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_empty_filter_key(self) -> None:
        transport = _StubTransport()
        resource = WebhookFiltersResource(transport)  # type: ignore[arg-type]
        input_data = _valid_filter_input()
        input_data["filterKey"] = ""
        with pytest.raises(InputValidationError):
            await resource.create(input_data)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_empty_filter_value(self) -> None:
        transport = _StubTransport()
        resource = WebhookFiltersResource(transport)  # type: ignore[arg-type]
        input_data = _valid_filter_input()
        input_data["filterValue"] = ""
        with pytest.raises(InputValidationError):
            await resource.create(input_data)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_invalid_filter_operator(self) -> None:
        transport = _StubTransport()
        resource = WebhookFiltersResource(transport)  # type: ignore[arg-type]
        input_data = _valid_filter_input()
        input_data["filterOperator"] = "invalid"
        with pytest.raises(InputValidationError):
            await resource.create(input_data)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_invalid_filter_type(self) -> None:
        transport = _StubTransport()
        resource = WebhookFiltersResource(transport)  # type: ignore[arg-type]
        input_data = _valid_filter_input()
        input_data["filterType"] = "invalid"
        with pytest.raises(InputValidationError):
            await resource.create(input_data)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_valid_input_reaches_transport(self) -> None:
        transport = _StubTransport()
        resource = WebhookFiltersResource(transport)  # type: ignore[arg-type]
        result = await resource.create(_valid_filter_input())
        assert transport.request_count == 1
        assert result.filter_key == "type"


class TestWebhookFilterIdValidation:
    """Tests that empty IDs are rejected for filter operations."""

    @pytest.mark.asyncio
    async def test_get_rejects_empty_id(self) -> None:
        transport = _StubTransport()
        resource = WebhookFiltersResource(transport)  # type: ignore[arg-type]
        with pytest.raises(InputValidationError):
            await resource.get("")
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_delete_rejects_empty_id(self) -> None:
        transport = _StubTransport()
        resource = WebhookFiltersResource(transport)  # type: ignore[arg-type]
        with pytest.raises(InputValidationError):
            await resource.delete("")
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_list_by_webhook_rejects_empty_id(self) -> None:
        transport = _StubTransport()
        resource = WebhookFiltersResource(transport)  # type: ignore[arg-type]
        with pytest.raises(InputValidationError):
            await resource.list_by_webhook("")
        assert transport.request_count == 0
