from enum import IntEnum, auto
from ipaddress import IPv4Address, IPv6Address, ip_address
from typing import Dict, List, Union

from dataclasses import dataclass, field


class NodeStatus(IntEnum):
    OFFLINE = auto()
    PROBLEM = auto()
    OK = auto()


class ClusterStatus(IntEnum):
    GREEN = auto()
    YELLOW = auto()
    RED = auto()


@dataclass()
class Node:
    hostname: str
    ip_address: Union[IPv4Address, IPv6Address]
    status: NodeStatus
    memory_total: float
    memory_used: float = 0

    @classmethod
    def create_from_dict(cls, data: Dict[str, Union[str, int, float]]):
        status_str = data['status'].lower()
        if not hasattr(NodeStatus, status_str):
            raise ValueError(f'Invalid status: {status_str}')

        return cls(
            hostname=data['hostname'],
            ip_address=ip_address(data['ipAddress']),
            status=getattr(NodeStatus, status_str),
            memory_total=float(data['memoryTotal']),
            memory_used=float(data.get('memoryUsed', 0))
        )

    def get_memory_free(self) -> float:
        return self.memory_total - self.memory_used

    def get_memory_used_ratio(self) -> float:
        return self.memory_used / self.memory_total


@dataclass()
class Cluster:
    domain: str
    nodes: List[Node] = field(default_factory=list)

    @classmethod
    def create_from_dict(cls, data: Dict[str, Union[str, int, float]]):
        return cls(
            domain=data['domain'],
            nodes=[Node.create_from_dict(node_data) for node_data in data.get('nodes', [])]
        )

    def get_status(self):
        worst_node_status = max(node.status for node in self.nodes)
        if worst_node_status == NodeStatus.OFFLINE:
            return ClusterStatus.RED
        elif worst_node_status == NodeStatus.PROBLEM:
            return ClusterStatus.YELLOW
        else:
            return ClusterStatus.GREEN

    def get_total_memory_used_ratio(self):
        cluster_memory_used = sum(node.memory_used for node in self.nodes)
        cluster_memory_total = sum(node.memory_total for node in self.nodes)
        return cluster_memory_used / cluster_memory_total
