
import os
import re
import logging
import requests
import tempfile

from retry import retry
from sandbox import sdk2
from sandbox.sdk2.vcs.svn import Arcadia


TANK_FINDER = 'https://tank-finder.yandex-team.ru'
SANDBOX = 'https://sandbox.yandex-team.ru'
LUNAPARK = 'https://lunapark.yandex-team.ru'


class ShootingError(Exception):
    def __init__(self, text):
        self.txt = text


def check_for_arc_path(path):
    try:
        return Arcadia.check(Arcadia.trunk_url(path))
    except Exception as ex:
        logging.debug('[CHECK ARC PATH] Path %s is not from arcadia. Exception type %s', path, type(ex), exc_info=True)


@retry(tries=3)
def read_arcfile(arc_path):
    local_file = os.path.join(tempfile.gettempdir(), next(tempfile._get_candidate_names()))
    Arcadia.export(Arcadia.trunk_url(arc_path), local_file, depth='empty')
    with open(local_file, 'r') as config:
        return config.read()


@retry(tries=3)
def read_urlfile(url_link):
    result = requests.get(url_link, stream=True)
    if result.status_code == 200:
        return result.content
    else:
        raise ShootingError(f'The link {url_link} is incorrect or outdated')


#
# PREPARE CONFIG
#
def prepare_config(config_content):

    logging.info(f'[PREPARE_CONFIG] Input tasklet config: {config_content}')
    use_public_tanks = True

    if not isinstance(config_content, dict):
        raise ShootingError('Wrong config type')

    if 'phantom' in config_content and config_content['phantom'].get('enabled', True):
        config_content['phantom'] = prepare_phantom(config_content['phantom'])
        if 'multi' in config_content['phantom']:
            for order, section in enumerate(config_content['phantom']['multi']):
                config_content['phantom']['multi'][order] = prepare_phantom(section)

    if 'pandora' in config_content and config_content['pandora'].get('enabled', True):
        config_content['pandora'] = prepare_pandora(config_content['pandora'])

    if 'metaconf' in config_content and config_content['metaconf'].get('enabled', True):
        use_public_tanks = not bool(config_content['metaconf'].get('firestarter', {}).get('tank', ''))

    if 'use_tank' in config_content.get('uploader', {}).get('meta', {}):
        use_public_tanks = False
        config_content['uploader']['meta']['use_tank'] = get_host(config_content['uploader']['meta']['use_tank'])

    if use_public_tanks:
        config_content['metaconf'] = config_content.get('metaconf') \
            if config_content.get('metaconf', False) \
                else {'enabled': True,
                        'package': 'yandextank.plugins.MetaConf'}

        config_content['metaconf']['firestarter'] = config_content['metaconf'].get('firestarter') \
            if config_content['metaconf'].get('firestarter', False) else {}

        config_content['metaconf']['firestarter'].update({'tank': 'common'})

    logging.info(f'[PREPARE_CONFIG] Output tasklet config: {config_content}')

    return config_content


def prepare_phantom(config):
    if isinstance(config, dict):
        attrs = {}
        if 'address' in config:
            config['address'] = get_host(config['address'])
        if 'ammo_attrs' in config:
            attrs = get_attrs(config.pop('ammo_attrs'))
        if 'ammofile' in config and re.match(r'^sandbox.', config['ammofile']):
            config['ammofile'] = get_resource_url(config['ammofile'].split('.')[1], attrs)
    else:
        logging.error(f'[PREPARE_PHANTOM] The transmitted configuration section is not a dictionary! {config}')
    return config


def prepare_pandora(config):
    if isinstance(config, dict):
        for order, resource in enumerate(config.get('resources', [])):
            attrs = {}
            if 'ammo_attrs' in resource:
                attrs = get_attrs(config['resources'][order].pop('ammo_attrs'))
            if re.match(r'^sandbox.', resource['src']):
                config['resources'][order]['src'] = get_resource_url(resource['src'].split('.')[1], attrs)

        for order, pool in enumerate(config.get('config_content', {}).get('pools', [])):
            target = pool.get('gun', {}).get('target', '')
            if target:
                config['config_content']['pools'][order]['gun']['target'] = get_host(target)
    else:
        logging.error(f'[PREPARE_PANDORA] The transmitted configuration section is not a dictionary! {config}')
    return config


def get_host(target):
    if isinstance(target, str):
        if re.match(r'^deploy:', target):
            target = get_deploy_target(target)
        elif re.match(r'^nanny:', target):
            target = get_nanny_target(target)

    elif isinstance(target, dict):
        target = get_deploy_target(target)

    elif isinstance(target, list):
        targets = []
        for item in target:
            targets.extend(get_host(item))
        target = targets

    return target


def apply_option(config, option):
    config_way, value = option.split('=', maxsplit=1)
    config_way = config_way.split('.')
    return update_config(config, config_way, value)


def update_config(config, keys, value):
    if not isinstance(keys, list):
        return config
    else:
        key = check_key(keys[0])
        config = check_config(config, keys)
    if len(keys) == 1:
        config[key] = value
    else:
        config[key] = update_config(config[key], keys[1:], value)
    return config


def check_config(config, keys):
    key = check_key(keys[0])
    key_next = check_key(keys[1]) if len(keys) > 1 else None

    if isinstance(config, dict):
        if config.get(key):
            pass
        elif isinstance(key_next, int):
            config[key] = []
        elif key_next:
            config[key] = {}

    elif isinstance(config, list):
        if isinstance(key, str):
            raise ShootingError(f'{key} is not suitable as a list index it must be an int')
        if isinstance(key_next, int):
            raise ShootingError('Double nested list is not supported in tank configuration')
        if len(config) < key:
            raise ShootingError('The index of the updated element is beyond the scope of the possible')
        elif len(config) == key:
            config.append({})

    else:
        raise ShootingError(f'Configuration {config} has unsupported format')

    return config


def check_key(key):
    return int(key) if str(key).isdigit() else str(key)


#
# TANK-FINDER REQUESTS
#
@retry(tries=3)
def get_nanny_target(target):
    nanny_vars = get_nanny_vars(target)
    try:
        resp = requests.get(f'{TANK_FINDER}/nanny?{nanny_vars}')
        return resp.json()['fqdn'][0]
    except (requests.HTTPError, TypeError, KeyError):
        logging.error('[GET_NANNY_TARGET] ', target, exc_info=True)
        raise ShootingError(f'FQDN for RTC object {target} is not found')


def get_nanny_vars(target):
    service, group, dc = (re.sub(r'^nanny:', '', target).split('.') + ['', ''])[:3]
    logging.info('[GET_NANNY_VARS] RTC service %s, group %s and datacenter %s', service, group, dc)
    return f'service={service}&group={group}&dc={dc}'


def get_deploy_target(target):
    if isinstance(target, str):
        target = re.sub(r'^deploy:', '', target)
        port = target.split(':')[-1] if ':' in target else None
        host = _get_deploy_fqdn(_parse_deploy_string(target.split(':')[0]))

    elif isinstance(target, dict):
        port = target.get('port')
        host = _get_deploy_fqdn(_parse_deploy_dict(target))

    else:
        raise ShootingError('Wrong config for the deploy\'s searching')

    return f'{host}:{port}' if str(port).isdigit() else host


@retry(tries=3)
def _get_deploy_fqdn(deploy_vars):
    request_url = f'{TANK_FINDER}/deploy?{deploy_vars}'
    try:
        responce = requests.get(request_url, stream=True).json()
        return responce['fqdn'][0]

    except (requests.RequestException, ValueError) as error:
        raise ShootingError('Failed get FQDN from tank finder') from error
    except (IndexError, KeyError) as error:
        raise ShootingError('FQDN for deploy object is not found') from error


def _parse_deploy_string(deploy_str):
    if len(deploy_str) == 0 or deploy_str.split('.')[0] == '':
        raise ShootingError(f'Wrong deploy parameters in target {deploy_str}')
    else:
        stage, unit, dc = (deploy_str.split('.') + ['', ''])[:3]
        return f'stage={stage}&unit={unit}&dc={dc}'


def _parse_deploy_dict(deploy_dict):
    if 'stage' in deploy_dict:
        stage = deploy_dict['stage']
        unit = deploy_dict.get('deploy_unit', '')
        dc = deploy_dict.get('data_center', '')
        return f'stage={stage}&unit={unit}&dc={dc}'
    else:
        raise ShootingError(f'Deploy stage is not specified in target {deploy_dict}')


#
#  SANDBOX REQUESTS
#
def get_attrs(ammo_attrs):
    try:
        return {attr.split('=')[0]: attr.split('=')[1] for attr in ammo_attrs.split(' ')}
    except AttributeError:
        logging.error('[GET_ATTRS] Ammo attributes %s is not string', ammo_attrs)
        return {}
    except (ValueError, IndexError):
        logging.error('[GET_ATTRS] Can\'t parse string %s by resource attributes', ammo_attrs, exc_info=True)
        return {}


def get_resource_url(resource_type, attrs=None):
    attrs = attrs or {}
    ammo_url = sdk2.Resource.find(type=resource_type, attrs=attrs).first().http_proxy
    if ammo_url:
        return ammo_url
    else:
        raise ShootingError(f'Resource {resource_type} is not found.')


def get_group(login):
    group_url = f'{SANDBOX}/api/v1.0/group?limit=1&user={login}'
    headers = {'Accept': 'application/json', 'Accept-Charset': 'utf-8'}
    try:
        response = requests.get(group_url, headers=headers)
        return response.json()['items'][0].get('name', login) if response.json().get('items', False) else login

    except requests.RequestException:
        logging.error('[GET_GROUP] Can\'t get group for the %s. Sandbox doesn\'t answer.', login, exc_info=True)
    except (TypeError, IndexError, KeyError):
        logging.error('[GET_GROUP] Can\'t get group for the %s. Wrong answer from sandbox: %s.', login, response, exc_info=True)

    return login


#
# LUNAPARK REQUESTS
#
def get_shooting_summary(shooting_id):
    summary_url = f'{LUNAPARK}/api/job/{shooting_id}/summary.json'
    headers = {'Accept': 'application/json', 'Accept-Charset': 'utf-8'}
    try:
        return requests.get(summary_url, headers=headers)
    except requests.RequestException:
        logging.error('[GET_SHOOTING_SUMMARY] Can\'t get summary for the shooting %s from the Lunapark.', shooting_id, exc_info=True)


def check_shooting_td(shooting_id):
    try:
        return get_shooting_summary(shooting_id).json()[0]['td'] != 'None'
    except (ValueError, IndexError, KeyError):
        logging.error('[CHECK_SHOOTING_TD] Wrong summary data for the shooting %s', shooting_id, exc_info=True)


def check_shooting_quit_status(shooting_id):
    try:
        return get_shooting_summary(shooting_id).json()[0]['quit_status'] == 0
    except (ValueError, IndexError, KeyError):
        logging.error('[CHECK_QUIT_STATUS] Wrong summary data for the shooting %s', shooting_id, exc_info=True)
