scanner

package site.daydream.colleen

import java.lang.reflect.Method
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.functions
import kotlin.reflect.jvm.isAccessible

object ControllerScanner {
    fun scan(obj: Any): ControllerMeta {
        val klass = obj::class
        val javaClass = obj.javaClass

        val basePath = klass.findAnnotation<Controller>()?.value ?: "/"
        val middleware = findMiddlewares(javaClass)
        val routes = findRoutes(javaClass)

        return ControllerMeta(basePath, middleware, routes, obj)
    }

    private fun findMiddlewares(javaClass: Class<*>): List<Method> {
        return javaClass.declaredMethods
            .filter { it.getAnnotation(Use::class.java) != null }
            .map { validateMiddlewareSignature(it) }
    }

    private fun validateMiddlewareSignature(method: Method): Method {
        val paramTypes = method.parameterTypes

        require(paramTypes.size == 2) {
            "@Use method '${method.name}' must have 2 parameters, got ${paramTypes.size}"
        }

        require(isContextType(paramTypes[0])) {
            "First parameter must be Context, got ${paramTypes[0].name}"
        }
        require(isNextType(paramTypes[1])) {
            "Second parameter must be Next, got ${paramTypes[1].name}"
        }
        require(method.returnType == Void.TYPE) {
            "Return type must be Unit/void, got ${method.returnType.name}"
        }

        method.isAccessible = true
        return method
    }

    private fun findRoutes(javaClass: Class<*>): List<RouteNode> {
        return javaClass.declaredMethods.mapNotNull { method ->
            val (annotation, httpMethod) = detectHttpMethod(method) ?: return@mapNotNull null
            val path = extractPath(annotation)

            method.isAccessible = true

            RouteNode(path, httpMethod, method)
        }
    }

    private fun detectHttpMethod(method: Method): Pair<Annotation, String>? {
        return when {
            method.getAnnotation(Get::class.java) != null ->
                method.getAnnotation(Get::class.java)!! to "GET"

            method.getAnnotation(Post::class.java) != null ->
                method.getAnnotation(Post::class.java)!! to "POST"

            method.getAnnotation(Put::class.java) != null ->
                method.getAnnotation(Put::class.java)!! to "PUT"

            method.getAnnotation(Delete::class.java) != null ->
                method.getAnnotation(Delete::class.java)!! to "DELETE"

            else -> null
        }
    }

    private fun extractPath(annotation: Annotation): String {
        return when (annotation) {
            is Get -> annotation.value
            is Post -> annotation.value
            is Put -> annotation.value
            is Delete -> annotation.value
            else -> "/"
        }
    }

    private fun isContextType(type: Class<*>): Boolean {
        return type.simpleName == "Context" ||
                type.interfaces.any { it.simpleName == "Context" } ||
                type.name.endsWith("Context")
    }

    private fun isNextType(type: Class<*>): Boolean {
        return type.simpleName == "Next" ||
                type.interfaces.any { it.simpleName == "Next" } ||
                type.name.endsWith("Next")
    }

    data class ControllerMeta(
        val basePath: String,
        val middlewares: List<Method>,
        val routes: List<RouteNode>,
        val obj: Any
    )

    data class RouteNode(
        val path: String,
        val method: String,
        val handler: Method
    )
}

object ControllerScannerKotlin {
    fun scan(obj: Any): ControllerMeta {
        val klass = obj::class
        val basePath = klass.findAnnotation<Controller>()?.value ?: "/"
        val middleware = findMiddleware(klass)
        val routes = findRoutes(klass, basePath)

        return ControllerMeta(basePath, middleware, routes)
    }

    private fun findMiddleware(klass: KClass<*>): KFunction<*>? {
        return klass.functions
            .firstOrNull { it.findAnnotation<Use>() != null }
            ?.apply { validateMiddlewareSignature(this) }
    }

    private fun validateMiddlewareSignature(method: KFunction<*>) {
        val params = method.parameters.filterNot { it.kind.name == "INSTANCE" }

        require(params.size == 2) {
            "@Use method '${method.name}' must have 2 parameters, got ${params.size}"
        }

        val paramTypes = params.map { it.type.toString() }
        require(paramTypes[0].contains("Context")) { "First parameter must be Context" }
        require(paramTypes[1].contains("Next")) { "Second parameter must be Next" }
        require(method.returnType.toString().contains("Unit")) { "Return type must be Unit" }

        method.isAccessible = true
    }

    private fun findRoutes(klass: KClass<*>, basePath: String): List<Route> {
        return klass.functions.mapNotNull { method ->
            val (annotation, httpMethod) = detectHttpMethod(method) ?: return@mapNotNull null
            val path = extractPath(annotation)

            method.isAccessible = true

            Route(normalizePath(basePath, path), httpMethod, method)
        }
    }

    private fun detectHttpMethod(method: KFunction<*>): Pair<Any, String>? {
        return when {
            method.findAnnotation<Get>() != null ->
                method.findAnnotation<Get>()!! to "GET"

            method.findAnnotation<Post>() != null ->
                method.findAnnotation<Post>()!! to "POST"

            method.findAnnotation<Put>() != null ->
                method.findAnnotation<Put>()!! to "PUT"

            method.findAnnotation<Delete>() != null ->
                method.findAnnotation<Delete>()!! to "DELETE"

            else -> null
        }
    }

    private fun extractPath(annotation: Any): String {
        return when (annotation) {
            is Get -> annotation.value
            is Post -> annotation.value
            is Put -> annotation.value
            is Delete -> annotation.value
            else -> "/"
        }
    }

    private fun normalizePath(basePath: String, path: String): String {
        return (basePath + path).replace(Regex("/+"), "/")
    }

    data class ControllerMeta(
        val basePath: String,
        val middleware: KFunction<*>?,
        val routes: List<Route>
    )

    data class Route(
        val path: String,
        val method: String,
        val handler: KFunction<*>
    )

}