"""Webhook management mixin for gRPC service."""

from __future__ import annotations

from collections.abc import Sequence
from dataclasses import replace
from typing import TYPE_CHECKING, Protocol, Self, cast

from noteflow.config.constants import (
    LOG_EVENT_WEBHOOK_DELETE_FAILED,
    LOG_EVENT_WEBHOOK_REGISTRATION_FAILED,
    LOG_EVENT_WEBHOOK_UPDATE_FAILED,
)
from noteflow.domain.utils.time import utc_now
from noteflow.domain.webhooks.events import WebhookConfig, WebhookEventType
from noteflow.infrastructure.logging import get_logger
from noteflow.infrastructure.persistence.constants import (
    DEFAULT_WEBHOOK_DELIVERY_HISTORY_LIMIT,
    MAX_WEBHOOK_DELIVERIES_LIMIT,
)

from ..proto import noteflow_pb2
from ._types import GrpcContext
from .converters import webhook_config_to_proto, webhook_delivery_to_proto
from .errors import (
    ENTITY_WEBHOOK,
    abort_invalid_argument,
    abort_not_found,
    parse_webhook_id,
    parse_workspace_id,
    require_feature_webhooks,
)

logger = get_logger(__name__)

if TYPE_CHECKING:
    from noteflow.domain.ports.repositories import WebhookRepository
    from noteflow.domain.ports.unit_of_work import UnitOfWork


def _parse_events(event_strings: list[str]) -> frozenset[WebhookEventType]:
    """Parse event type strings to WebhookEventType enum values."""
    return frozenset(WebhookEventType(e) for e in event_strings)


class WebhooksRepositoryProvider(Protocol):
    """Repository provider protocol for webhook operations."""

    supports_webhooks: bool
    webhooks: WebhookRepository

    async def commit(self) -> None: ...

    async def __aenter__(self) -> Self: ...

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: object,
    ) -> None: ...


class WebhooksServicer(Protocol):
    """Protocol for hosts that support webhook operations."""

    def create_repository_provider(self) -> WebhooksRepositoryProvider | UnitOfWork: ...


class _HasField(Protocol):
    def HasField(self, field_name: str) -> bool: ...


class WebhooksMixin:
    """Mixin providing webhook CRUD operations.

    Requires host to implement WebhooksServicer protocol.
    Webhooks require database persistence.
    """

    async def RegisterWebhook(
        self: WebhooksServicer,
        request: noteflow_pb2.RegisterWebhookRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.WebhookConfigProto:
        """Register a new webhook configuration."""
        # Validate URL
        if not request.url or not request.url.startswith(("http://", "https://")):
            logger.error(LOG_EVENT_WEBHOOK_REGISTRATION_FAILED, reason="invalid_url", url=request.url)
            await abort_invalid_argument(context, "URL must start with http:// or https://")
            raise  # Unreachable: abort raises, but helps Pyrefly control flow analysis

        event_values = cast(Sequence[str], request.events)
        # Validate events
        if not event_values:
            logger.error(LOG_EVENT_WEBHOOK_REGISTRATION_FAILED, reason="no_events", url=request.url)
            await abort_invalid_argument(context, "At least one event type required")
            raise  # Unreachable: abort raises, but helps Pyrefly control flow analysis

        try:
            events = _parse_events(list(event_values))
        except ValueError as exc:
            logger.error(LOG_EVENT_WEBHOOK_REGISTRATION_FAILED, reason="invalid_event_type", url=request.url, error=str(exc))
            await abort_invalid_argument(context, f"Invalid event type: {exc}")
            raise  # Unreachable: abort raises, but helps Pyrefly control flow analysis

        workspace_id = await parse_workspace_id(request.workspace_id, context)

        async with self.create_repository_provider() as uow:
            await require_feature_webhooks(uow, context)

            config = WebhookConfig.create(
                workspace_id=workspace_id,
                url=request.url,
                events=list(events),
                name=request.name or "Webhook",
                secret=request.secret or None,
                timeout_ms=request.timeout_ms or 10000,
                max_retries=request.max_retries or 3,
            )
            saved = await uow.webhooks.create(config)
            await uow.commit()
            logger.info("webhook_registered", webhook_id=str(saved.id), workspace_id=str(workspace_id), url=request.url, name=saved.name)
            return webhook_config_to_proto(saved)

    async def ListWebhooks(
        self: WebhooksServicer,
        request: noteflow_pb2.ListWebhooksRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.ListWebhooksResponse:
        """List registered webhooks."""
        async with self.create_repository_provider() as uow:
            await require_feature_webhooks(uow, context)

            if request.enabled_only:
                webhooks = await uow.webhooks.get_all_enabled()
            else:
                webhooks = await uow.webhooks.get_all()

            logger.debug(
                "webhooks_listed",
                count=len(webhooks),
                enabled_only=request.enabled_only,
            )
            return noteflow_pb2.ListWebhooksResponse(
                webhooks=[webhook_config_to_proto(w) for w in webhooks],
                total_count=len(webhooks),
            )

    async def UpdateWebhook(
        self: WebhooksServicer,
        request: noteflow_pb2.UpdateWebhookRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.WebhookConfigProto:
        """Update an existing webhook configuration."""
        webhook_id = await parse_webhook_id(request.webhook_id, context)

        async with self.create_repository_provider() as uow:
            await require_feature_webhooks(uow, context)

            config = await uow.webhooks.get_by_id(webhook_id)
            if config is None:
                logger.error(
                    LOG_EVENT_WEBHOOK_UPDATE_FAILED,
                    reason="not_found",
                    webhook_id=str(webhook_id),
                )
                await abort_not_found(context, ENTITY_WEBHOOK, request.webhook_id)
                raise  # Unreachable: abort raises, but helps Pyrefly control flow analysis

            # Build updated config with explicit field assignments to satisfy type checker
            updated = replace(
                config,
                url=request.url if cast(_HasField, request).HasField("url") else config.url,
                events=(
                    _parse_events(list(cast(Sequence[str], request.events)))
                    if cast(Sequence[str], request.events)
                    else config.events
                ),
                name=request.name if cast(_HasField, request).HasField("name") else config.name,
                enabled=request.enabled if cast(_HasField, request).HasField("enabled") else config.enabled,
                timeout_ms=(
                    request.timeout_ms
                    if cast(_HasField, request).HasField("timeout_ms")
                    else config.timeout_ms
                ),
                max_retries=(
                    request.max_retries
                    if cast(_HasField, request).HasField("max_retries")
                    else config.max_retries
                ),
                secret=request.secret if cast(_HasField, request).HasField("secret") else config.secret,
                updated_at=utc_now(),
            )
            saved = await uow.webhooks.update(updated)
            await uow.commit()

            logger.info(
                "webhook_updated",
                webhook_id=str(webhook_id),
            )
            return webhook_config_to_proto(saved)

    async def DeleteWebhook(
        self: WebhooksServicer,
        request: noteflow_pb2.DeleteWebhookRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.DeleteWebhookResponse:
        """Delete a webhook configuration."""
        webhook_id = await parse_webhook_id(request.webhook_id, context)

        async with self.create_repository_provider() as uow:
            await require_feature_webhooks(uow, context)

            deleted = await uow.webhooks.delete(webhook_id)
            await uow.commit()

            if deleted:
                logger.info(
                    "webhook_deleted",
                    webhook_id=str(webhook_id),
                )
            else:
                logger.error(
                    LOG_EVENT_WEBHOOK_DELETE_FAILED,
                    reason="not_found",
                    webhook_id=str(webhook_id),
                )
            return noteflow_pb2.DeleteWebhookResponse(success=deleted)

    async def GetWebhookDeliveries(
        self: WebhooksServicer,
        request: noteflow_pb2.GetWebhookDeliveriesRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.GetWebhookDeliveriesResponse:
        """Get delivery history for a webhook."""
        webhook_id = await parse_webhook_id(request.webhook_id, context)
        limit = min(request.limit or DEFAULT_WEBHOOK_DELIVERY_HISTORY_LIMIT, MAX_WEBHOOK_DELIVERIES_LIMIT)

        async with self.create_repository_provider() as uow:
            await require_feature_webhooks(uow, context)

            deliveries = await uow.webhooks.get_deliveries(webhook_id, limit=limit)

            logger.debug(
                "webhook_deliveries_queried",
                webhook_id=str(webhook_id),
                count=len(deliveries),
                limit=limit,
            )
            return noteflow_pb2.GetWebhookDeliveriesResponse(
                deliveries=[webhook_delivery_to_proto(d) for d in deliveries],
                total_count=len(deliveries),
            )
