package com.livingworld.core.services;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

/**
 * Unit tests for {@link ServiceRegistry}.
 */
class ServiceRegistryTest {

    // ------------------------------------------------------------------
    // Registration and retrieval
    // ------------------------------------------------------------------

    @Nested
    @DisplayName("Registration and retrieval")
    class RegistrationAndRetrieval {

        @Test
        void registerAndGet() {
            var registry = new ServiceRegistry();
            var key = new ServiceKey<>("test-service", String.class);
            registry.register(key, "hello");
            String result = registry.get(key);
            assertEquals("hello", result);
        }

        @Test
        void findReturnsPresentService() {
            var registry = new ServiceRegistry();
            var key = new ServiceKey<>("test-service-2", Integer.class);
            registry.register(key, 42);
            var found = registry.find(key);
            assertTrue(found.isPresent());
            assertEquals(42, found.get());
        }

        @Test
        void findReturnsEmptyForUnregistered() {
            var registry = new ServiceRegistry();
            var key = new ServiceKey<>("unregistered", Boolean.class);
            var found = registry.find(key);
            assertFalse(found.isPresent());
        }

        @Test
        void isRegisteredReturnsTrueAfterRegistration() {
            var registry = new ServiceRegistry();
            var key = new ServiceKey<>("test-service-3", Object.class);
            registry.register(key, new Object());
            assertTrue(registry.isRegistered(key));
        }

        @Test
        void isRegisteredReturnsFalseForUnregistered() {
            var registry = new ServiceRegistry();
            var key = new ServiceKey<>("unregistered-2", Number.class);
            assertFalse(registry.isRegistered(key));
        }
    }

    // ------------------------------------------------------------------
    // Null argument validation
    // ------------------------------------------------------------------

    @Nested
    @DisplayName("Null argument validation")
    class NullArgumentValidation {

        @Test
        void registerWithNullKeyThrows() {
            var registry = new ServiceRegistry();
            assertThrows(IllegalArgumentException.class, () -> registry.register(null, "service"));
        }

        @Test
        void registerWithNullServiceThrows() {
            var registry = new ServiceRegistry();
            var key = new ServiceKey<>("null-service", String.class);
            assertThrows(IllegalArgumentException.class, () -> registry.register(key, null));
        }

        @Test
        void getWithNullKeyThrows() {
            var registry = new ServiceRegistry();
            assertThrows(IllegalArgumentException.class, () -> registry.get(null));
        }

        @Test
        void findWithNullKeyThrows() {
            var registry = new ServiceRegistry();
            assertThrows(IllegalArgumentException.class, () -> registry.find(null));
        }

        @Test
        void isRegisteredWithNullKeyThrows() {
            var registry = new ServiceRegistry();
            assertThrows(IllegalArgumentException.class, () -> registry.isRegistered(null));
        }
    }

    // ------------------------------------------------------------------
    // Duplicate registration rejection
    // ------------------------------------------------------------------

    @Nested
    @DisplayName("Duplicate registration rejection")
    class DuplicateRegistrationRejection {

        @Test
        void duplicateKeyThrows() {
            var registry = new ServiceRegistry();
            var key = new ServiceKey<>("duplicate", String.class);
            registry.register(key, "first");
            IllegalStateException exception = assertThrows(IllegalStateException.class, () -> registry.register(key, "second"));
            assertTrue(exception.getMessage().contains("already registered"));
        }

        @Test
        void differentKeysDoNotConflict() {
            var registry = new ServiceRegistry();
            var key1 = new ServiceKey<>("key-1", String.class);
            var key2 = new ServiceKey<>("key-2", Integer.class);
            registry.register(key1, "hello");
            registry.register(key2, 42);
            assertEquals("hello", registry.get(key1));
            assertEquals(42, registry.get(key2));
        }
    }

    // ------------------------------------------------------------------
    // Lock mechanism
    // ------------------------------------------------------------------

    @Nested
    @DisplayName("Lock mechanism")
    class LockMechanism {

        @Test
        void isLockedReturnsFalseByDefault() {
            var registry = new ServiceRegistry();
            assertFalse(registry.isLocked());
        }

        @Test
        void lockSetsLockedState() {
            var registry = new ServiceRegistry();
            registry.lock();
            assertTrue(registry.isLocked());
        }

        @Test
        void registerAfterLockThrows() {
            var registry = new ServiceRegistry();
            registry.lock();
            var key = new ServiceKey<>("after-lock", String.class);
            IllegalStateException exception = assertThrows(IllegalStateException.class, () -> registry.register(key, "service"));
            assertTrue(exception.getMessage().contains("locked"));
        }

        @Test
        void registerBeforeLockThenRegisterAfterThrows() {
            var registry = new ServiceRegistry();
            var key1 = new ServiceKey<>("before-lock", String.class);
            registry.register(key1, "before");
            registry.lock();
            var key2 = new ServiceKey<>("after-lock-2", Integer.class);
            assertThrows(IllegalStateException.class, () -> registry.register(key2, 42));
        }

        @Test
        void getAndFindWorkAfterLock() {
            var registry = new ServiceRegistry();
            var key = new ServiceKey<>("locked-service", String.class);
            registry.register(key, "locked");
            registry.lock();
            assertEquals("locked", registry.get(key));
            assertTrue(registry.find(key).isPresent());
        }
    }

    // ------------------------------------------------------------------
    // get() throws IllegalStateException for missing service
    // ------------------------------------------------------------------

    @Nested
    @DisplayName("get() throws IllegalStateException for missing service")
    class GetMissingThrows {

        @Test
        void getUnregisteredServiceThrows() {
            var registry = new ServiceRegistry();
            var key = new ServiceKey<>("missing", String.class);
            IllegalStateException exception = assertThrows(IllegalStateException.class, () -> registry.get(key));
            assertTrue(exception.getMessage().contains("No service registered"));
        }
    }

    // ------------------------------------------------------------------
    // No Minecraft dependencies
    // ------------------------------------------------------------------

    @Nested
    @DisplayName("Plain Java - no Minecraft imports")
    class PlainJava {

        @Test
        void registryIsPureJava() {
            var registry = new ServiceRegistry();
            var key = new ServiceKey<>("plain", String.class);
            registry.register(key, "test");
            assertEquals("test", registry.get(key));
        }
    }
}