import datetime
import time
import uuid
from threading import Semaphore

from keble_helpers import ThreadController, threaded


def test_multithread():
    @threaded()
    def some_func(results):
        value = "some_compute_heavy_value"
        results.append(value)

    @threaded()
    def other_func(results):
        value = "some_other_compute_heavy_value"
        results.append(value)

    @threaded()
    def third_func(results):
        value = "another_compute_heavy_value"
        results.append(value)

    threads = []
    results = []
    for _ in range(10):
        threads.append(some_func(results))
        threads.append(other_func(results))
        threads.append(third_func(results))

    # wait for all threads to finish before moving on
    for thread in threads:
        thread.join()
    assert len(results) == 30


def test_multithread_with_sema():
    thread_size = 3
    sema = Semaphore(thread_size)

    @threaded(sema=sema)
    def some_func(results):
        value = "some_compute_heavy_value"
        results.append(value)
        sema.release()

    @threaded(sema=sema)
    def other_func(results):
        value = "some_other_compute_heavy_value"
        results.append(value)
        sema.release()

    @threaded(sema=sema)
    def third_func(results):
        value = "another_compute_heavy_value"
        results.append(value)
        sema.release()

    threads = []
    results = []
    for _ in range(10):
        threads.append(some_func(results))
        threads.append(other_func(results))
        threads.append(third_func(results))

    # wait for all threads to finish before moving on
    for thread in threads:
        thread.join()

    # check semaphore is empty
    assert sema._value == thread_size
    assert len(results) == 30


def test_multithread_by_controller():
    time_sleep_in_thread = 1
    thread_size = 5
    tasks = 10
    controller = ThreadController(thread_size=thread_size)

    def some_func(results):
        value = "some_compute_heavy_value"
        time.sleep(time_sleep_in_thread)
        results.append(value)
        controller.release()

    def other_func(results):
        value = "some_other_compute_heavy_value"
        time.sleep(time_sleep_in_thread)
        results.append(value)
        controller.release()

    def third_func(results):
        value = "another_compute_heavy_value"
        time.sleep(time_sleep_in_thread)
        results.append(value)
        controller.release()

    start_time = datetime.datetime.now().timestamp()
    results = []
    for _ in range(tasks):
        controller.create_thread(target=some_func, args=(results,))
        controller.create_thread(target=other_func, args=(results,))
        controller.create_thread(target=third_func, args=(results,))

    controller.wait_all_to_finish()
    end_time = datetime.datetime.now().timestamp()
    diff = end_time - start_time
    assert diff >= ((tasks * time_sleep_in_thread) / thread_size)
    assert len(results) == tasks * 3


def test_multithread_by_owner():
    time_sleep_in_thread = 1
    thread_size = 5
    tasks = 10
    controller = ThreadController(thread_size=thread_size)
    thread_owner = str(uuid.uuid4())

    def some_func(results):
        value = "some_compute_heavy_value"
        time.sleep(time_sleep_in_thread)
        results.append(value)

        controller.release(thread_owner=thread_owner)

    def other_func(results):
        value = "some_other_compute_heavy_value"
        time.sleep(time_sleep_in_thread)
        results.append(value)
        controller.release(thread_owner=thread_owner)

    def third_func(results):
        value = "another_compute_heavy_value"
        time.sleep(time_sleep_in_thread)
        results.append(value)
        controller.release(thread_owner=thread_owner)

    start_time = datetime.datetime.now().timestamp()
    results = []
    for _ in range(tasks):
        controller.create_thread(
            target=some_func, args=(results,), thread_owner=thread_owner
        )
        controller.create_thread(
            target=other_func, args=(results,), thread_owner=thread_owner
        )
        controller.create_thread(
            target=third_func, args=(results,), thread_owner=thread_owner
        )

    controller.wait_owner_to_finish(thread_owner=thread_owner)
    end_time = datetime.datetime.now().timestamp()
    diff = end_time - start_time
    # assert diff >= ((tasks * time_sleep_in_thread) / thread_size)
    assert len(results) == tasks * 3
