import json
import threading
from typing import Any, Dict

from moto import settings
from moto.core import CloudFormationModel
from moto.awslambda import lambda_backends
from moto.moto_api._internal import mock_random


class CustomModel(CloudFormationModel):
    def __init__(
        self, region_name: str, request_id: str, logical_id: str, resource_name: str
    ):
        self.region_name = region_name
        self.request_id = request_id
        self.logical_id = logical_id
        self.resource_name = resource_name
        self.data: Dict[str, Any] = dict()
        self._finished = False

    def set_data(self, data: Dict[str, Any]) -> None:
        self.data = data
        self._finished = True

    def is_created(self) -> bool:
        return self._finished

    @property
    def physical_resource_id(self) -> str:
        return self.resource_name

    @staticmethod
    def cloudformation_type() -> str:
        return "?"

    @classmethod
    def create_from_cloudformation_json(  # type: ignore[misc]
        cls,
        resource_name: str,
        cloudformation_json: Dict[str, Any],
        account_id: str,
        region_name: str,
        **kwargs: Any,
    ) -> "CustomModel":
        logical_id = kwargs["LogicalId"]
        stack_id = kwargs["StackId"]
        resource_type = kwargs["ResourceType"]
        properties = cloudformation_json["Properties"]
        service_token = properties["ServiceToken"]

        backend = lambda_backends[account_id][region_name]
        fn = backend.get_function(service_token)

        request_id = str(mock_random.uuid4())

        custom_resource = CustomModel(
            region_name, request_id, logical_id, resource_name
        )

        from moto.cloudformation import cloudformation_backends

        stack = cloudformation_backends[account_id][region_name].get_stack(stack_id)
        stack.add_custom_resource(custom_resource)

        # A request will be send to this URL to indicate success/failure
        # This request will be coming from inside a Docker container
        # Note that, in order to reach the Moto host, the Moto-server should be listening on 0.0.0.0
        #
        # Alternative: Maybe we should let the user pass in a container-name where Moto is running?
        # Similar to how we know for sure that the container in our CI is called 'motoserver'
        host = f"{settings.moto_server_host()}:{settings.moto_server_port()}"
        response_url = (
            f"{host}/cloudformation_{region_name}/cfnresponse?stack={stack_id}"
        )

        event = {
            "RequestType": "Create",
            "ServiceToken": service_token,
            "ResponseURL": response_url,
            "StackId": stack_id,
            "RequestId": request_id,
            "LogicalResourceId": logical_id,
            "ResourceType": resource_type,
            "ResourceProperties": properties,
        }

        invoke_thread = threading.Thread(
            target=fn.invoke, args=(json.dumps(event), {}, {})
        )
        invoke_thread.start()

        return custom_resource

    @classmethod
    def has_cfn_attr(cls, attr: str) -> bool:  # pylint: disable=unused-argument
        # We don't know which attributes are supported for third-party resources
        return True

    def get_cfn_attribute(self, attribute_name: str) -> Any:
        if attribute_name in self.data:
            return self.data[attribute_name]
        return None
