from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, Any, Optional

import botocore.endpoint

from mockmesh.core.fallback import FallbackConfig, safe_intercept
from mockmesh.core.logger import get_logger
from mockmesh.interceptors.aws.dynamo import handle_dynamodb
from mockmesh.interceptors.aws.misc import handle_elasticache, handle_secretsmanager, handle_ssm
from mockmesh.interceptors.aws.rds import handle_rds
from mockmesh.interceptors.aws.s3 import handle_s3
from mockmesh.interceptors.aws.sqs import handle_sqs
from mockmesh.interceptors.common import is_generic_fallback, cloud_provider_for_url
from mockmesh.interceptors.loader import load_json
from mockmesh.interceptors.responses import make_aws_response
from mockmesh.plugins.base import BasePlugin

if TYPE_CHECKING:
    from mockmesh.response.engine import ResponseEngine
    from mockmesh.storage.manager import StorageManager

_log = get_logger("interceptors.aws")

_DEFAULTS_JSON = Path(__file__).parent.parent.parent / "defaults" / "aws.json"


def _get_service(op_model: Any) -> str:
    for attr in ("service_model.service_name", "service_model.endpoint_prefix"):
        try:
            obj = op_model
            for part in attr.split("."):
                obj = getattr(obj, part)
            return obj.lower()
        except Exception:
            pass
    return "unknown"


def _get_operation(op_model: Any) -> str:
    try:
        return op_model.name
    except Exception:
        return "Unknown"


class AwsInterceptor(BasePlugin):
    name = "aws"

    def __init__(self, response_engine: "ResponseEngine") -> None:
        super().__init__(response_engine)
        self._original_make_request: Any = None
        self._defaults: dict = load_json(_DEFAULTS_JSON, "map")

    def activate(
        self,
        config: dict,
        *,
        storage: Optional["StorageManager"] = None,
        provider_defaults: Optional[dict] = None,
        fallback_config: Optional[FallbackConfig] = None,
        custom_handlers: Optional[dict] = None,
        **_: Any,
    ) -> None:
        self.storage = storage
        self._fallback_config = fallback_config
        self._custom_handlers = custom_handlers if custom_handlers is not None else {}
        if provider_defaults is not None:
            self._defaults = provider_defaults.get("aws", self._defaults)
        self._patch()
        self._active = True
        _log.info("AWS interceptor activated",
                  extra={"mm_patched": "botocore.endpoint.Endpoint.make_request"})

    def deactivate(self) -> None:
        if not self._active:
            return
        self._unpatch()
        self._active = False
        _log.info("AWS interceptor deactivated")

    def _patch(self) -> None:
        self._original_make_request = botocore.endpoint.Endpoint.make_request
        interceptor = self

        def _intercept(endpoint_self: Any, operation_model: Any, request_dict: dict) -> tuple:
            operation = _get_operation(operation_model)
            return safe_intercept(
                provider="aws",
                operation=operation,
                handler_fn=interceptor._handle_request,
                handler_args=(operation_model, request_dict),
                fallback_config=interceptor._fallback_config,
                original_fn=interceptor._original_make_request,
                original_args=(endpoint_self, operation_model, request_dict),
                generic_response=make_aws_response({"mocked": True, "service": "aws", "operation": operation}),
                custom_handlers=interceptor._custom_handlers,
            )

        botocore.endpoint.Endpoint.make_request = _intercept  # type: ignore

    def _unpatch(self) -> None:
        if self._original_make_request is None:
            return
        botocore.endpoint.Endpoint.make_request = self._original_make_request
        self._original_make_request = None

    def _handle_request(self, operation_model: Any, request_dict: dict) -> tuple:
        service   = _get_service(operation_model)
        operation = _get_operation(operation_model)

        _url      = request_dict.get("url", "")
        _provider = cloud_provider_for_url(_url) if _url else ""
        if _provider and _provider != "aws":
            _log.debug(
                "AWS interceptor skipping non-AWS URL",
                extra={"mm_url": _url[:80], "mm_provider": _provider},
            )
            return ({}, None)  # type: ignore[return-value]

        mock        = self.response_engine.resolve_aws(service=service, operation=operation)
        config_body = mock.resolve_body(service=service, operation=operation)

        if config_body and not is_generic_fallback(config_body) and mock.tier == "user":
            _log.info("AWS intercepted (user config rule)",
                      extra={"mm_service": service, "mm_operation": operation})
            return make_aws_response(config_body)

        if self.storage and self.storage._ready:
            result = self._dispatch_storage(request_dict, service, operation)
            if result is not None:
                _log.info("AWS intercepted (storage)",
                          extra={"mm_service": service, "mm_operation": operation})
                return make_aws_response(result)

        svc_map = self._defaults.get(service, {})
        if operation in svc_map:
            _log.info("AWS intercepted (default body)",
                      extra={"mm_service": service, "mm_operation": operation})
            return make_aws_response(svc_map[operation])

        if config_body and not is_generic_fallback(config_body):
            _log.info("AWS intercepted (default config rule)",
                      extra={"mm_service": service, "mm_operation": operation})
            return make_aws_response(config_body)

        _log.info("AWS intercepted (generic fallback)",
                  extra={"mm_service": service, "mm_operation": operation})
        return make_aws_response({"mocked": True, "service": service, "operation": operation})

    def _dispatch_storage(self, req: dict, service: str, operation: str) -> Optional[dict]:
        sm = self.storage
        handlers = {
            "s3":             handle_s3,
            "dynamodb":       handle_dynamodb,
            "sqs":            handle_sqs,
            "ssm":            handle_ssm,
            "secretsmanager": handle_secretsmanager,
            "elasticache":    handle_elasticache,
            "rds":            handle_rds,
        }
        handler = handlers.get(service)
        return handler(sm, req, operation) if handler else None
