package viaduct.codegen.km

import io.kotest.matchers.collections.shouldHaveSize
import kotlin.reflect.KClass
import kotlinx.metadata.KmClass
import kotlinx.metadata.jvm.KotlinClassMetadata
import org.junit.jupiter.api.Test
import viaduct.invariants.InvariantChecker

/** A few smoke tests of the invariant checker using Kotlin metadata
 *  generated by the Kotlin compiler.
 */
class KmClassInvariantsFromCompilerTests {
    val KClass<*>.kmClass: KmClass get() {
        val m = this.java.getAnnotation(Metadata::class.java)
        return (KotlinClassMetadata.readStrict(m) as KotlinClassMetadata.Class).kmClass
    }

    fun checkInvariants(
        c: KClass<*>,
        o: KClass<*>? = null
    ): InvariantChecker {
        val check = InvariantChecker()
        checkKmClassInvariants(o?.kmClass, c.kmClass, check)
        return check
    }

    @Test
    fun testSimpleInterface() {
        checkInvariants(SimpleInterface::class).assertEmptyMultiline("SimpleInterface should be empty")
    }

    @Test
    fun testSimpleEnum() {
        checkInvariants(SimpleInterface::class).assertEmptyMultiline("SimpleEnum should be empty")
    }

    @Test
    fun testSimpleEnumTwo() {
        checkInvariants(SimpleInterface::class).assertEmptyMultiline("SimpleEnumTwo should be empty")
    }

    @Test
    fun testSimpleNestedClass() {
        checkInvariants(SimpleInterface::class).assertEmptyMultiline("SimpleNestedClass should be empty")
    }

    // Should fail

    @Test
    fun testSimplePrivateClass() {
        checkInvariants(SimplePrivateClass::class) shouldHaveSize 1
    }

    @Test
    fun testSimpleSealedInterface() {
        checkInvariants(SimpleSealedInterface::class) shouldHaveSize 3
    }

    @Test
    fun testSimpleOpen() {
        checkInvariants(SimpleOpenClass::class) shouldHaveSize 2
    }

    @Test
    fun testSimpleFunInterface() {
        checkInvariants(SimpleFunInterface::class) shouldHaveSize 1
    }

    @Test
    fun testSimpleInlineClass() {
        checkInvariants(SimpleInlineClass::class) shouldHaveSize 4
    }

    @Test
    fun testSimpleGenericClass() {
        checkInvariants(SimpleGenericClass::class) shouldHaveSize 1
    }

    // Nested-class fail cases

    @Test
    fun testSimpleNestedEnum() {
        checkInvariants(I1.SimpleNestedEnum::class, I1::class) shouldHaveSize 1
    }

    @Test
    fun testMissingOuterClass() {
        checkInvariants(C2.I::class, null) shouldHaveSize 1
    }

    @Test
    fun testWrongOuterClass() {
        checkInvariants(I1.SimpleNestedClass::class, SimpleInterface::class) shouldHaveSize 2
    }

    @Test
    fun testSimpleInnerClass() {
        checkInvariants(C2.SimpleInnerClass::class, C2::class) shouldHaveSize 1
    }

    @Test
    fun testDoubleNestedClass() {
        checkInvariants(C2.I.DoubleNestedClass::class, C2.I::class).assertEmptyMultiline("C2.I.DoubleNestedClass and C2.I should be empty")
    }
}

interface SimpleInterface

enum class SimpleEnum

enum class SimpleEnumTwo { ONE, TWO }

interface I1 {
    class SimpleNestedClass

    enum class SimpleNestedEnum
}

private class SimplePrivateClass

sealed interface SimpleSealedInterface

class Foo : SimpleSealedInterface // force something into KmClass.sealedSubclasses

open class SimpleOpenClass

fun interface SimpleFunInterface {
    fun invoke()
}

@JvmInline
value class SimpleInlineClass(val i: Int)

class SimpleGenericClass<T>

class C2 {
    inner class SimpleInnerClass

    interface I {
        class DoubleNestedClass
    }
}
