from __future__ import unicode_literals
import ipaddress
import requests
import socket
import re

from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from redis.exceptions import RedisError
from requests.exceptions import ConnectionError, Timeout, RetryError

from library.python.vault_client.errors import ClientError
from load.projects.tank_finder.lib.app import cache, app, Error400, Error404
from load.projects.tank_finder.lib.config import logger
from load.projects.tank_finder.lib.deploy import YpSearch, YpSearchError


def set_to_cache(key, data, timeout=300):
    try:
        cache.set(key, data, timeout=timeout)
    except (ClientError, RedisError) as e:
        logger.error('Cache error: %s', e)


def get_from_cache(key):
    res = []
    try:
        res = cache.get(key)
    except (ClientError, RedisError) as e:
        logger.error('Cache error: %s', e)
    return res


def make_request(url):
    session = requests.Session()
    retry = Retry(total=3,
                  backoff_factor=0.1,
                  status_forcelist=[500, 502, 503, 504])
    session.mount("http://", HTTPAdapter(max_retries=retry))
    session.mount("https://", HTTPAdapter(max_retries=retry))
    try:
        resp = session.get(url, timeout=5)
        return resp
    except (ConnectionError, Timeout, RetryError) as e:
        logger.error('Request failed: %s', e)
        return None


def get_tank_status(tank, port):
    url = 'http://{}:{}/api/v1/tank/status.json'.format(tank, port)
    resp = make_request(url)
    if resp is not None and resp.status_code == 200:
        return resp.json()
    else:
        return None


def get_rt_networks():
    networks = get_from_cache('rt_networks')

    if not networks:
        resp = make_request(app.config['RACKTABLES_URL'])
        if resp is not None and resp.status_code == 200:
            networks = resp.json()
            set_to_cache('rt_networks', networks)
        else:
            raise Error404('Can\'t get networks list from RackTables')

    return networks


def get_host_dc(host, networks):
    dc = ''
    cache_result = get_from_cache('get_host_dc_{}'.format(host))

    if cache_result:
        dc = cache_result
    else:
        try:
            ip = socket.getaddrinfo(host, None, 0, socket.SOCK_STREAM)
            tank_ip = ipaddress.ip_address(unicode(ip[0][4][0]))  # for python2
            # tank_ip = ipaddress.ip_address(ip[0][4][0])  # for python3
        except Exception as e:
            logger.error('Wrong tank or target: %s', e)
            raise Error404('Wrong tank or target hostname(ip address)')

        for n in networks:
            if tank_ip in ipaddress.ip_network(n['network']):
                dc = str(n['dcname'])
                break
        else:
            raise Error404('Ip address or hostname not found')

        set_to_cache('get_host_dc_{}'.format(host), dc)

    return dc


def get_deploy_tanks(tanks, tank_port=8083, data_center=None, target=None, target_port=None):
    if isinstance(tanks, list):
        return [{
            'tank': tank,
            'port': tank_port,
            'dc': data_center,
            'service': 'deploy',
            'hostname': target,
            'target_port': target_port
        } for tank in tanks]


def get_rtc_tanks(app, nanny_service='production_yandex_tank', group=None, data_center=None, target=None, target_port=None):
    if nanny_service == 'production_yandex_tank':
        group = 'ALL_RCLOUD_TANKS'

    tanks_common = []
    tanks_cache_lifetime = app.config['TANKS_CACHE_INFO_LIFETIME']
    cache_result = get_from_cache('rtc_tanks_{}_{}_{}_{}'.format(nanny_service, group, data_center, target))
    if cache_result:
        tanks_common = cache_result
    else:
        try:
            response = make_request(app.config['PUBLIC_TANKS_RTC_URL'].format(nanny_service))
        except ConnectionError:
            logger.error('Unable to get RTC public tanks list')
        else:
            if response is not None and response.status_code == 200:
                tanks = response.json()
                try:
                    for tank in tanks['result']:
                        if not group or re.findall(group, ' '.join(tank['itags'])):
                            for itag in tank['itags']:
                                if re.match('a_dc_', itag):
                                    dc = re.split('a_dc_', itag, maxsplit=2)
                                    tank_info = {
                                        'tank': tank['container_hostname'],
                                        'port': tank['port'],
                                        'dc': dc[1],
                                        'service': 'rtc'
                                    }
                                    if data_center:
                                        if dc[1] == data_center:
                                            tank_info.update({'hostname': target, 'target_port': target_port})
                                            tanks_common.append(tank_info)
                                    else:
                                        tanks_common.append(tank_info)
                        else:
                            logger.error('The nanny service %s has no the group %s.', nanny_service, group)
                except KeyError as e:
                    logger.error('Empty nanny request %s', e)
            else:
                logger.error('Can\'t get RTC public tanks')
                set_to_cache('rtc_tanks_{}_{}_{}_{}'.format(nanny_service, group, data_center, target), tanks_common, tanks_cache_lifetime)

    return tanks_common


def get_rtc_targets(app, nanny_service, nanny_group='', data_center=''):
    targets_found = []

    try:
        response = make_request(app.config['PUBLIC_TANKS_RTC_URL'].format(nanny_service))
        if response is not None and response.status_code == 200:
            for instance in response.json()['result']:
                host = instance.get('container_hostname', '')
                port = instance.get('port', '')
                itags = ' '.join(instance.get('itags', []))
                if re.findall(nanny_group, itags) and re.findall('a_dc_{}'.format(data_center), itags):
                    targets_found.append('{host}:{port}'.format(host=host, port=port))

    except ConnectionError:
        logger.error('Unable to get instances for the RTC service %s', nanny_service, exc_info=True)
        raise Error404('Can\'t connect to RTC for defined instances')

    except KeyError:
        logger.error('Wrong value in the nanny responce %s', response.content, exc_info=True)
        raise Error404('Unable to get instances for the RTC service {}'.format(nanny_service))

    return targets_found


def get_deploy_hosts(stage, du='', dc=''):
    try:
        yp_search = YpSearch(stage, du, dc)
        fqdn = yp_search.get_fqdn()
        if fqdn:
            return fqdn
        else:
            raise Error404('Can\'t find pods for stage {}.'.format(stage))
    except YpSearchError:
        raise Error400('Error in YP metadata for stage {}.'.format(stage))


def get_conductor_tanks(app, data_center=None, target=None):
    tanks_common = []
    tanks_cache_lifetime = app.config['TANKS_CACHE_INFO_LIFETIME']
    cache_result = get_from_cache('conductor_tanks_{}_{}'.format(data_center, target))

    if not cache_result:
        try:
            tanks = make_request(app.config['PUBLIC_TANKS_CONDUCTOR_URL'])
        except ConnectionError:
            logger.error('Unable to get Hardware public tanks list')
        else:
            if tanks is not None and tanks.status_code == 200:
                for tank in re.split('\n', tanks.content):
                    if tank:
                        line = re.split(r'\s', tank)
                        tank_info = {
                            'tank': line[0],
                            'port': 8083,
                            'dc': line[1],
                            'service': 'hardware'
                        }

                        if data_center is not None:
                            if line[1] == data_center:
                                tank_info.update({'hostname': target})
                                tanks_common.append(tank_info)
                        else:
                            tanks_common.append(tank_info)
            else:
                logger.error('Can\'t get conductor public tanks')

            set_to_cache('conductor_tanks_{}_{}'.format(data_center, target), tanks_common, tanks_cache_lifetime)

    else:
        tanks_common = cache_result

    return tanks_common
