import re
from flask import current_app

from saas.tools.abcd.app.app import prepare_flask_app
from saas.tools.abcd.models.models import db, Account, SaasService, NannyService, Segment, \
    ResourceType, Resource, Provision
from saas.tools.abcd.models.database_handles import get_instance, get_or_create, create_or_update


def fixup_resource_name(res_name):
    RESOURCE_FIXES = {r'cpu.*limit': 'cpu_limit', r'hdd.*bw': 'hdd_read_bw', r'ssd.*bw': 'ssd_read_bw'}
    res_name = res_name.strip().lower()
    for pattern, replacement in RESOURCE_FIXES.items():
        res_name = re.sub(pattern, replacement, res_name)
    return res_name


def get_resource_info(res_name):
    RESOURCE_UNITS = {r'cpu': 'millicores', r'(ssd|hdd|ram)': 'gibibytes', r'bw': 'mebibytesPerSecond'}
    res_name = fixup_resource_name(res_name)
    ret = [(res_name, unit) for pat, unit in RESOURCE_UNITS.items() if re.match(pat, res_name)]
    return ret[-1]


def convert_unit(amount, dst_unit, src_unit):
    UNIT_MULT = {
        'exb': 2**60, 'peb': 2**50, 'teb': 2**40, 'gib': 2**30, 'meb': 2**20, 'kib': 2**10,
        'exa': 10**18, 'pet': 10**15, 'ter': 10**12, 'gig': 10**9, 'meg': 10**6, 'kil': 10**3,
        'mil': 10**-3,
    }
    dst_unit, src_unit = dst_unit.strip().lower()[:3], src_unit.strip().lower()[:3]
    return round(amount / UNIT_MULT.get(dst_unit, 1) * UNIT_MULT.get(src_unit, 1))


def _get_resource_instance(res_name):
    resource, default_unit = get_resource_info(res_name)
    resource_instance = get_instance(db, ResourceType, key=resource)
    return resource_instance or get_or_create(db, ResourceType, key=resource, unit=default_unit)


class SaaSUsageConnector():
    app = prepare_flask_app(env_type="production")

    @classmethod
    def set_rusage(cls, rusage):
        """
        Write resource usage into ABCD database.
        :param rusage: type [{
            'service_name': str,
            'service_ctype': str,
            'service_owner': str,
            'service_abc': {
                'id': int,
                'name': str,
                'hr_name': str
            },
            'quota_abc': {
                'id': int,
                'name': str,
                'hr_name': str
            },
            'usage_per_nanny': [{
                '<location>': {'<resource_type>': int(bytes|bytesPerSecond|millicores), ...}, ...
            }],
            'nanny_services': [str],
        }]
        :return:
        """
        with cls.app.app_context():
            try:
                db.session.query(Resource).delete()
                db.session.query(NannyService).delete()
                db.session.query(SaasService).delete()
                db.session.commit()
            except:
                db.session.rollback()
                current_app.logger.error('Failed to delete old table entries')
                return
            for p in rusage:
                abc_id = int(p['service_abc']['id'])
                account_instance = get_instance(db, Account, abc_service_id=abc_id)
                if not account_instance:
                    current_app.logger.info(f'No user with ABC service ID: {abc_id}')
                    continue
                service_args = {
                    'name': p['service_name'],
                    'ctype': p['service_ctype'],
                    'owner': p['service_owner'],
                    'account_id': account_instance.id,
                    'abc_service_quota': int(p['quota_abc']['id']),
                    'abc_service_user': abc_id,
                    'abc_name': p['service_abc']['name'],
                    'abc_hr_name': p['service_abc']['hr_name'],
                    'abc_quota_name': p['quota_abc']['name'],
                    'abc_quota_hr_name': p['quota_abc']['hr_name'],
                }
                saas_instance = get_or_create(db, SaasService, **service_args)
                for nanny_usage, nanny_name in zip(p['usage_per_nanny'], p['nanny_services']):
                    nanny_instance = get_or_create(db, NannyService,
                                                   name=nanny_name,
                                                   saas_service_id=saas_instance.id,
                                                   account_id=account_instance.id)
                    for loc, resources in nanny_usage.items():
                        loc = loc.strip().lower()
                        segment_instance = get_instance(db, Segment, key=loc)
                        if not segment_instance:
                            current_app.logger.info(f'No location: {loc}')
                            continue
                        for resource, value in resources.items():
                            src_unit = 'millicores' if 'cpu' in resource.lower() else 'bytes'
                            value, resource_instance = int(value), _get_resource_instance(resource)
                            resource_args_min = {
                                'key': resource_instance.key,
                                'nanny_service_id': nanny_instance.id,
                                'segment_id': segment_instance.id,
                                'resource_type_id': resource_instance.id,
                            }
                            resource_args_full = {
                                **resource_args_min,
                                'amount': convert_unit(value, resource_instance.unit, src_unit),
                            }
                            create_or_update(db, Resource, resource_args_min, resource_args_full)
            try:
                for provision_instance in db.session.query(Provision).all():
                    provision_instance.allocated_amount = provision_instance.get_allocated_amount()
                db.session.commit()
            except:
                db.session.rollback()
                current_app.logger.error('Failed to set allocated amount for all rows.')
