from enum import Enum
from dataclasses import dataclass
from typing import Dict, Mapping, Optional

from enoslib.infra.configuration import BaseConfiguration

from .constants import (
    DEFAULT_COMMERCIAL_TYPE,
    DEFAULT_GATEWAY_NAME,
    DEFAULT_GATEWAY_TYPE,
    DEFAULT_IMAGE,
    DEFAULT_NAME,
    DEFAULT_NETWORK_NAME,
    DEFAULT_ARCHITECTURE,
)
from .schema import SCHEMA

class Architecture(Enum):
    Standalone = "standalone"
    PrivateNetwork = "private_network"

class Configuration(BaseConfiguration):
    ENOS_META = "Configuration"

    _SCHEMA = SCHEMA

    def __init__(self):
        super().__init__()
        self.config_file = None
        self.profile = None
        self.region = None
        self.network_name = DEFAULT_NETWORK_NAME
        self.gateway_type = DEFAULT_GATEWAY_TYPE
        self.gateway_name = DEFAULT_GATEWAY_NAME
        self.gateway_zone = None
        self.dynamic_ip_required = False
        self.enable_ipv6 = False
        self.ports_to_open = []

        self.architecture = DEFAULT_ARCHITECTURE

        self._machine_cls = InstanceConfiguration
        self._network_cls = str

    @classmethod
    def from_dictionary(
        cls, dictionary: Mapping, validate: bool = True
    ) -> "Configuration":
        if validate:
            cls.validate(dictionary)

        self = cls()
        for k in self.__dict__:
            v = dictionary.get(k)
            if v is not None:
                setattr(self, k, v)
        _machines = dictionary["resources"]["machines"]
        _networks = dictionary["resources"]["networks"]
        self.machines = [InstanceConfiguration.from_dictionary(m) for m in _machines]
        self.networks = _networks

        self.finalize()
        return self

    def to_dict(self) -> Dict:
        d = {}
        for k, v in self.__dict__.items():
            if v is None or k in [
                "machines",
                "networks",
                "_machine_cls",
                "_network_cls",
            ]:
                continue
            d.update({k: v})

        d.update(
            resources={
                "machines": [m.to_dict() for m in self.machines],
                "networks": [n.to_dict() for n in self.networks],
            },
        )
        return d

    def get_ports_configurations(self) -> list["PortToOpen"]:
        ret = []
        for configuration in self.ports_to_open:
            public_port = configuration[0]
            private_port = configuration[1]
            role = configuration[2]
            if len(configuration) == 4:
                protocol = configuration[3]
                ret.append(PortToOpen(public_port, private_port, role, protocol))
            else:
                ret.append(PortToOpen(public_port, private_port, role))
        return ret


class InstanceConfiguration:
    ENOS_META = "InstanceConfiguration"

    def __init__(
        self,
        zone=None,
        commercial_type=None,
        name=None,
        image=None,
        volumes=None,
        enable_ipv6=False,
        protected=None,
        number=None,
        roles=None,
    ):
        self.zone = zone
        self.commercial_type = (
            commercial_type if commercial_type is not None else DEFAULT_COMMERCIAL_TYPE
        )
        self.name = name if name is not None else DEFAULT_NAME
        self.image = image if image is not None else DEFAULT_IMAGE
        self.volumes = volumes
        self.enable_ipv6 = enable_ipv6
        self.protected = protected
        # EnOSlib specific attributes
        self.number = number
        self.roles = roles

    @classmethod
    def from_dictionary(cls, dictionary: Mapping):
        zone = dictionary.get("zone")
        commercial_type = dictionary.get("commercial_type")
        name = dictionary.get("name")
        image = dictionary.get("image")
        volumes = dictionary.get("volumes")
        enable_ipv6 = dictionary.get("enable_ipv6")
        protected = dictionary.get("protected")
        number = dictionary.get("number", 1)
        roles = dictionary.get("roles")
        return cls(
            zone=zone,
            commercial_type=commercial_type,
            name=name,
            image=image,
            volumes=volumes,
            enable_ipv6=enable_ipv6,
            protected=protected,
            number=number,
            roles=roles,
        )

    def to_dict(self) -> Dict:
        d = {}
        d.update({k: v for k, v in self.__dict__.items() if v is not None})
        return d

    def to_instance_kwargs(self) -> Dict:
        """Filter dict for appropriate instances_api.create_server() call

        Returns:
            Dict: kwargs for create_server
        """

        d = self.to_dict()
        # Remove keys not accepted by create_server()
        d.pop("number", None)
        d.pop("roles", None)
        return d


@dataclass
class PortToOpen:
    public_port: int
    private_port: int
    role: str
    protocol: Optional[str] = None
