@file:Suppress("unused", "ClassName")

package viaduct.tenant.tutorial02

import org.junit.jupiter.api.Test
import viaduct.api.Resolver
import viaduct.graphql.test.assertEquals
import viaduct.tenant.runtime.fixtures.FeatureAppTestBase
import viaduct.tenant.tutorial02.resolverbases.QueryResolvers

/**
 * LEARNING OBJECTIVES:
 * - Understand Node Resolvers for object-by-ID patterns
 * - Learn GlobalID system for type-safe object references
 * - See how Field Resolvers and Node Resolvers work together
 *
 * VIADUCT FEATURES DEMONSTRATED:
 * - Node Resolvers (NodeResolvers.Foo())
 * - GlobalID system (ctx.globalIDFor, ctx.nodeFor)
 * - Object builders (Foo.Builder)
 * - Node interface implementation
 *
 * CONCEPTS COVERED:
 * - Relay Global Object Identification specification
 * - Type safety with GlobalIDs
 * - Separation of object creation vs field computation
 *
 * PREVIOUS: [viaduct.tenant.tutorial01.SimpleFieldResolverFeatureAppTest]
 * NEXT: [viaduct.tenant.tutorial03.SimpleResolversFeatureAppTest]
 */
class SimpleNodeResolverFeatureAppTest : FeatureAppTestBase() {
    override var sdl = """
        | #START_SCHEMA
        | type Foo implements Node @resolver {  # <- Node interface + @resolver = generate NodeResolvers.Foo()
        |   id: ID!        # Required by Node interface
        |   bar: String!   # Your custom field
        | }
        |
        | extend type Query {
        |   foo(id: String!): Foo! @resolver  # Query that returns a Node
        | }
        | #END_SCHEMA
    """.trimMargin()

    /**
     * NODE RESOLVER - Creates objects by GlobalID
     *
     * What YOU write:
     * - Extend NodeResolvers.Foo() (generated by the viaduct module plugin when
     *      reading "type Foo implements Node @resolver" from the schema.)
     * - Implement resolve() to create Foo objects from GlobalIDs
     * - Use Foo.Builder to construct objects
     *
     * What VIADUCT generates:
     * - NodeResolvers.Foo() abstract base class
     * - Context with typed GlobalID (ctx.id)
     * - Foo.Builder for type-safe object construction
     * - Automatic GlobalID encoding/decoding
     */
    class FooNodeResolver : NodeResolvers.Foo() {
        override suspend fun resolve(ctx: Context): Foo {
            // EXTRACT INTERNAL ID FROM GLOBALID
            // GlobalID contains: type info + your internal ID
            // ctx.id.internalID gives you back your original ID
            val internalID = ctx.id.internalID

            // YOUR BUSINESS LOGIC - typically database lookup
            val message = when (internalID) {
                "foo-123" -> "Hello from the other Node!"
                "foo-456" -> "Another Foo object!"
                else -> throw IllegalArgumentException("Foo not found: $internalID")
            }

            // BUILD AND RETURN OBJECT
            return Foo.Builder(ctx) // Generated builder
                .id(ctx.id) // Use the GlobalID passed in
                .bar(message) // Set your custom fields
                .build() // Create immutable object
        }
    }

    /**
     * QUERY FIELD RESOLVER - Uses Node Resolver system
     *
     * Pattern: Query field -> Convert string to GlobalID -> Use Node Resolver
     * This creates a clean separation between "how to find objects" vs "what objects contain"
     */
    @Resolver
    class fooResolver : QueryResolvers.Foo() { // Generated from query field
        override suspend fun resolve(ctx: Context): Foo {
            // CONVERT STRING ID TO GLOBALID
            val globalId = ctx.globalIDFor(Foo.Reflection, ctx.arguments.id)

            // USE NODE RESOLVER SYSTEM
            // This automatically routes to FooNodeResolver.resolve()
            return ctx.nodeFor(globalId)
        }
    }

    // TESTS - Show the complete flow
    @Test
    fun `Query returns a node through node resolver`() {
        execute(
            query = """
                query TestQuery {
                   foo(id: "foo-123") {    # 1. fooResolver gets "foo-123"
                       id                   # 2. Converts to GlobalID
                       bar                  # 3. Calls FooNodeResolver
                   }                        # 4. Returns Foo object
                }
            """.trimIndent()
        ).assertEquals {
            "data" to {
                "foo" to {
                    // NOTE: createGlobalIdString is a TEST-ONLY utility method provided by FeatureAppTestBase
                    "id" to createGlobalIdString(Foo.Reflection, "foo-123") // Encoded GlobalID
                    "bar" to "Hello from the other Node!"
                }
            }
        }
    }

    /**
     * WHAT HAPPENS UNDER THE HOOD:
     *
     * 1. Client: foo(id: "foo-123")
     * 2. fooResolver.resolve() called with arguments.id = "foo-123"
     * 3. Creates GlobalID from string: ctx.globalIDFor(Foo.Reflection, "foo-123")
     * 4. ctx.nodeFor(globalId) routes to FooNodeResolver.resolve()
     * 5. FooNodeResolver extracts "foo-123" from GlobalID
     * 6. Looks up data and builds Foo object
     * 7. Returns object to client with encoded GlobalID
     *
     * KEY TAKEAWAYS:
     * - Node Resolvers handle object creation by ID
     * - GlobalIDs provide type safety and encoding
     * - Clean separation: Query resolvers route, Node resolvers create
     * - Use ctx.nodeFor() to leverage the Node Resolver system
     */
}
