import json
import logging

from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Optional, Set, Tuple, Iterator

from saas.library.python.deploy_manager_api import SaasService
from saas.library.python.deploy_manager_api.helpers import saas_service_iterator
from saas.library.python.saas_ctype import SaasCtype
from saas.library.python.warden import WardenFunctionality
from saas.tools.devops.saas_disaster_alerts.disaster_alerts.modules.config import DisasterAlertsConfig
from saas.tools.devops.saas_disaster_alerts.disaster_alerts.modules.warden import WardenManager


DEFAULT_CRIT_VALUE = 1.0
DEFAULT_CRIT_WARN_RATIO = 5
SLA_INFO_LIMITS_MULTIPLIER = 1.5


@dataclass(frozen=True)
class Tags:
    prj: str = None


@dataclass(frozen=True)
class Service:
    name: str
    ctype: str

    warden_test_flow: bool
    warden_functionality_slug: Optional[str] = None

    tags: Optional[Tags] = None
    geos: Optional[Tuple[str]] = None

    alert_limits: Optional[Tuple[float, float]] = None

    def to_dict(self) -> dict:
        return asdict(self, dict_factory=lambda d: {k: v for k, v in d if v})

    def to_jinja_text(self) -> str:
        return json.dumps(self.to_dict(), indent=4)


def _create_or_update_warden_functionality(warden_manager: WardenManager, service: Service, weight: int = 0) -> None:
    if service.warden_test_flow:
        name: str = 'Test searchproxy 5xx rate [auto]'
        slug: str = f'{warden_manager.component_name}_searchproxy_test_failures_rate_auto'
        description: str = '5xx rate for searchproxy on all services without explicit disaster_alerts set in SLA'
        instructions: str = 'Incidents should not be taken seriously, this is a test monitoring'
        warden_queue: str = 'TESTSPI'
    else:
        name: str = f'{service.ctype} {service.name} searchproxy 5xx rate [auto]'
        slug: str = f'{warden_manager.component_name}_searchproxy_{service.ctype}_{service.name}_failures_rate_auto'
        description: str = f'5xx rate for prod searchproxy on {service.name} with ctype {service.ctype}'
        instructions: str = 'Information should be provided to SAAS on duty'
        warden_queue: str = 'SPI'

    functionality: WardenFunctionality = WardenFunctionality(
        name=name,
        slug=slug,
        description=description,
        instructions=instructions,
        weight=weight,
        target_queue=warden_queue,

        test_flow=service.warden_test_flow,
        component_name=warden_manager.component_name,
    )

    warden_manager.create_or_update_functionality(functionality)


def _generate_warden_functionality_slug(config: DisasterAlertsConfig, service: SaasService, test_flow: bool) -> str:
    if test_flow:
        return f'{config.warden_component_name}_searchproxy_test_failures_rate_auto'
    else:
        return f'{config.warden_component_name}_searchproxy_{service.ctype}_{service.name}_failures_rate_auto'


def service_iterator(config: DisasterAlertsConfig) -> Iterator[Service]:
    for saas_service in saas_service_iterator():
        test_flow: bool = not saas_service.sla_info.get('disaster_alerts')
        if test_flow and not config.warden_use_test_spi:
            continue

        warn: float = saas_service.sla_info.get('unanswers_5min_perc_warn')
        crit: float = saas_service.sla_info.get('unanswers_5min_perc_crit')

        if not crit:
            if not test_flow:
                logging.error('Attention! Service %s has no crit value and will be skipped', saas_service.name)
                continue

            crit: float = DEFAULT_CRIT_VALUE
            logging.debug('Service %s has no crit value, it will be set to %f', saas_service.name, crit)

        is_warn_incorrect: bool = warn and warn > crit
        if not warn or is_warn_incorrect:
            warn: float = crit / DEFAULT_CRIT_WARN_RATIO
            log_level: int = logging.WARNING if not test_flow else logging.DEBUG

            logging.log(
                log_level,
                'Service %s has %s warn value, it will be set to %f',
                saas_service.name,
                'incorrect' if is_warn_incorrect else 'no',
                warn
            )

        mp: float = SLA_INFO_LIMITS_MULTIPLIER / 100
        alert_limits: Tuple[float, float] = warn * mp, crit * mp

        geos: Set[str] = set()
        if saas_service.per_dc_search:
            for slot in saas_service.slots:
                geos.add(slot.geo)
        geos: Optional[Tuple[str]] = tuple(map(lambda x: x.lower(), geos)) or None

        saas_ctype: SaasCtype = SaasCtype(saas_service.ctype)
        tags: Tags = Tags(prj=saas_ctype.yasm_prj_prefix)

        service: Service = Service(
            name=saas_service.name,
            ctype=saas_service.ctype,
            tags=tags,
            geos=geos,
            alert_limits=alert_limits,
            warden_test_flow=test_flow,
            warden_functionality_slug=_generate_warden_functionality_slug(config, saas_service, test_flow)
        )

        yield service


def get_and_process_services(config: DisasterAlertsConfig, warden_manager: WardenManager, replace_remote: bool) -> int:
    content = []

    services = list(service_iterator(config))
    ydt_services = list(filter(lambda x: not x.warden_test_flow, services))
    ydt_weight = round(1/len(ydt_services), 4)

    for service in services:
        if replace_remote:
            try:
                _create_or_update_warden_functionality(
                    warden_manager,
                    service,
                    weight=ydt_weight if not service.warden_test_flow else 0
                )
            except:
                logging.exception('Unable to create or update warden functionality for service %s, '
                                  'the service will be skipped', service.name)
                continue

        text: str = service.to_jinja_text()
        content.append(text)

        logging.debug('%s... OK, the service will be recorded', service.name)

    if not content:
        return 0

    path: Path = config.get_template_file_path(config.services_template_key)
    services_content: str = ',\n'.join(content)

    with open(path, 'w+', encoding='utf-8') as f:
        f.write(f'<% set services = [{services_content}] %>')

    return len(content)
