from collections import namedtuple
from xml.etree import ElementTree as et

from parallels.core.logging import get_logger
from .. import core
from parallels.core.utils.common import if_not_none

logger = get_logger(__name__)

ServerInfo = namedtuple('ServerInfo', (
    'key', 'gen_info', 'components', 'statistics', 'admin', 'interfaces', 'services', 'shells', 'session_setup'
))

ServerKey = namedtuple('ServerKey', ('properties'))
ServerGenInfo = namedtuple('ServerGenInfo', ('name', 'guid', 'external_id', 'mode'))
ServerComponent = namedtuple('ServerComponent', ('name', 'version'))
ServerStat = namedtuple('ServerStat', ('objects', 'versions', 'other', 'load', 'memory', 'swap', 'disk_space'))
ServerAdmin = namedtuple('ServerAdmin', (
    'cname', 'pname', 'phone', 'fax', 'email', 'address', 'city', 'state', 'pcode', 'country', 'send_announce',
))
ServerServiceState = namedtuple('ServerServiceState', ('id', 'title', 'state', 'error'))
ServerPrefs = namedtuple('ServerPrefs', ())
ServerShell = namedtuple('ServerShell', ('name', 'path'))
ServerSessionSetup = namedtuple('ServerSessionSetup', ('login_timeout'))

ServerObjectsStatistics = namedtuple('ServerObjectsStatistics', (
    'clients', 'domains', 'active_domains', 'mail_boxes', 'mail_redirects', 'mail_groups',
    'mail_responders', 'web_users', 'databases', 'database_users', 'problem_clients', 'problem_domains',
))
ServerVersionStatistics = namedtuple('ServerVersionStatistics', (
    'plesk_name', 'plesk_version', 'plesk_os', 'plesk_os_version', 'plesk_build', 'os_release', 'veid'
))
ServerOtherStatistics = namedtuple('ServerOtherStatistics', (
    'cpu', 'uptime', 'inside_vz',
))
ServerCpuUsage = namedtuple('ServerCpuUsage', ('l1', 'l5', 'l15'))
ServerMemoryStatistics = namedtuple('ServerMemoryStatistics', (
    'total', 'used', 'free', 'shared', 'buffer', 'cached',
))
ServerSwapStatistics = namedtuple('ServerSwapStatistics', ('total', 'used', 'free'))
ServerDiskSpaceStatistics = namedtuple('ServerDiskSpaceStatistics', (
    'name', 'total', 'used', 'free',
))


class ServerOperator(object):
    class Dataset(object):
        KEY = 'key'
        GEN_INFO = 'gen_info'
        COMPONENTS = 'components'
        STAT = 'stat'
        ADMIN = 'admin'
        INTERFACES = 'interfaces'
        SERVICES_STATE = 'services_state'
        PREFERENCES = 'prefs'
        SHELLS = 'shells'
        SESSION_SETUP = 'session_setup'

        values = [KEY, GEN_INFO, COMPONENTS, STAT, ADMIN, INTERFACES, SERVICES_STATE, PREFERENCES, SHELLS, SESSION_SETUP]
        
    class Get(core.operation('Get', ('dataset'))):
        operator_name = 'server'
        operation_name = 'get'
        min_api_version = '1.5.2.1'
        max_api_version = None

        def inner_xml(self):
            return [et.Element(value) for value in ServerOperator.Dataset.values if value in self.dataset]
            
        @classmethod
        def parse(cls, elem):
            return core.Result.parse(elem.find('result'), cls._parse_data)
        
        @classmethod
        def _parse_data(cls, elem):
            key = if_not_none(elem.find('key'), cls._parse_key)
            gen_info = if_not_none(elem.find('gen_info'), cls._parse_gen_info)
            components = if_not_none(elem.find('components'), cls._parse_components)
            statistics = if_not_none(elem.find('stat'), cls._parse_statistics)
            admin = if_not_none(elem.find('admin'), cls._parse_admin_info)
            interfaces = if_not_none(elem.find('interfaces'), cls._parse_interfaces)
            services = if_not_none(elem.find('services_state'), cls._parse_services_state)
            shells = if_not_none(elem.find('shells'), cls._parse_shells)
            session_setup = if_not_none(elem.find('session_setup'), cls._parse_session_setup)
            return ServerInfo(key, gen_info, components, statistics, admin, interfaces, services, shells, session_setup)
            
        @classmethod
        def _parse_key(cls, key_elem):
            key_props = [(e.findtext('name'), e.findtext('value')) for e in key_elem.findall('property')]
            return ServerKey(dict(key_props))

        @classmethod
        def _parse_gen_info(cls, gi_elem):
            name = gi_elem.findtext('server_name')
            guid = gi_elem.findtext('server_guid')
            external_id = gi_elem.findtext('external-id')
            mode = gi_elem.findtext('mode')  # standard | poweruser
            return ServerGenInfo(name, guid, external_id, mode)
        
        @classmethod
        def _parse_components(cls, elem):
            return dict(
                (n.findtext('name'), ServerComponent(n.findtext('name'), n.findtext('version')))
                for n in elem.findall('component')
            )
        
        @classmethod
        def _parse_statistics(cls, elem):
            objects = cls._parse_objects_statistics(elem.find('objects'))
            versions = cls._parse_version_statistics(elem.find('version'))
            other = cls._parse_other_statistics(elem.find('other'))
            load = cls._parse_load_statistics(elem.find('load_avg'))
            memory = cls._parse_memory_statistics(elem.find('mem'))
            swap = cls._parse_swap_statistics(elem.find('swap'))
            disk_space = cls._parse_disk_space_statistics(elem.find('diskspace'))
            return ServerStat(objects, versions, other, load, memory, swap, disk_space)
        
        @classmethod
        def _parse_objects_statistics(cls, elem):
            return ServerObjectsStatistics._make(
                cls._parse_int(elem.findtext(name)) for name in ServerObjectsStatistics._fields
            )
        
        @classmethod
        def _parse_version_statistics(cls, elem):
            return ServerVersionStatistics(
                elem.findtext('plesk_name'),
                elem.findtext('plesk_version'),
                elem.findtext('plesk_os'),
                elem.findtext('plesk_os_version'),
                elem.findtext('plesk_build'),
                elem.findtext('os_release'),
                if_not_none(elem.findtext('veid'), int),
            )
            
        @classmethod
        def _parse_other_statistics(cls, elem):
            return ServerOtherStatistics(
                elem.findtext('cpu'),
                cls._parse_int(elem.findtext('uptime')),
                elem.findtext('inside_vz') == 'true',
            )
            
        @classmethod
        def _parse_load_statistics(cls, elem):
            return ServerCpuUsage._make(float(elem.findtext(name)) for name in ServerCpuUsage._fields)
        
        @classmethod
        def _parse_memory_statistics(cls, elem):
            return ServerMemoryStatistics._make(
                cls._parse_int(elem.findtext(name)) for name in ServerMemoryStatistics._fields
            )
        
        @classmethod
        def _parse_swap_statistics(cls, elem):
            return ServerSwapStatistics._make(
                cls._parse_int(elem.findtext(name)) for name in ServerSwapStatistics._fields
            )

        @classmethod
        def _parse_disk_space_statistics(cls, elem):
            stats = [
                ServerDiskSpaceStatistics(
                    e.findtext('name'),
                    cls._parse_int(e.findtext('total')),
                    cls._parse_int(e.findtext('used')),
                    cls._parse_int(e.findtext('free'))
                )
                for e in elem.findall('device')
            ]
            return dict((s.name, s) for s in stats)
        
        @classmethod
        def _parse_interfaces(cls, elem):
            return set(e.text for e in elem.findall('interface'))

        @classmethod
        def _parse_services_state(cls, elem):
            services = [
                ServerServiceState(e.findtext('id'), e.findtext('title'), e.findtext('state'), e.findtext('error'))
                for e in elem.findall('srv')
            ]
            return dict((s.id, s) for s in services)

        @classmethod
        def _parse_shells(cls, elem):
            shells = [
                ServerShell(e.findtext('name'), e.findtext('path'))
                for e in elem.findall('shell')
            ]
            return dict((s.name, s) for s in shells)
        
        @classmethod
        def _parse_session_setup(cls, elem):
            return ServerSessionSetup(if_not_none(elem.findtext('login_timeout'), int))
        
        @classmethod
        def _parse_admin_info(cls, elem):
            return ServerAdmin(
                elem.findtext('cname'), elem.findtext('pname'),
                elem.findtext('phone'), elem.findtext('fax'), elem.findtext('email'),
                elem.findtext('address'), elem.findtext('city'), elem.findtext('state'), elem.findtext('pcode'),
                elem.findtext('country'), elem.findtext('send_announce') == 'true',
            )

        @staticmethod
        def _parse_int(string):
            """Return integer representation of given string
            :type string: str | None
            :rtype: int
            """
            if string is None:
                # suppose that None value given instead of string means zero
                return 0
            string = string.strip()
            if not string:
                # suppose that empty string means zero
                return 0
            try:
                return int(string)
            except Exception:
                logger.exception()
                # given string can not be converted to integer, so take it as zero
                return 0
