@file:OptIn(ExperimentalCoroutinesApi::class)

package viaduct.engine.runtime.tenantloading

import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertInstanceOf
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
import viaduct.engine.api.NodeResolverExecutor
import viaduct.engine.api.TenantModuleBootstrapper
import viaduct.engine.api.TenantModuleException
import viaduct.engine.api.ViaductSchema
import viaduct.engine.api.mocks.MockCheckerExecutor
import viaduct.engine.api.mocks.MockCheckerExecutorFactory
import viaduct.engine.api.mocks.MockTenantAPIBootstrapper
import viaduct.engine.api.mocks.MockTenantModuleBootstrapper
import viaduct.engine.api.mocks.Samples
import viaduct.engine.api.mocks.mkEngineObjectData
import viaduct.engine.runtime.NodeResolverDispatcherImpl
import viaduct.engine.runtime.instrumentation.resolver.InstrumentedNodeResolverDispatcher
import viaduct.engine.runtime.validation.Validator

/** Tests generated by Claude. */
class DispatcherRegistryAdditionalTests {
    @Test
    fun `handles TenantModuleException gracefully`() {
        val throwingModule = object : TenantModuleBootstrapper {
            override fun fieldResolverExecutors(schema: ViaductSchema) = throw TenantModuleException("Test exception")

            override fun nodeResolverExecutors(schema: ViaductSchema) = emptyList<Pair<String, NodeResolverExecutor>>()
        }
        val workingModule = Samples.mockTenantModule

        val bootstrapper = MockTenantAPIBootstrapper(listOf(throwingModule, workingModule))
        val checkerExecutorFactory = MockCheckerExecutorFactory()
        val registry = DispatcherRegistryFactory(bootstrapper, Validator.Unvalidated, checkerExecutorFactory)
            .create(Samples.testSchema)

        // Should still have resolvers from working module
        assertEquals(6, registry.fieldResolverDispatchers.size)
    }

    @Test
    fun `resolver coordinate collision - last wins`() {
        val module1 = MockTenantModuleBootstrapper(Samples.testSchema) {
            field("TestType" to "aField") {
                resolver {
                    fn { _, _, _, _, _ -> "module1" }
                }
            }
        }
        val module2 = MockTenantModuleBootstrapper(Samples.testSchema) {
            field("TestType" to "aField") {
                resolver {
                    fn { _, _, _, _, _ -> "module2" }
                }
            }
        }

        val bootstrapper = MockTenantAPIBootstrapper(listOf(module1, module2))
        val registry = DispatcherRegistryFactory(bootstrapper, Validator.Unvalidated, MockCheckerExecutorFactory())
            .create(Samples.testSchema)

        assertEquals(1, registry.fieldResolverDispatchers.size)
        // The second module should win - verify the resolver is from module2
        val resolver = registry.getFieldResolverDispatcher("TestType", "aField")
        assertNotNull(resolver)
    }

    @Test
    fun `checker executors only created for existing resolvers`() {
        val checkerFactory = MockCheckerExecutorFactory(
            mapOf(Pair("NonExistentType", "nonExistentField") to MockCheckerExecutor()),
            mapOf(
                "NonExistentNode" to MockCheckerExecutor(),
                "TestNode" to MockCheckerExecutor() // Add checker for existing node
            )
        )

        val bootstrapper = MockTenantAPIBootstrapper(listOf(Samples.mockTenantModule))
        val registry = DispatcherRegistryFactory(bootstrapper, Validator.Unvalidated, checkerFactory)
            .create(Samples.testSchema)

        // Should not have checker executors for non-existent resolvers
        assertNull(registry.getFieldCheckerDispatcher("NonExistentType", "nonExistentField"))
        assertNull(registry.getTypeCheckerDispatcher("NonExistentNode"))

        // But should have checker executors for existing resolvers
        assertNotNull(registry.getTypeCheckerDispatcher("TestNode"))
    }

    @Test
    fun `batch resolver wrapping in NodeResolverDispatcherImpl`() {
        val bootstrapper = MockTenantAPIBootstrapper(listOf(Samples.mockTenantModule))
        val checkerExecutorFactory = MockCheckerExecutorFactory()
        val registry = DispatcherRegistryFactory(bootstrapper, Validator.Unvalidated, checkerExecutorFactory)
            .create(Samples.testSchema)

        val batchResolver = registry.getNodeResolverDispatcher("TestBatchNode")

        assertInstanceOf(InstrumentedNodeResolverDispatcher::class.java, batchResolver)
        val instrumentedDispatcher = batchResolver as InstrumentedNodeResolverDispatcher
        assertInstanceOf(NodeResolverDispatcherImpl::class.java, instrumentedDispatcher.dispatcher)
    }

    @Test
    fun `empty tenant modules handling`() {
        val bootstrapper = MockTenantAPIBootstrapper(emptyList())
        val registry = DispatcherRegistryFactory(bootstrapper, Validator.Unvalidated, MockCheckerExecutorFactory())
            .create(Samples.testSchema)

        assertEquals(0, registry.fieldResolverDispatchers.size)
        assertEquals(0, registry.nodeResolverDispatchers.size)
    }

    @Test
    fun `multiple tenant modules with mixed resolver types`() {
        val module1 = MockTenantModuleBootstrapper(Samples.testSchema) {
            field("TestType" to "field1") {
                resolver {
                    fn { _, _, _, _, _ -> "field1" }
                }
            }
            type("NodeType1") {
                nodeUnbatchedExecutor { id, _, _ ->
                    mkEngineObjectData(
                        Samples.testSchema.schema.getObjectType("TestNode"),
                        mapOf("id" to id)
                    )
                }
            }
        }

        val module2 = MockTenantModuleBootstrapper(Samples.testSchema) {
            field("TestType" to "field2") {
                resolver {
                    fn { _, _, _, _, _ -> "field2" }
                }
            }
            type("BatchNodeType1") {
                nodeBatchedExecutor { selectors, _ ->
                    selectors.associate { selector ->
                        selector to Result.success(
                            mkEngineObjectData(
                                Samples.testSchema.schema.getObjectType("TestNode"),
                                mapOf("id" to selector.id)
                            )
                        )
                    }
                }
            }
        }

        val bootstrapper = MockTenantAPIBootstrapper(listOf(module1, module2))
        val registry = DispatcherRegistryFactory(bootstrapper, Validator.Unvalidated, MockCheckerExecutorFactory())
            .create(Samples.testSchema)

        // Should have both field resolvers
        assertEquals(2, registry.fieldResolverDispatchers.size)
        assertNotNull(registry.getFieldResolverDispatcher("TestType", "field1"))
        assertNotNull(registry.getFieldResolverDispatcher("TestType", "field2"))

        // Should have both node resolvers
        assertEquals(2, registry.nodeResolverDispatchers.size)
        assertNotNull(registry.getNodeResolverDispatcher("NodeType1"))
        assertNotNull(registry.getNodeResolverDispatcher("BatchNodeType1"))

        val batchNodeDispatcher = registry.getNodeResolverDispatcher("BatchNodeType1")
        assertInstanceOf(InstrumentedNodeResolverDispatcher::class.java, batchNodeDispatcher)
        val instrumentedDispatcher = batchNodeDispatcher as InstrumentedNodeResolverDispatcher
        assertInstanceOf(NodeResolverDispatcherImpl::class.java, instrumentedDispatcher.dispatcher)
    }

    @Test
    fun `node resolver and batch resolver do not conflict`() {
        val moduleWithBoth = MockTenantModuleBootstrapper(Samples.testSchema) {
            // Regular node resolver
            type("TestNode") {
                nodeUnbatchedExecutor { id, _, _ ->
                    mkEngineObjectData(
                        Samples.testSchema.schema.getObjectType("TestNode"),
                        mapOf("id" to id, "type" to "regular")
                    )
                }
            }
            // Batch node resolver for different type
            type("TestBatchNode") {
                nodeBatchedExecutor { selectors, _ ->
                    selectors.associate { selector ->
                        selector to Result.success(
                            mkEngineObjectData(
                                Samples.testSchema.schema.getObjectType("TestNode"),
                                mapOf("id" to selector.id, "type" to "batch")
                            )
                        )
                    }
                }
            }
        }

        val bootstrapper = MockTenantAPIBootstrapper(listOf(moduleWithBoth))
        val registry = DispatcherRegistryFactory(bootstrapper, Validator.Unvalidated, MockCheckerExecutorFactory())
            .create(Samples.testSchema)

        // Should have both resolvers
        val regularResolver = registry.getNodeResolverDispatcher("TestNode")
        val batchResolver = registry.getNodeResolverDispatcher("TestBatchNode")

        assertNotNull(regularResolver)
        assertNotNull(batchResolver)
    }

    @Test
    fun `do not register checkers for introspection types or fields`() {
        val checkerFactory = MockCheckerExecutorFactory()

        val bootstrapper = MockTenantAPIBootstrapper(listOf(Samples.mockTenantModule))
        val registry = DispatcherRegistryFactory(bootstrapper, Validator.Unvalidated, checkerFactory)
            .create(Samples.testSchema)

        // Introspection type and fields should not have checkers registered
        assertNull(registry.getTypeCheckerDispatcher("__Schema"))
        assertNull(registry.getFieldCheckerDispatcher("__Type", "kind"))
        assertNull(registry.getFieldCheckerDispatcher("__Field", "name"))
        assertNull(registry.getFieldCheckerDispatcher("TestType", "__typename"))
        assertNull(registry.getFieldCheckerDispatcher("Query", "__typename"))
        assertNull(registry.getFieldCheckerDispatcher("Query", "__schema"))
    }
}
