import multiprocessing
import sys
import threading
import time

import pytest

from gri_utils.resources.memtest import (
    ResourceResult,
    get_cpu_cores,
    get_memory_mb,
    hhmmss,
    resource_worker,
)
from gri_utils.resources.returnable_thread import ReturnableThread

### Note: pytest coverage doesn't show coverage of child thread portions (because they
# are happening in other processes), so coverage may not accurately show what is being
# tested in this suite


def test_hhmmss():
    assert hhmmss(0.0001) == "00.000 (ss.sss)"
    assert hhmmss(0.001) == "00.001 (ss.sss)"
    assert hhmmss(4.001) == "04.001 (ss.sss)"
    assert hhmmss(34.001) == "34.001 (ss.sss)"
    assert hhmmss(121.001) == "02:01.001 (mm:ss.sss)"
    assert hhmmss(60 * 60 + 1 * 60 + 1.001) == "1:01:01.001 (hh:mm:ss.sss)"
    assert hhmmss(60 * 60 * 200 + 1 * 60 + 1.001) == "200:01:01.001 (hh:mm:ss.sss)"


STOP_CPU_THREAD = False


def cpu_thread(delay: float = 10.0):
    global STOP_CPU_THREAD  # noqa: PLW0602
    t = time.time()
    a = 0
    while time.time() < t + delay and not STOP_CPU_THREAD:
        a += 1
    return a


def test_get_cpu_cores():
    global STOP_CPU_THREAD  # noqa: PLW0603

    t = threading.Thread(target=cpu_thread)
    t.start()
    cores, process = get_cpu_cores()
    assert cores == 0
    time.sleep(0.1)
    cores, _ = get_cpu_cores(process)
    assert cores > 0.5
    STOP_CPU_THREAD = True
    t.join()


def test_get_mem():
    mem, process = get_memory_mb()
    assert mem > 1
    ints: list[int] = [0]
    ii = 123
    b = sys.getsizeof(ii)
    ints.extend(range(int(2 * 1024 * 1024 / b)))
    mem2, _ = get_memory_mb(process)
    assert mem2 >= mem + 1


def test_resource_result():
    rr = ResourceResult(12.34567, 12345.12345, 0.012345)
    assert rr[0] == pytest.approx(12.34567)
    assert rr[1] == pytest.approx(12345.12345)
    assert rr[2] == pytest.approx(0.012345)
    assert rr.first == rr[0]
    assert rr.max == rr[1]
    assert rr.last == rr[2]
    assert str(rr) == "[12.3=>(12345)=>0.0123]"


def test_worker():
    exit_event = threading.Event()
    rt = ReturnableThread[tuple[ReturnableThread, ReturnableThread]](
        target=resource_worker,
        args=(exit_event,),
    )
    rt.start()
    time.sleep(0.1)
    exit_event.clear()
    exit_event.set()
    mem, cpu = rt.join()
    assert len(str(mem)) > 0
    assert len(str(cpu)) > 0


def test_multiprocessing():
    exit_event = threading.Event()
    rt = ReturnableThread[tuple[ReturnableThread, ReturnableThread]](
        target=resource_worker,
        args=(exit_event, 0.1),
    )
    rt.start()
    p1 = multiprocessing.Process(target=cpu_thread, args=(9.4,))
    p2 = multiprocessing.Process(target=cpu_thread, args=(9.4,))
    p1.start()
    p2.start()
    time.sleep(0.3)
    exit_event.clear()
    exit_event.set()
    mem, cpu = rt.join()
    p1.join()
    p2.join()
    assert len(str(mem)) > 0
    assert len(str(cpu)) > 0
