import logging
import asyncio
from concurrent.futures import ThreadPoolExecutor
# hints
from typing import List, Dict
# locals
from .configurator import CHECounter
from .calculator import CalculationEntity
# from .configurator import CustomJob

log = logging.getLogger('calc/async')

async def async_handle_targets(
    ches: List[CalculationEntity],
    #jobs: List[CustomJob] or None = None,
    max_concurrent: int = 5
):
    # start this linke asyncio.run(async_handle(calculationEntities, jobs=[CEDJob.DEFAULT], max_concurrent=2))
    """Single async function handles everything for 3.9 compatibility."""

    loop = asyncio.get_running_loop()
    results: Dict[str, CHECounter] = {}
    errors: Dict[str, Exception] = {}

    # Semaphore created INSIDE async context (3.9 requirement)
    sem = asyncio.Semaphore(max_concurrent)
    total = 0
    async def worker(ce: CalculationEntity, count) -> None:
        async with sem:
            try:
                # run_in_executor is fully supported in 3.9
                res = await loop.run_in_executor(
                    ThreadPoolExecutor(max_workers=max_concurrent),
                    ce.calculate_targets # excecutes all jobs
                )
                results[f'{ce}'] = ce.che.counter
                log.info(f'results worker {count}/{total} => {ce}: {ce.che.counter}')
            except Exception as exc:
                log.exception(f'error worker {ce}')
                errors[f'{ce}'] = exc


    # Process all instances with concurrency limit
    tasks = [worker(che, i) for i, che in enumerate(ches)]  # [:5]
    total = len(tasks)
    # task_timeout = 300
    # tasks = [asyncio.wait_for(worker(che), timeout=task_timeout) for che in ches]  # [:5]

    # with ThreadPoolExecutor(max_workers=max_concurrent) as executor:
    # loop.set_default_executor(executor)  # 3.9 compatible
    try:
        await asyncio.gather(*tasks)
    except asyncio.CancelledError:
        print("\nCancelled - cleaning up...")
        raise

    r_count = len(results)
    r_runtime = sum([r.runtime for k, r in results.items()]) #ms
    r_avg = r_runtime / r_count if r_count else 0
    log.info(
        f'Results: Processed {r_count} devices for {r_runtime}ms ({r_avg}ms)')
    # print("Results:", json.dumps(results, indent=2))
    for k, r in errors.items():
        log.warning(f'error in process of {k}: {r}')

# test for good concurrent values
# {1: 52.31, 2: 54.02, 3: 18.15, 4: 21.31, 5: 10.02, 6: 12.34, 7: 9.71, 8: 11.82, 9: 12.44} # 10 devices
# {1: 64.89, 2: 29.35, 3: 21.07, 4: 15.06, 5: 13.4, 6: 11.5, 7: 9.62, 8: 9.53, 9: 9.16} #20 devices
# {5: 72.01, 6: 64.05, 7: 67.29, 8: 57.8, 9: 50.79, 10: 49.7, 20: 42.8, 30: 44.1} #100 devices