import json

from typeguard import typechecked
from typing import Any, Dict, List, Union
from wallboard.models import AlertConfig, ProductAccountConfig
from wallboard.utils.constants import ALERT_LABELS_TO_THRESHOLD_LABEL


@typechecked
class ProductConfig:
    """Model class for a product config.

    Enforces attribute schemas (types/allowed values) and defaults.
    Implements to_dict() and from_dict() to allow easy conversion to and from dictionaries.
    """

    def __init__(
        self,
        title: str = None,
        product_id: str = None,
        alert_cfgs: List[AlertConfig] = [],
        account_cfgs: List[ProductAccountConfig] = [],
    ):
        self.title = title
        self.product_id = product_id
        self.alert_cfgs = alert_cfgs
        self.account_cfgs = account_cfgs

    @property
    def title(self) -> Union[str, None]:
        return self.__title

    @title.setter
    def title(self, title: Union[str, None]):
        if title is not None:
            assert title, "Product title cannot be an empty string"
        self.__title = title

    @property
    def product_id(self) -> Union[str, None]:
        return self.__product_id

    @product_id.setter
    def product_id(self, product_id: Union[str, None]):
        if product_id is not None:
            assert product_id, "Product ID cannot be an empty string"
        self.__product_id = product_id

    @property
    def alert_cfgs(self) -> List[AlertConfig]:
        return list(sorted(self.__alert_cfgs.values(), key=lambda cfg: cfg.label))

    @alert_cfgs.setter
    def alert_cfgs(self, alert_cfgs: List[AlertConfig]):
        self.__alert_cfgs = {label: AlertConfig(label) for label in ALERT_LABELS_TO_THRESHOLD_LABEL}
        for cfg in alert_cfgs:
            self.__alert_cfgs[cfg.label] = cfg

    @property
    def account_cfgs(self) -> List[ProductAccountConfig]:
        return list(sorted(self.__account_cfgs.values(), key=lambda cfg: cfg.account_id))

    @account_cfgs.setter
    def account_cfgs(self, account_cfgs: List[ProductAccountConfig]):
        self.__account_cfgs = {}
        for cfg in account_cfgs:
            cfg.product_id = self.product_id
            self.__account_cfgs[cfg.account_id] = cfg

    def all_monitors(self) -> List[str]:
        """Returns a list of all of this product's synthetic monitor IDs across all accounts"""
        all_monitors: List[str] = []
        for acct_cfg in self.account_cfgs:
            all_monitors.extend(acct_cfg.monitors or [])
        return list(sorted(set(all_monitors)))

    def from_dict(self, cfg: Dict[str, Any]):
        """Load this object's attributes from a dictionary

        Parameters
        ----------
        cfg : Dict[str, Any]
            The dictionary to load this object's attributes from. Requires structure:

            {
                "title": str|None,
                "productId": int|None,
                "alerts": Dict[str, dict]|None,
                "accounts": List[dict]|None
            }
        """
        self.title = cfg.get("title")
        self.product_id = cfg.get("productId")

        alert_cfgs: List[AlertConfig] = []
        for k, v in (cfg.get("alerts") or {}).items():
            alert_cfg = AlertConfig(label=k)
            alert_cfg.from_dict(v)
            alert_cfgs.append(alert_cfg)
        self.alert_cfgs = alert_cfgs

        account_cfgs: List[ProductAccountConfig] = []
        for account_cfg_dict in cfg.get("accounts") or []:
            account_cfg = ProductAccountConfig()
            account_cfg.from_dict(account_cfg_dict)
            account_cfgs.append(account_cfg)
        self.account_cfgs = account_cfgs

    def to_dict(self, sparse: bool = False) -> Dict[str, Any]:
        """Convert this object to a dictionary

        Parameters
        ----------
        sparse : bool
            (optional) If true, does not include empty fields on the returned dictionary.

        Returns
        -------
        product_dict : Dict[str, Any]
            This object in dictionary form with the structure:

            {
                "productId": str|None,
                "accountId": int|None,
                "syntheticGuids": List[str]|None,
                "browserApps": List[str]|None,
                "apmApps": List[str]|None,
                "notificationChannelIds": List[int]|None
            }
        """
        d = {}
        if self.title or not sparse:
            d["title"] = self.title
        if self.product_id or not sparse:
            d["productId"] = self.product_id

        alert_cfgs = [cfg for cfg in self.alert_cfgs if cfg.to_dict(sparse)]
        if alert_cfgs or not sparse:
            d["alerts"] = {cfg.label: cfg.to_dict(sparse) for cfg in alert_cfgs}

        acct_cfgs = [cfg for cfg in self.account_cfgs if cfg.to_dict(sparse)]
        if acct_cfgs or not sparse:
            d["accounts"] = [cfg.to_dict(sparse) for cfg in acct_cfgs]
        return d

    def __repr__(self) -> str:
        return json.dumps(self.to_dict(sparse=True))
