package com.datavirtuality.dv.core.interfaces.concurrent;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @author Alexander Fischer
 * @date 2025.08.28
 */

public class TestObservableLock {

    @Test
    @Timeout(value = 10, unit = TimeUnit.SECONDS)
    public void tryLockGuard_timesOutWhenBusy() throws Exception {
        ObservableLock lock = new ObservableLock("TEST_LOCK", 10_000L);

        CountDownLatch holderHasLock = new CountDownLatch(1);

        Thread holder = new Thread(() -> {
            try (ObservableLock.Guard guard = lock.lockGuard("holder")) {
                holderHasLock.countDown(); // signal that the lock is acquired
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }, "holder");
        holder.start();

        // wait until the holder definitely owns the lock
        assertTrue(holderHasLock.await(1, TimeUnit.SECONDS), "holder didn't acquire the lock in time");

        long started = System.currentTimeMillis();
        IllegalStateException ex = assertThrows(IllegalStateException.class, () -> {
            try {
                lock.tryLockGuard(100, "contender"); // should time out and throw
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                fail("Unexpected interruption");
            }
        });
        long elapsed = System.currentTimeMillis() - started;

        // allow some CI jitter; we expect ~100ms wait, definitely < 1s
        assertTrue(elapsed >= 100 && elapsed < 1_000, "elapsed time should be ~ lock attempt timeout");
        assertNotNull(ex.getMessage());

        holder.join();
    }

    @Test
    @Timeout(value = 10, unit = TimeUnit.SECONDS)
    public void warnThreshold_onlyLogs_notAFailure() {
        // Holding the lock longer than warnThreshold should log a warning
        // but should not throw or fail release
        ObservableLock lock = new ObservableLock("TEST_LOCK", 50L);
        assertDoesNotThrow(() -> {
            try (ObservableLock.Guard guard = lock.lockGuard("long-hold")) {
                try {
                    Thread.sleep(70); // exceed threshold
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
    }

    @Test
    @Timeout(value = 10, unit = TimeUnit.SECONDS)
    public void standardLockInterface_usage() {
        // Declare as standard interface
       Lock lock = new ObservableLock("STD_IFACE");

        // Acquire and release via standard API
        lock.lock();
        try {
            assertTrue(((ObservableLock) lock).isHeldByCurrentThread(), "Lock should be held by current thread");
            assertTrue(((ObservableLock) lock).isLocked(), "Lock should report as locked");
        } finally {
            lock.unlock();
        }

        assertFalse(((ObservableLock) lock).isLocked(), "Lock should be released");
        assertEquals(0, ((ObservableLock) lock).getHoldCount(), "Hold count should be reset to 0");
        assertNull(((ObservableLock) lock).getOwnerThread(), "Owner should be cleared after release");
    }
}
