"""KafkaBackend class with methods for supported APIs."""

import uuid
from collections.abc import Iterator
from datetime import datetime
from typing import Any

from moto.core.base_backend import BackendDict, BaseBackend
from moto.core.common_models import BaseModel
from moto.core.resource_tagging import TaggableResourcesMixin, TaggedResource
from moto.kafka.exceptions import BadRequestException, NotFoundException
from moto.utilities.utils import get_partition

from ..utilities.tagging_service import TaggingService


class FakeKafkaCluster(BaseModel):
    def __init__(
        self,
        cluster_name: str,
        account_id: str,
        region_name: str,
        cluster_type: str,
        tags: dict[str, str] | None = None,
        broker_node_group_info: dict[str, Any] | None = None,
        kafka_version: str | None = None,
        number_of_broker_nodes: int | None = None,
        configuration_info: dict[str, Any] | None = None,
        serverless_config: dict[str, Any] | None = None,
        encryption_info: dict[str, Any] | None = None,
        enhanced_monitoring: str = "DEFAULT",
        open_monitoring: dict[str, Any] | None = None,
        logging_info: dict[str, Any] | None = None,
        storage_mode: str = "LOCAL",
        current_version: str = "1.0",
        client_authentication: dict[str, Any] | None = None,
        state: str = "CREATING",
        active_operation_arn: str | None = None,
        zookeeper_connect_string: str | None = None,
        zookeeper_connect_string_tls: str | None = None,
    ):
        # General attributes
        self.cluster_id = str(uuid.uuid4())
        self.cluster_name = cluster_name
        self.account_id = account_id
        self.region_name = region_name
        self.cluster_type = cluster_type
        self.tags = tags or {}
        self.state = state
        self.creation_time = datetime.now().isoformat()
        self.current_version = current_version
        self.active_operation_arn = active_operation_arn
        self.arn = self._generate_arn()

        # Attributes specific to PROVISIONED clusters
        self.broker_node_group_info = broker_node_group_info
        self.kafka_version = kafka_version
        self.number_of_broker_nodes = number_of_broker_nodes
        self.configuration_info = configuration_info
        self.encryption_info = encryption_info
        self.enhanced_monitoring = enhanced_monitoring
        self.open_monitoring = open_monitoring
        self.logging_info = logging_info
        self.storage_mode = storage_mode
        self.client_authentication = client_authentication
        self.zookeeper_connect_string = zookeeper_connect_string
        self.zookeeper_connect_string_tls = zookeeper_connect_string_tls

        # Attributes specific to SERVERLESS clusters
        self.serverless_config = serverless_config

    def _generate_arn(self) -> str:
        resource_type = (
            "cluster" if self.cluster_type == "PROVISIONED" else "serverless-cluster"
        )
        partition = get_partition(self.region_name)
        return f"arn:{partition}:kafka:{self.region_name}:{self.account_id}:{resource_type}/{self.cluster_id}"


class KafkaBackend(BaseBackend, TaggableResourcesMixin):
    """Implementation of Kafka APIs."""

    SERVICE_NAMESPACE = "kafka"

    def __init__(self, region_name: str, account_id: str):
        super().__init__(region_name, account_id)
        self.clusters: dict[str, FakeKafkaCluster] = {}
        self.policies: dict[str, dict[str, str]] = {}
        self.tagger = TaggingService()

    def create_cluster_v2(
        self,
        cluster_name: str,
        tags: dict[str, str] | None,
        provisioned: dict[str, Any] | None,
        serverless: dict[str, Any] | None,
    ) -> tuple[str, str, str, str]:
        if provisioned:
            cluster_type = "PROVISIONED"
            broker_node_group_info = provisioned.get("brokerNodeGroupInfo")
            kafka_version = provisioned.get("kafkaVersion", "default-kafka-version")
            number_of_broker_nodes = int(provisioned.get("numberOfBrokerNodes", 1))
            storage_mode = provisioned.get("storageMode", "LOCAL")
            serverless_config = None
        elif serverless:
            cluster_type = "SERVERLESS"
            broker_node_group_info = None
            kafka_version = None
            number_of_broker_nodes = None
            storage_mode = None
            serverless_config = serverless

        new_cluster = FakeKafkaCluster(
            cluster_name=cluster_name,
            account_id=self.account_id,
            region_name=self.region_name,
            cluster_type=cluster_type,
            broker_node_group_info=broker_node_group_info,
            kafka_version=kafka_version,
            number_of_broker_nodes=number_of_broker_nodes,
            serverless_config=serverless_config,
            tags=tags,
            state="CREATING",
            storage_mode=storage_mode if storage_mode else "LOCAL",
            current_version="1.0",
        )

        self.clusters[new_cluster.arn] = new_cluster

        if tags:
            self.tag_resource(new_cluster.arn, tags)

        return (
            new_cluster.arn,
            new_cluster.cluster_name,
            new_cluster.state,
            new_cluster.cluster_type,
        )

    def describe_cluster_v2(self, cluster_arn: str) -> dict[str, Any]:
        cluster = self.clusters[cluster_arn]

        cluster_info: dict[str, Any] = {
            "activeOperationArn": "arn:aws:kafka:region:account-id:operation/active-operation",
            "clusterArn": cluster.arn,
            "clusterName": cluster.cluster_name,
            "clusterType": cluster.cluster_type,
            "creationTime": cluster.creation_time,
            "currentVersion": cluster.current_version,
            "state": cluster.state,
            "stateInfo": {
                "code": "string",
                "message": "Cluster state details.",
            },
            "tags": self.list_tags_for_resource(cluster.arn),
        }

        if cluster.cluster_type == "PROVISIONED":
            cluster_info.update(
                {
                    "provisioned": {
                        "brokerNodeGroupInfo": cluster.broker_node_group_info or {},
                        "clientAuthentication": cluster.client_authentication or {},
                        "currentBrokerSoftwareInfo": {
                            "configurationArn": (cluster.configuration_info or {}).get(
                                "arn", "string"
                            ),
                            "configurationRevision": (
                                cluster.configuration_info or {}
                            ).get("revision", 1),
                            "kafkaVersion": cluster.kafka_version,
                        },
                        "encryptionInfo": cluster.encryption_info or {},
                        "enhancedMonitoring": cluster.enhanced_monitoring,
                        "openMonitoring": cluster.open_monitoring or {},
                        "loggingInfo": cluster.logging_info or {},
                        "numberOfBrokerNodes": cluster.number_of_broker_nodes or 0,
                        "zookeeperConnectString": cluster.zookeeper_connect_string
                        or "zookeeper.example.com:2181",
                        "zookeeperConnectStringTls": cluster.zookeeper_connect_string_tls
                        or "zookeeper.example.com:2181",
                        "storageMode": cluster.storage_mode,
                        "customerActionStatus": "NONE",
                    }
                }
            )

        elif cluster.cluster_type == "SERVERLESS":
            cluster_info.update(
                {
                    "serverless": {
                        "vpcConfigs": cluster.serverless_config.get("vpcConfigs", [])
                        if cluster.serverless_config
                        else [],
                        "clientAuthentication": cluster.serverless_config.get(
                            "clientAuthentication", {}
                        )
                        if cluster.serverless_config
                        else {},
                    }
                }
            )

        return cluster_info

    def list_clusters_v2(
        self,
        cluster_name_filter: str | None,
        cluster_type_filter: str | None,
        max_results: int | None,
        next_token: str | None,
    ) -> tuple[list[dict[str, Any]], str | None]:
        cluster_info_list = []
        for cluster_arn in self.clusters.keys():
            cluster_info = self.describe_cluster_v2(cluster_arn)
            cluster_info_list.append(cluster_info)

        return cluster_info_list, None

    def create_cluster(
        self,
        broker_node_group_info: dict[str, Any],
        client_authentication: dict[str, Any] | None,
        cluster_name: str,
        configuration_info: dict[str, Any] | None = None,
        encryption_info: dict[str, Any] | None = None,
        enhanced_monitoring: str = "DEFAULT",
        open_monitoring: dict[str, Any] | None = None,
        kafka_version: str = "2.8.1",
        logging_info: dict[str, Any] | None = None,
        number_of_broker_nodes: int = 1,
        tags: dict[str, str] | None = None,
        storage_mode: str = "LOCAL",
    ) -> tuple[str, str, str]:
        new_cluster = FakeKafkaCluster(
            cluster_name=cluster_name,
            account_id=self.account_id,
            region_name=self.region_name,
            cluster_type="PROVISIONED",
            broker_node_group_info=broker_node_group_info,
            client_authentication=client_authentication,
            kafka_version=kafka_version,
            number_of_broker_nodes=number_of_broker_nodes,
            configuration_info=configuration_info,
            encryption_info=encryption_info,
            enhanced_monitoring=enhanced_monitoring,
            open_monitoring=open_monitoring,
            logging_info=logging_info,
            storage_mode=storage_mode,
        )

        self.clusters[new_cluster.arn] = new_cluster

        if tags:
            self.tag_resource(new_cluster.arn, tags)

        return new_cluster.arn, new_cluster.cluster_name, new_cluster.state

    def describe_cluster(self, cluster_arn: str) -> dict[str, Any]:
        cluster = self.clusters[cluster_arn]

        return {
            "activeOperationArn": "arn:aws:kafka:region:account-id:operation/active-operation",
            "brokerNodeGroupInfo": cluster.broker_node_group_info or {},
            "clientAuthentication": cluster.client_authentication or {},
            "clusterArn": cluster.arn,
            "clusterName": cluster.cluster_name,
            "creationTime": cluster.creation_time,
            "currentBrokerSoftwareInfo": {
                "configurationArn": (cluster.configuration_info or {}).get(
                    "arn", "string"
                ),
                "configurationRevision": (cluster.configuration_info or {}).get(
                    "revision", 1
                ),
                "kafkaVersion": cluster.kafka_version,
            },
            "currentVersion": cluster.current_version,
            "encryptionInfo": cluster.encryption_info or {},
            "enhancedMonitoring": cluster.enhanced_monitoring,
            "openMonitoring": cluster.open_monitoring or {},
            "loggingInfo": cluster.logging_info or {},
            "numberOfBrokerNodes": cluster.number_of_broker_nodes or 0,
            "state": cluster.state,
            "stateInfo": {
                "code": "string",
                "message": "Cluster state details.",
            },
            "tags": self.list_tags_for_resource(cluster.arn),
            "zookeeperConnectString": cluster.zookeeper_connect_string
            or "zookeeper.example.com:2181",
            "zookeeperConnectStringTls": cluster.zookeeper_connect_string_tls
            or "zookeeper.example.com:2181",
            "storageMode": cluster.storage_mode,
            "customerActionStatus": "NONE",
        }

    def list_clusters(
        self,
        cluster_name_filter: str | None,
        max_results: int | None,
        next_token: str | None,
    ) -> list[dict[str, Any]]:
        cluster_info_list = [
            {
                "clusterArn": cluster.arn,
                "clusterName": cluster.cluster_name,
                "state": cluster.state,
                "creationTime": cluster.creation_time,
                "clusterType": cluster.cluster_type,
            }
            for cluster_arn, cluster in self.clusters.items()
        ]

        return cluster_info_list

    def delete_cluster(self, cluster_arn: str, current_version: str) -> tuple[str, str]:
        cluster = self.clusters.pop(cluster_arn)
        return cluster_arn, cluster.state

    def put_cluster_policy(
        self,
        cluster_arn: str,
        current_version: str,
        policy: str,
    ) -> str:
        if cluster_arn not in self.clusters:
            raise BadRequestException(f"Resource not found: {cluster_arn}")

        if cluster_arn in self.policies:
            stored_version = self.policies[cluster_arn]["current_version"]
            if stored_version != current_version:
                raise BadRequestException(
                    f"Version mismatch: expected {stored_version}, got {current_version}",
                )

        new_version = str(uuid.uuid4())[:8]
        self.policies[cluster_arn] = {
            "policy": policy,
            "current_version": new_version,
        }
        return new_version

    def get_cluster_policy(self, cluster_arn: str) -> tuple[str, str]:
        if cluster_arn not in self.clusters:
            raise NotFoundException(f"Resource not found: {cluster_arn}")

        if cluster_arn not in self.policies:
            return "", ""

        stored = self.policies[cluster_arn]
        return stored["policy"], stored["current_version"]

    def delete_cluster_policy(self, cluster_arn: str) -> None:
        if cluster_arn not in self.clusters:
            raise NotFoundException(f"Resource not found: {cluster_arn}")

        if cluster_arn not in self.policies:
            raise NotFoundException(f"Resource not found: {cluster_arn}")

        del self.policies[cluster_arn]

    def list_tags_for_resource(self, resource_arn: str) -> dict[str, str]:
        return self.tagger.get_tag_dict_for_resource(resource_arn)

    # Resource Groups Tagging API (TaggableResourcesMixin method overrides)
    def iter_tagged_resources(self) -> Iterator[TaggedResource]:
        for cluster in self.clusters.values():
            yield TaggedResource(
                arn=cluster.arn,
                tags=self.tagger.get_tag_dict_for_resource(cluster.arn),
                resource_type="kafka:cluster",
            )

    def tag_resource(self, resource_arn: str, tags: dict[str, str]) -> None:
        tags_list = [{"Key": k, "Value": v} for k, v in tags.items()]
        self.tagger.tag_resource(resource_arn, tags_list)

    def untag_resource(self, resource_arn: str, tag_keys: list[str]) -> None:
        self.tagger.untag_resource_using_names(resource_arn, tag_keys)


kafka_backends = BackendDict(KafkaBackend, "kafka")
