import yaml
import json
from typing import Union, Tuple
import logging
from configparser import Error as ConfigParserError

from common.models import Component, Server, MobileJobDataKey
from mobilepage.models import MobileJob
from firestarter.models import JobTank
from common.util.meta import Ini2Json, Json2Ini
from common.util.clients import MDSClient, link_and_create_task
from firestarter.views.tank_finder import TankFinder


class BadRequest(Exception):
    pass


def transform_config(config_initial: Union[str, dict]) -> Tuple[dict, str]:

    if isinstance(config_initial, str) and config_initial.startswith('https://storage-int.mds.yandex.net'):
        mds_client = MDSClient()
        config_initial = mds_client.get(config_initial)

    if isinstance(config_initial, dict):
        config = config_initial
        config['configinitial'] = config_initial
    else:
        try:
            config = yaml.safe_load(config_initial.replace('!!python/unicode ', ''))
            config['configinitial'] = config_initial
        except yaml.YAMLError:
            try:
                config = Ini2Json(config_initial).convert()
                config['configinitial'] = Json2Ini(config).convert()
                logging.warning('Ini config format is deprecated')
            except AttributeError:
                raise BadRequest('json config must not contain nested data structures')
            except ConfigParserError:
                logging.error('Config parsing error', exc_info=True)
                raise BadRequest('Config could not be parsed')
        except AttributeError:
            logging.error('Config parsing error', exc_info=True)
            raise BadRequest('Config could not be parsed')

    return extract_data_from_config(config)


def extract_data_from_config(dict_config: dict) -> Tuple[dict, str]:
    """Create json from config for Job creation, extract tanks for BDK requests"""
    data = {}

    config_task = dict_config.get('uploader', {}).get('task', '')
    task = link_and_create_task('', config_task)
    component = Component.objects.get_or_create(
        name=dict_config.get('meta', {}).get('component', ''), tag=task.key.split('-')[0])[0].n

    tanks = dict_config.get('uploader', {}).get('meta', {}).get('use_tank', '')

    if data.get('n'):
        data.update({
            'id': 'n',
            'mobile_data_key': None,
            'mobile_job': None,
        })
        mobile_data_key = MobileJobDataKey.objects.filter(job=data['n'])
        if mobile_data_key.count():
            mdk = mobile_data_key[0].mobile_data_key
            mobile_job = MobileJob.objects.filter(n=mdk) if mdk.isdigit() else MobileJob.objects.filter(test_id=mdk)
            if mobile_job.count():
                data.update({
                    'mobile_data_key': mobile_job[0].test_id,
                    'mobile_job': mobile_job[0].n
                })

    data['srv'], data['srv_port'] = extract_address(dict_config)

    data.update({
        'task': task.key,
        'component': component,
        'person': dict_config.get('meta', {}).get('operator', ''),
        'name': dict_config.get('meta', {}).get('job_name', ''),
        'dsc': dict_config.get('meta', {}).get('job_dsc', ''),
        'status': 'queued',
        'ver': dict_config.get('meta', {}).get('ver', ''),
        'instances': dict_config.get('phantom', {}).get('instances', 1000),
        'ammo_path': dict_config.get('phantom', {}).get('ammofile', ''),
        'finalized': False,
        'configinitial': dict_config.get('configinitial')
    })

    return data, tanks


def extract_address(config: dict) -> Tuple[Server, int]:
    # TODO: extract address from pandora
    target = config.get('phantom', {}).get('address')
    port = 80
    address = config.get('phantom', {}).get('address', '') or config.get('bfg', {}).get('address', '')
    if address:
        target, port = extract_target_and_port(address)

    if target is not None:
        target_approval = TankFinder(target).approve_target(check_lock=False)['success']
        if not target_approval:
            raise BadRequest(target_approval['error'])
    new_server = Server.objects.get_or_create(host=target)[0]

    return new_server, port


def extract_target_and_port(address: str) -> Tuple[str, int]:
    port = 80
    ipv6 = address.count(':') > 1
    if ipv6:
        try:
            address.index(']:')
            port = address.split(':')[-1]
            host = ':'.join(address.split(':')[:-1]).replace('[', '').replace(']', '')
        except ValueError:
            host = address.replace('[', '').replace(']', '')
    else:
        host = address.split(':')[0].replace('[', '').replace(']', '')
        if ':' in address:
            port = address.split(':')[-1]
    assert isinstance(host, str)
    # TODO: check port is int
    port = int(port)
    return host, port


def extract_tanks(use_tanks: str, job_id: int) -> None:
    use_tanks = use_tanks.split(',')
    for tank in use_tanks:
        server = Server.objects.get_or_create(host=tank.strip())[0]
        JobTank.objects.create(job_id=job_id, tank=server)

