"""Event handlers for API Gateway and EventBridge integrations."""

from __future__ import annotations

import logging
from abc import ABC
from dataclasses import dataclass
from typing import TYPE_CHECKING, cast

from aws_lambda_typing.events import APIGatewayProxyEventV1, EventBridgeEvent
from aws_lambda_typing.responses import APIGatewayProxyResponseV1
from pydantic import ValidationError

from ..authz import Principal
from ..context import set_principal
from ..exceptions import (
    DomainValidationError,
    FeatureDisabledError,
    ForbiddenError,
    NotFoundError,
    UnauthorizedError,
)
from ..protocols import ICorsHandler, IJwtTokenHandler, IPolicyHandler, IRestHandler
from ..rest.responses import (
    bad_request,
    feature_disabled,
    forbidden,
    internal_server_error,
    not_found,
    unauthorized,
)

log = logging.getLogger(__name__)

if TYPE_CHECKING:
    RestHandlers = list[IRestHandler[APIGatewayProxyEventV1, APIGatewayProxyResponseV1]]
    PolicyHandlers = list[IPolicyHandler[EventBridgeEvent, None]]
else:
    # punq resolves list[T] by using T as a registration key.
    RestHandlers = list[IRestHandler]  # type: ignore[type-arg]
    PolicyHandlers = list[IPolicyHandler]  # type: ignore[type-arg]


@dataclass(slots=True, frozen=True)
class ApiGatewayEventHandler(ABC):
    """Route API Gateway proxy events to matching REST handlers."""

    handlers: RestHandlers
    jwt_token_handler: IJwtTokenHandler
    cors_handler: ICorsHandler

    def handle(self, event: APIGatewayProxyEventV1) -> APIGatewayProxyResponseV1:
        response: APIGatewayProxyResponseV1
        try:
            event = _normalize_resource(event)
            log.info("Handling API Gateway event")
            if event.get("httpMethod") != "OPTIONS":
                self.jwt_token_handler(event)
            handler = next((h for h in self.handlers if h.can_handle(event)), None)
            if handler is None:
                response = not_found("Not found")
            else:
                response = handler.handle(event)
        except ValidationError as exc:
            log.warning("Validation error: %s", exc)
            response = bad_request("Invalid input", details=exc.errors())
        except DomainValidationError as exc:
            log.warning("Domain validation error: %s", exc)
            response = bad_request("Invalid input", details=[str(exc)])
        except NotFoundError as exc:
            log.warning("Not found error: %s", exc)
            response = not_found("Not found", details=[str(exc)])
        except ForbiddenError as exc:
            log.warning("Forbidden error: %s", exc)
            response = forbidden("Forbidden", details=[str(exc)])
        except FeatureDisabledError as exc:
            log.warning("Feature disabled: %s", exc)
            response = feature_disabled("Feature disabled", details=[str(exc)])
        except UnauthorizedError as exc:
            log.warning("Unauthorized error: %s", exc)
            response = unauthorized("Unauthorized", details=[str(exc)])
        except Exception as exc:
            log.exception("Error handling API Gateway event: %s", exc)
            response = internal_server_error()
        return self.cors_handler.apply(event, response)


@dataclass(slots=True, frozen=True)
class EventBridgeEventHandler(ABC):
    """Route EventBridge events to matching policy handlers."""

    handlers: PolicyHandlers

    def handle(self, event: EventBridgeEvent) -> None:
        set_principal(Principal.system())
        for handler in self.handlers:
            if handler.can_handle(event):
                handler.handle(event)


def _normalize_resource(event: APIGatewayProxyEventV1) -> APIGatewayProxyEventV1:
    if event.get("resource"):
        return event

    resource_path = (event.get("requestContext") or {}).get("resourcePath")
    if isinstance(resource_path, str) and resource_path:
        normalized = dict(event)
        normalized["resource"] = resource_path
        return cast(APIGatewayProxyEventV1, normalized)

    path = event.get("path") or event.get("rawPath")
    if not isinstance(path, str) or not path:
        return event

    path_params = event.get("pathParameters")
    resource = path
    if not isinstance(path_params, dict):
        path_params = {}
    for param_name, param_value in path_params.items():
        if not isinstance(param_name, str) or not isinstance(param_value, str):
            continue
        resource = resource.replace(f"/{param_value}", f"/{{{param_name}}}", 1)
    normalized = dict(event)
    normalized["resource"] = resource
    return cast(APIGatewayProxyEventV1, normalized)


__all__ = ["ApiGatewayEventHandler", "EventBridgeEventHandler"]
