package com.lingniu.ingest.tdenginehistory;

import java.util.ArrayList;
import java.util.List;

public final class TdengineHistoryQueries {

    private static final String RAW_COLUMNS = "ts, frame_id, received_at, message_id, sub_type, event_time, "
            + "raw_uri, checksum, raw_size_bytes, parse_status, parse_error, peer, metadata_json, parsed_json, "
            + "protocol, vehicle_key, vin, phone";
    private static final String LOCATION_COLUMNS = "ts, fact_id, frame_id, received_at, longitude, latitude, "
            + "altitude_m, speed_kmh, direction_deg, alarm_flag, status_flag, total_mileage_km, raw_uri, "
            + "protocol, vehicle_key, vin, phone";

    private final TdengineHistorySchema schema;

    public TdengineHistoryQueries(TdengineHistorySchema schema) {
        if (schema == null) {
            throw new IllegalArgumentException("schema must not be null");
        }
        this.schema = schema;
    }

    public TdengineQueryStatement rawFrames(TdengineRawFrameQuery query) {
        if (query.childTableQuery()) {
            String table = schema.rawFrameTable(query.protocol(), query.vehicleKey());
            return query(table, RAW_COLUMNS, "frame_id", query.from(), query.to(),
                    query.order(), query.limit(), query.cursor());
        }
        return rawFramesStable(query);
    }

    public TdengineQueryStatement locations(TdengineLocationQuery query) {
        String table = schema.locationTable(query.protocol(), query.vehicleKey());
        return query(table, LOCATION_COLUMNS, "fact_id", query.from(), query.to(),
                query.order(), query.limit(), query.cursor());
    }

    public TdengineQueryStatement locationsByRawUri(String protocol, String rawUri, int limit) {
        return byRawUri(schema.locationsStableTable(), LOCATION_COLUMNS, protocol, rawUri, "fact_id", limit);
    }

    private static TdengineQueryStatement query(String table,
                                                String columns,
                                                String tieBreakerColumn,
                                                Object from,
                                                Object to,
                                                TdengineQueryOrder order,
                                                int limit,
                                                TdenginePageCursor cursor) {
        List<Object> values = new ArrayList<>();
        StringBuilder sql = new StringBuilder(256)
                .append("SELECT ").append(columns)
                .append(" FROM ").append(table)
                .append(" WHERE ts >= ? AND ts < ?");
        values.add(from);
        values.add(to);
        if (cursor != null) {
            String op = order == TdengineQueryOrder.ASC ? ">" : "<";
            sql.append(" AND (ts ").append(op).append(" ? OR (ts = ? AND ")
                    .append(tieBreakerColumn).append(' ').append(op).append(" ?))");
            values.add(cursor.ts());
            values.add(cursor.ts());
            values.add(cursor.tieBreaker());
        }
        sql.append(" ORDER BY ts ").append(order.name())
                .append(", ").append(tieBreakerColumn).append(' ').append(order.name())
                .append(" LIMIT ?");
        values.add(limit);
        return new TdengineQueryStatement(sql.toString(), values);
    }

    private TdengineQueryStatement rawFramesStable(TdengineRawFrameQuery query) {
        List<Object> values = new ArrayList<>();
        StringBuilder sql = new StringBuilder(384)
                .append("SELECT ").append(RAW_COLUMNS)
                .append(" FROM ").append(schema.rawFramesStableTable())
                .append(" WHERE ts >= ? AND ts < ?");
        values.add(query.from());
        values.add(query.to());
        addFilter(sql, values, "protocol", query.protocol());
        addFilter(sql, values, "vehicle_key", query.vehicleKey());
        addFilter(sql, values, "vin", query.vin());
        addFilter(sql, values, "phone", query.phone());
        if (query.messageId() != null) {
            sql.append(" AND message_id = ?");
            values.add(query.messageId());
        }
        addFilter(sql, values, "parse_status", query.parseStatus());
        if (query.cursor() != null) {
            String op = query.order() == TdengineQueryOrder.ASC ? ">" : "<";
            sql.append(" AND (ts ").append(op).append(" ? OR (ts = ? AND frame_id ")
                    .append(op).append(" ?))");
            values.add(query.cursor().ts());
            values.add(query.cursor().ts());
            values.add(query.cursor().tieBreaker());
        }
        sql.append(" ORDER BY ts ").append(query.order().name())
                .append(", frame_id ").append(query.order().name())
                .append(" LIMIT ?");
        values.add(query.limit());
        return new TdengineQueryStatement(sql.toString(), values);
    }

    private static void addFilter(StringBuilder sql, List<Object> values, String column, String value) {
        if (value != null && !value.isBlank()) {
            sql.append(" AND ").append(column).append(" = ?");
            values.add(value);
        }
    }

    private static TdengineQueryStatement byRawUri(String table,
                                                   String columns,
                                                   String protocol,
                                                   String rawUri,
                                                   String tieBreakerColumn,
                                                   int limit) {
        List<Object> values = new ArrayList<>();
        StringBuilder sql = new StringBuilder(256)
                .append("SELECT ").append(columns)
                .append(" FROM ").append(table)
                .append(" WHERE raw_uri = ?");
        values.add(rawUri);
        addFilter(sql, values, "protocol", protocol == null ? "" : protocol.trim().toUpperCase());
        sql.append(" ORDER BY ts DESC, ").append(tieBreakerColumn).append(" DESC LIMIT ?");
        values.add(Math.max(1, Math.min(limit, 1000)));
        return new TdengineQueryStatement(sql.toString(), values);
    }
}
