/**
 * Copyright © 2012-2024 by Data Virtuality GmbH
 */
package org.teiid.query.resolver.util;

import org.junit.jupiter.api.Test;
import org.teiid.api.exception.query.QueryResolverException;
import org.teiid.metadata.FunctionMethod;
import org.teiid.metadata.FunctionParameter;
import org.teiid.query.function.FunctionLibrary;
import org.teiid.query.function.FunctionMetadataSource;
import org.teiid.query.function.FunctionMethods;
import org.teiid.query.function.FunctionTree;
import org.teiid.query.metadata.QueryMetadataInterface;
import org.teiid.query.optimizer.FakeFunctionMetadataSource;
import org.teiid.query.sql.lang.GroupContext;
import org.teiid.query.sql.symbol.Constant;
import org.teiid.query.sql.symbol.Expression;
import org.teiid.query.sql.symbol.Function;
import org.teiid.query.sql.symbol.GroupSymbol;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;

/**
 * @author Serhii Bashniak
 * @date 2024.12.26
 */
public class TestResolverVisitor implements FunctionMetadataSource {

    @Test
    void resolveFunction_InvokeWithDouble() {
        QueryMetadataInterface metadata = mock(QueryMetadataInterface.class);
        Collection<GroupSymbol> internalGroups = mock(Collection.class);
        GroupContext externalContext = mock(GroupContext.class);
        FunctionTree systems = new FunctionTree("SYS",  new TestResolverVisitor());
        FunctionTree users = new FunctionTree("UTILS",  new FakeFunctionMetadataSource());

        Expression[] args = new Expression[1];
        args[0] = new Constant(9.99e1);
        Function bitnot = new Function("bitnot", args);
        ResolverVisitor visitor = new ResolverVisitor(metadata, internalGroups, externalContext);
        assertDoesNotThrow(() -> visitor.resolveFunction(bitnot, new FunctionLibrary(systems, users)));
    }

    @Test
    void resolveFunction_InvokeWithBigInteger() {
        QueryMetadataInterface metadata = mock(QueryMetadataInterface.class);
        Collection<GroupSymbol> internalGroups = mock(Collection.class);
        GroupContext externalContext = mock(GroupContext.class);
        FunctionTree systems = new FunctionTree("SYS",  new TestResolverVisitor());
        FunctionTree users = new FunctionTree("UTILS",  new FakeFunctionMetadataSource());

        Expression[] args = new Expression[1];
        args[0] = new Constant(new BigInteger("999000000000000"));
        Function bitnot = new Function("bitnot", args);
        ResolverVisitor visitor = new ResolverVisitor(metadata, internalGroups, externalContext);
        assertDoesNotThrow(() -> visitor.resolveFunction(bitnot, new FunctionLibrary(systems, users)));
    }

    @Test
    void resolveFunction_InvokeWithBigDecimal() {
        QueryMetadataInterface metadata = mock(QueryMetadataInterface.class);
        Collection<GroupSymbol> internalGroups = mock(Collection.class);
        GroupContext externalContext = mock(GroupContext.class);
        FunctionTree systems = new FunctionTree("SYS",  new TestResolverVisitor());
        FunctionTree users = new FunctionTree("UTILS",  new FakeFunctionMetadataSource());

        Expression[] args = new Expression[1];
        args[0] = new Constant(new BigDecimal("999000000000000"));
        Function bitnot = new Function("bitnot", args);
        ResolverVisitor visitor = new ResolverVisitor(metadata, internalGroups, externalContext);
        assertThrows(QueryResolverException.class, () -> visitor.resolveFunction(bitnot, new FunctionLibrary(systems, users)));
    }

    @Override
    public Collection<FunctionMethod> getFunctionMethods() {
        List<FunctionMethod> methods = new ArrayList<>();
        try {
            FunctionMethod bitnot = new FunctionMethod("bitnot", "", "Numeric", 
                    FunctionMethod.PushDown.CAN_PUSHDOWN, FunctionMethods.class.getName(), "bitnot", 
                    List.of(new FunctionParameter("integer", "integer")), new FunctionParameter("result", "integer"), 
                    true, FunctionMethod.Determinism.DETERMINISTIC);
            Method method = FunctionMethods.class.getDeclaredMethod("bitnot", int.class);
            bitnot.setMethod(method);            
            methods.add(bitnot);
            methods.add(getFunctionMethod("bigdecimal"));           
            methods.add(getFunctionMethod("double"));
            methods.add(getFunctionMethod("biginteger"));
            
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }

        return methods;
    }

    private static FunctionMethod getFunctionMethod(String type) throws NoSuchMethodException {
        FunctionMethod functionMethod = new FunctionMethod("convert", "", "Conversion",
                FunctionMethod.PushDown.CANNOT_PUSHDOWN, FunctionMethods.class.getName(), "convert", 
                List.of(new FunctionParameter("value", type), new FunctionParameter("target", "string")),
                new FunctionParameter("result", "object"), true, FunctionMethod.Determinism.DETERMINISTIC);

        functionMethod.setMethod(FunctionMethods.class.getDeclaredMethod("convert", Object.class, String.class));
        return functionMethod;
    }

    @Override
    public Class<?> getInvocationClass(String className) throws ClassNotFoundException {
        return Class.forName(className, true, this.getClass().getClassLoader());
    }

    @Override
    public ClassLoader getClassLoader() {
        return null;
    }
}