package example;

import java.math.BigDecimal;
import java.sql.Connection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

public final class DynamicLab {
    private DynamicLab() {}
    private static void require(boolean condition, String message) {
        if (!condition) throw new AssertionError(message);
    }
    private static boolean causedBy(Throwable error, Class<? extends Throwable> type) {
        for (Throwable cause = error; cause != null; cause = cause.getCause()) {
            if (type.isInstance(cause)) return true;
        }
        return false;
    }
    private static Map<String, Object> arguments() {
        Map<String, Object> result = new HashMap<>();
        result.put("tenantId", 7L); result.put("status", null); result.put("ids", null);
        result.put("sort", "id"); result.put("pattern", null); return result;
    }
    private static void first() throws Exception {
        Factories.reset();
        try (SqlSession session = Factories.create().openSession()) {
            List<Order> rows = new SearchService(session.getMapper(OrderMapper.class))
                    .search(7L, OrderStatus.NEW, null, "id", null);
            require(rows.size() == 1 && rows.get(0).getId() == 1, "Tenant/status filter failed");
            Order order = rows.get(0);
            require(order.getStatus() == OrderStatus.NEW && order.getAmount().compareTo(new BigDecimal("10.25")) == 0,
                    "Type mapping failed");
            System.out.println("tenantId=7 rows=1 orderId=1 status=NEW amount=10.25");
        }
    }
    private static void bound() throws Exception {
        SqlSessionFactory factory = Factories.create();
        var mapped = factory.getConfiguration().getMappedStatement("example.OrderMapper.search");
        require(mapped.getSqlSource() instanceof org.apache.ibatis.scripting.xmltags.DynamicSqlSource,
                "Expected DynamicSqlSource");
        Map<String, Object> parameters = arguments();
        parameters.put("ids", List.of(1L, 2L));
        BoundSql bound = mapped.getBoundSql(parameters);
        require(bound.getParameterMappings().size() == 3, "Expected tenant plus two IDs");
        List<Object> foreachValues = bound.getParameterMappings().stream().skip(1)
                .map(ParameterMapping::getProperty).map(bound::getAdditionalParameter).toList();
        require(foreachValues.equals(List.of(1L, 2L)), "Lost foreach additional parameters " + foreachValues);
        require(bound.getSql().chars().filter(c -> c == '?').count() == 3, "Wrong JDBC placeholders");
        System.out.println("sqlSource=DynamicSqlSource parameterSlots=3 foreachValues=" + foreachValues);
        var raw = factory.getConfiguration().getMappedStatement("example.OrderMapper.rawOrderForComparison")
                .getBoundSql(Map.of("sortFragment", "id DESC"));
        require(raw.getParameterMappings().isEmpty() && raw.getSql().contains("ORDER BY id DESC"),
                "Expected direct SQL text substitution");
        System.out.println("hashParametersRemainPlaceholders=true dollarFragmentBecameSqlText=true");
    }
    private static void filters() throws Exception {
        try (SqlSession session = Factories.create().openSession()) {
            OrderMapper mapper = session.getMapper(OrderMapper.class);
            SearchService service = new SearchService(mapper);
            require(service.search(7L, null, List.of(), "id", null).isEmpty(), "Empty list expanded query");
            require(service.search(7L, null, List.of(1L, 2L, 3L), "id", null).size() == 2,
                    "Tenant condition was lost with foreach");
            List<Order> literal = service.search(7L, null, null, "id", "A_100%");
            require(literal.size() == 1 && literal.get(0).getId() == 1, "Literal LIKE escaping failed");
            List<Order> sorted = service.search(7L, null, null, "amount", null);
            require(sorted.size() == 2 && sorted.get(0).getId() == 2, "Whitelist sort failed");
            try { service.search(null, null, null, "id", null); throw new AssertionError("Missing tenant accepted"); }
            catch (IllegalArgumentException expected) { require(expected.getMessage().contains("tenantId"), "Wrong error"); }
            try { service.search(7L, null, null, "amount desc", null); throw new AssertionError("Raw sort accepted"); }
            catch (IllegalArgumentException expected) { require(expected.getMessage().contains("sort"), "Wrong error"); }
            System.out.println("emptyIdsReturnedZero=true tenantRetained=true literalLikeMatched=true whitelistSort=true");
            System.out.println("missingTenantRejected=true rawSortRejected=true");
        }
    }
    private static void roundtrip() throws Exception {
        Factories.reset();
        StatusHandler.WRITES.set(0); StatusHandler.READS.set(0);
        try {
            try (SqlSession session = Factories.create().openSession()) {
                require(session.getMapper(OrderMapper.class).updateStatus(1, OrderStatus.PAID) == 1, "Update missing");
                session.commit();
            }
            try (SqlSession session = Factories.create().openSession()) {
                require(session.getMapper(OrderMapper.class).find(1).getStatus() == OrderStatus.PAID, "Enum roundtrip failed");
            }
            require(StatusHandler.WRITES.get() > 0 && StatusHandler.READS.get() > 0, "Handler callbacks missing");
            try (Connection connection = Factories.dataSource().getConnection(); var query = connection.createStatement();
                 var rows = query.executeQuery("select status_code from purchase_order where id=1")) {
                require(rows.next() && rows.getInt(1) == 20, "Stored value was not stable code 20");
            }
            System.out.println("handlerWriteObserved=true handlerReadObserved=true storedCode=20 roundtrip=PAID");
        } finally { Factories.reset(); }
    }
    private static void nulls() throws Exception {
        StatusHandler.WRITES.set(0); StatusHandler.READS.set(0);
        try {
            try (SqlSession session = Factories.create().openSession()) {
                require(session.getMapper(OrderMapper.class).updateStatus(1, null) == 1, "Null update missing");
                session.commit();
            }
            try (SqlSession session = Factories.create().openSession()) {
                require(session.getMapper(OrderMapper.class).find(1).getStatus() == null, "SQL NULL became an enum");
            }
            require(StatusHandler.WRITES.get() == 0, "Null should use BaseTypeHandler null binding");
            require(StatusHandler.READS.get() > 0, "Read callback missing");
            try (Connection connection = Factories.dataSource().getConnection(); var query = connection.createStatement();
                 var rows = query.executeQuery("select status_code is null from purchase_order where id=1")) {
                require(rows.next() && rows.getBoolean(1), "Database value not SQL NULL");
            }
            System.out.println("sqlNullStored=true nullPreserved=true nonNullWriteCallbackSkipped=true");
        } finally { Factories.reset(); }
    }
    private static void unknown() throws Exception {
        try {
            try (Connection connection = Factories.dataSource().getConnection(); var statement = connection.createStatement()) {
                require(statement.executeUpdate("update purchase_order set status_code=99 where id=1") == 1, "Unknown seed missing");
            }
            try (SqlSession session = Factories.create().openSession()) {
                try {
                    session.getMapper(OrderMapper.class).find(1);
                    throw new AssertionError("Unknown enum code accepted");
                } catch (RuntimeException expected) {
                    require(causedBy(expected, IllegalArgumentException.class) && expected.getMessage().contains("99"),
                            "Unexpected mapping failure");
                }
            }
            Factories.reset();
            try (SqlSession session = Factories.create().openSession()) {
                require(session.getMapper(OrderMapper.class).find(1).getStatus() == OrderStatus.NEW, "Recovery mapping failed");
            }
            System.out.println("unknownCodeRejected=true repairedCode=10 mappedStatus=NEW");
        } finally { Factories.reset(); }
    }
    public static void main(String[] args) throws Exception {
        String mode = args.length == 0 ? "all" : args[0];
        switch (mode) {
            case "first" -> first();
            case "bound" -> bound();
            case "filters" -> filters();
            case "roundtrip" -> roundtrip();
            case "nulls" -> nulls();
            case "unknown" -> unknown();
            case "all" -> { first(); bound(); filters(); roundtrip(); nulls(); unknown(); }
            default -> throw new IllegalArgumentException("Mode: first|bound|filters|roundtrip|nulls|unknown|all");
        }
    }
}
