@file:Suppress("UNCHECKED_CAST")

package gaf3.core.jpa.extension

import gaf3.core.data.PageParam
import gaf3.core.jpa.annotation.WithIgnoreCase
import gaf3.core.jpa.annotation.WithIgnorePath
import gaf3.core.jpa.annotation.WithStringMatcher
import gaf3.core.util.DataBeanHelper
import org.springframework.beans.BeanUtils
import org.springframework.data.domain.*
import org.springframework.data.repository.query.QueryByExampleExecutor

inline fun <T, reified S : T> QueryByExampleExecutor<T>.findOrNull(example: S?, exact: Boolean = true): S? {
    return example?.let { findOne(Example.of(example, example.matching(exact))).orElse(null) }
}

inline fun <T, reified S : T> QueryByExampleExecutor<T>.findOrNull(pair: Pair<String, Any>, exact: Boolean = true): S? {
    val example: S = BeanUtils.instantiateClass(S::class.java)
    DataBeanHelper.setNestedProperty(example, pair.second, pair.first)
    // // return findOne(example, exact)
    return findAll(example, PageRequest.of(0,1), exact).firstOrNull()
}

inline fun<T, reified S: T> QueryByExampleExecutor<T>.findAll(example: S, exact: Boolean = true): List<S>  {
    return findAll(Example.of(example, example.matching(exact))) as List<S>
}

inline fun<T, reified S: T> QueryByExampleExecutor<T>.findAll(example: S, sort: Sort, exact: Boolean = true): List<S>  {
    return findAll(Example.of(example, example.matching(exact)), sort) as List<S>
}

inline fun<T, reified S: T> QueryByExampleExecutor<T>.findAll(example: S, pageable: Pageable, exact: Boolean = true): Page<S> {
    return findAll(Example.of(example, example.matching(exact)), pageable)
}

inline fun<T, reified S: T> QueryByExampleExecutor<T>.findAll(example: S, paging: PageParam): Page<S> {
    return findAll(Example.of(example, example.matching(paging.exact)), paging.toPageRequest())
}

inline fun<T, reified S: T> QueryByExampleExecutor<T>.count(example: S, exact: Boolean = true): Long  {
    return count(Example.of(example, example.matching(exact)))
}

inline fun<T, reified S: T> QueryByExampleExecutor<T>.exists(example: S, exact: Boolean = true): Boolean  {
    return exists(Example.of(example, example.matching(exact)))
}

inline fun <T, reified S : T> QueryByExampleExecutor<T>.exists(pair: Pair<String, Any>, exact: Boolean = true): Boolean {
    val example: S = BeanUtils.instantiateClass(S::class.java)
    DataBeanHelper.setNestedProperty(example, pair.second, pair.first)
    return exists(example, exact)
}


inline fun<reified S> S.matching(exact: Boolean = true): ExampleMatcher {
    // 默认模糊查询匹配规则
    val defaultMatcher = System.getProperty("db.matcher")?.let {
        runCatching {
            ExampleMatcher.StringMatcher.valueOf(it)
        }.onFailure {
            println("!!!!!! warning: db.matcher invalid, $it !!!!!!")
        }.getOrNull()
    } ?: ExampleMatcher.StringMatcher.STARTING

    val cls = S::class.java
    var matcher: ExampleMatcher = ExampleMatcher.matching()

    // 计算IgnorePaths
    cls.declaredFields.filter {
        it.isAnnotationPresent(WithIgnorePath::class.java)
    }.map { it.name }.toTypedArray().takeIf { it.isNotEmpty() }?.let {
        matcher = matcher.withIgnorePaths(*it)
    }

    // 计算IgnoreCase
    cls.declaredFields.filter {
        it.isAnnotationPresent(WithIgnoreCase::class.java)
    }.forEach {
        val value = it.getAnnotation(WithIgnoreCase::class.java).value
        matcher = matcher.withMatcher(it.name, ExampleMatcher.GenericPropertyMatcher().ignoreCase(value))
    }

    if (!exact) {
        // 非精确匹配模式下，启用Entity类和字段上定义的StringMatcher来实现模糊查询
        // 默认所有字段使用左匹配
        matcher = cls.getAnnotation(WithStringMatcher::class.java)?. let {
            if (it.value == ExampleMatcher.StringMatcher.DEFAULT) {
                matcher.withStringMatcher(defaultMatcher)
            } else {
                matcher.withStringMatcher(it.value)
            }
        } ?: matcher.withStringMatcher(defaultMatcher)
        cls.declaredFields.filter { it.isAnnotationPresent(WithStringMatcher::class.java) }.forEach {
            // 字段默认StringMatcher配置
            var fieldMatcher = it.getAnnotation(WithStringMatcher::class.java).value
            if (fieldMatcher == ExampleMatcher.StringMatcher.DEFAULT) {
                fieldMatcher = defaultMatcher
            }
            matcher = matcher.withMatcher(it.name, ExampleMatcher.GenericPropertyMatcher.of(fieldMatcher))
        }
    }
    return matcher
}


fun main(args: Array<String>) {
    val defaultMatcher = System.getProperty("db.matcher")?.let {
        runCatching {
            ExampleMatcher.StringMatcher.valueOf(it)
        }.onFailure {
            println("!!!!!! warning: db.matcher invalid, $it !!!!!!")
        }.getOrNull()
    } ?: ExampleMatcher.StringMatcher.STARTING
    println(defaultMatcher)
}