import json
import itertools
from collections import defaultdict

from intranet.trip.src.config import settings
from intranet.trip.src.cache import Cache
from intranet.trip.src.unit_of_work import UnitOfWork


async def get_count_pt_group_by_status(uow: UnitOfWork):
    count_pt_group_by_status = await uow.person_trips.get_count_group_by_status()
    return (
        [f'count_{status}_pt_axxx', count] for status, count in count_pt_group_by_status
    )


async def get_count_services_group_by_status(uow: UnitOfWork):
    count_services_group_by_status = await uow.services.get_count_group_by_status()
    return (
        [f'count_{status}_service_axxx', count] for status, count in count_services_group_by_status
    )


async def get_broken_services_count(uow: UnitOfWork):
    broken_services_count = await uow.services.get_broken_services_count()
    return [['broken_services_count_axxx', broken_services_count]]


async def get_trips_for_staff_push_count(uow: UnitOfWork):
    trips_for_staff_push_count = await uow.trips.get_trips_for_staff_push_count()
    return [['trips_for_staff_push_count_axxx', trips_for_staff_push_count]]


async def get_person_trips_for_aeroclub_create_count(uow: UnitOfWork):
    person_trips_for_aeroclub_create_count = (
        await uow.person_trips.get_person_trips_for_aeroclub_create_count()
    )
    return [['person_trips_for_aeroclub_create_count_axxx', person_trips_for_aeroclub_create_count]]


async def get_not_authorized_person_trips_count(uow: UnitOfWork):
    not_authorized_person_trips_count = (
        await uow.person_trips.get_not_authorized_person_trips_count()
    )
    return [['not_authorized_person_trips_count_axxx', not_authorized_person_trips_count]]


async def get_person_for_ihub_sync_count(uow: UnitOfWork):
    person_for_ihub_sync_count = await uow.persons.get_count_for_ihub_sync()
    return [['person_for_ihub_sync_count_axxx', person_for_ihub_sync_count]]


async def get_person_trips_without_chat_id_count(uow: UnitOfWork):
    person_trips_without_chat_id_count = (
        await uow.person_trips.get_person_trips_without_chat_id_count()
    )
    return [['person_trips_without_chat_id_count_axxx', person_trips_without_chat_id_count]]


async def unistat(uow: UnitOfWork):
    return list(itertools.chain(
        await get_count_pt_group_by_status(uow),
        await get_count_services_group_by_status(uow),
        await get_broken_services_count(uow),

        await get_trips_for_staff_push_count(uow),
        await get_person_trips_for_aeroclub_create_count(uow),
        await get_not_authorized_person_trips_count(uow),
        await get_person_for_ihub_sync_count(uow),
        await get_person_trips_without_chat_id_count(uow),
    ))


def _get_timings(times: list) -> dict:
    times.sort()
    length = len(times)
    if not length:
        return {}
    return {
        'min': min(times),
        'max': max(times),
        'avg': sum(times) / length,
        'median': times[length // 2],
    }


async def get_arq_stats(redis, cache: Cache, function: str = None) -> dict:
    cache_key = 'arq_stats'
    data = await cache.get(cache_key)
    if data is not None:
        stats = json.loads(data)
    else:
        job_results = await redis.all_job_results()
        queued_count = await redis.zcard(settings.REDIS_QUEUE_NAME)
        stats = build_arq_stats_response(
            job_results=job_results,
            queued_count=queued_count,
            function=function,
        )
        await cache.set(
            key=cache_key,
            data=json.dumps(stats),
            expire_after=60,
        )
    return stats


def build_arq_stats_response(job_results: list, queued_count: int, function: str = None) -> dict:
    results = []
    delay_times = []
    execution_times = []
    counts = {
        'total': 0,
        'by_success': defaultdict(int),
        'by_function': defaultdict(int),
        'by_tries': defaultdict(int),
    }
    for result in job_results:
        if function and result.function != function:
            continue

        delay_times.append((result.start_time - result.enqueue_time).total_seconds())
        execution_times.append((result.finish_time - result.start_time).total_seconds())

        counts['total'] += 1
        counts['by_success'][result.success] += 1
        counts['by_function'][result.function] += 1
        counts['by_tries'][result.job_try] += 1

        results.append(result)

    return {
        'queue_name': settings.REDIS_QUEUE_NAME,
        'queued_count': queued_count,
        'counts': counts,
        'timings': {
            'delay': _get_timings(delay_times),
            'execution': _get_timings(execution_times),
        },
    }
