package com.steerinfo.meterwork.meterworkmonitor.rtdb;

import com.steerinfo.meterwork.meterworkmonitor.model.MeterWorkMonitor;
import com.steerinfo.meterwork.rtdb.RtdbClientPool;
import com.steerinfo.rtdb.Client;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Repository;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * METER_WORK_MONITOR Tarantool 访问封装。
 */
@Repository
@ConditionalOnProperty(name = "rtdb.monitor.enabled", havingValue = "true", matchIfMissing = true)
public class MeterWorkMonitorRtdbRepository {

    private static final Logger log = LoggerFactory.getLogger(MeterWorkMonitorRtdbRepository.class);

    private static final String SPACE_GET = "db.space.METER_WORK_MONITOR:get";
    private static final String SPACE_REPLACE = "db.space.METER_WORK_MONITOR:replace";
    private static final String SPACE_UPDATE = "db.space.METER_WORK_MONITOR:update";
    private static final String SPACE_SELECT = "db.space.METER_WORK_MONITOR:select";
    private static final String DIRTY_SELECT = "db.space.METER_WORK_MONITOR.index.dirty:select";

    private final RtdbClientPool pool;

    public MeterWorkMonitorRtdbRepository(RtdbClientPool pool) {
        this.pool = pool;
    }

    public MeterWorkMonitor getByPointNo(String pointNo) {
        long start = System.currentTimeMillis();
        MeterWorkMonitor result = pool.execute(client -> {
            List<?> raw = callResult(client, SPACE_GET, pointNo);
            List<?> tuple = unwrapTuple(raw);
            return MeterWorkMonitorTupleCodec.fromTuple(tuple);
        });
        log.debug("[MonitorRTDB] tarantool get pointNo={} hit={} costMs={}",
                pointNo, result != null && result.getPointNo() != null, System.currentTimeMillis() - start);
        return result;
    }

    public void replace(MeterWorkMonitor model, boolean dirty) {
        long start = System.currentTimeMillis();
        String pointNo = model == null ? null : model.getPointNo();
        List<Object> tuple = MeterWorkMonitorTupleCodec.toTuple(model, dirty);
        try {
            pool.execute(client -> {
                client.syncOps().call(SPACE_REPLACE, tuple);
                return null;
            });
            log.info("[MonitorRTDB] tarantool replace ok pointNo={} dirty={} tupleSize={} costMs={}",
                    pointNo, dirty, tuple.size(), System.currentTimeMillis() - start);
        } catch (RuntimeException e) {
            log.error("[MonitorRTDB] tarantool replace fail pointNo={} dirty={} costMs={}",
                    pointNo, dirty, System.currentTimeMillis() - start, e);
            throw e;
        }
    }

    public void updateLastTime(String pointNo, long lastTimeMs) {
        long start = System.currentTimeMillis();
        List<List<?>> ops = Arrays.asList(
                Arrays.asList("=", MeterWorkMonitorTupleCodec.IDX_LAST_TIME, lastTimeMs),
                Arrays.asList("=", MeterWorkMonitorTupleCodec.IDX_DIRTY, true),
                Arrays.asList("=", MeterWorkMonitorTupleCodec.IDX_UPDATED_AT, System.currentTimeMillis())
        );
        try {
            pool.execute(client -> {
                client.syncOps().call(SPACE_UPDATE, pointNo, ops);
                return null;
            });
            log.info("[MonitorRTDB] tarantool update LAST_TIME ok pointNo={} lastTimeMs={} costMs={}",
                    pointNo, lastTimeMs, System.currentTimeMillis() - start);
        } catch (RuntimeException e) {
            log.error("[MonitorRTDB] tarantool update LAST_TIME fail pointNo={} lastTimeMs={} costMs={}",
                    pointNo, lastTimeMs, System.currentTimeMillis() - start, e);
            throw e;
        }
    }

    public void clearDirty(String pointNo) {
        List<List<?>> ops = Arrays.asList(
                Arrays.asList("=", MeterWorkMonitorTupleCodec.IDX_DIRTY, false),
                Arrays.asList("=", MeterWorkMonitorTupleCodec.IDX_UPDATED_AT, System.currentTimeMillis())
        );
        pool.execute(client -> {
            client.syncOps().call(SPACE_UPDATE, pointNo, ops);
            return null;
        });
    }

    public List<MeterWorkMonitor> selectDirty() {
        return pool.execute(client -> {
            List<?> raw;
            try {
                raw = callResult(client, DIRTY_SELECT, true);
            } catch (Exception e) {
                log.warn("dirty index select failed, fallback full select: {}", e.getMessage());
                raw = callResult(client, SPACE_SELECT);
            }
            List<List<?>> tuples = unwrapTupleList(raw);
            List<MeterWorkMonitor> result = new ArrayList<>();
            for (List<?> tuple : tuples) {
                if (MeterWorkMonitorTupleCodec.isDirty(tuple)) {
                    MeterWorkMonitor m = MeterWorkMonitorTupleCodec.fromTuple(tuple);
                    if (m != null && m.getPointNo() != null) {
                        result.add(m);
                    }
                }
            }
            return result;
        });
    }

    public List<MeterWorkMonitor> selectAll() {
        return pool.execute(client -> {
            List<?> raw = callResult(client, SPACE_SELECT);
            List<List<?>> tuples = unwrapTupleList(raw);
            List<MeterWorkMonitor> result = new ArrayList<>();
            for (List<?> tuple : tuples) {
                MeterWorkMonitor m = MeterWorkMonitorTupleCodec.fromTuple(tuple);
                if (m != null && m.getPointNo() != null) {
                    result.add(m);
                }
            }
            return result;
        });
    }

    @SuppressWarnings("unchecked")
    private static List<?> callResult(Client client, String function, Object... args) {
        Object res = client.syncOps().call(function, args);
        if (res instanceof List) {
            return (List<?>) res;
        }
        return Collections.emptyList();
    }

    /**
     * call 返回通常为 [tuple] 或 [ [tuples...] ]，兼容多种包装。
     */
    @SuppressWarnings("unchecked")
    private static List<?> unwrapTuple(List<?> raw) {
        if (raw == null || raw.isEmpty()) {
            return null;
        }
        Object first = raw.get(0);
        if (first == null) {
            return null;
        }
        if (first instanceof List) {
            List<?> inner = (List<?>) first;
            // get 返回单行：可能是字段 list，也可能再包一层
            if (!inner.isEmpty() && !(inner.get(0) instanceof List) && inner.size() >= 1) {
                // 可能是 tuple 本身（首字段为 POINT_NO string）
                if (inner.get(0) instanceof String || inner.get(0) instanceof Number || inner.get(0) instanceof Boolean) {
                    return inner;
                }
            }
            if (!inner.isEmpty() && inner.get(0) instanceof List) {
                return (List<?>) inner.get(0);
            }
            return inner;
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    private static List<List<?>> unwrapTupleList(List<?> raw) {
        List<List<?>> out = new ArrayList<>();
        if (raw == null || raw.isEmpty()) {
            return out;
        }
        Object first = raw.get(0);
        if (!(first instanceof List)) {
            return out;
        }
        List<?> level1 = (List<?>) first;
        if (level1.isEmpty()) {
            return out;
        }
        // select 返回 [[tuple1],[tuple2],...] 或 [tuple1, tuple2, ...]
        if (level1.get(0) instanceof List) {
            Object maybeField = ((List<?>) level1.get(0)).isEmpty() ? null : ((List<?>) level1.get(0)).get(0);
            // 若内层首元素仍是 List，则 level1 是 tuple 列表
            if (maybeField instanceof List) {
                for (Object o : level1) {
                    if (o instanceof List) {
                        out.add((List<?>) o);
                    }
                }
                return out;
            }
            // level1.get(0) 是 tuple（字段为标量）→ level1 即多行
            for (Object o : level1) {
                if (o instanceof List) {
                    List<?> row = (List<?>) o;
                    if (!row.isEmpty() && !(row.get(0) instanceof List)) {
                        out.add(row);
                    } else if (!row.isEmpty() && row.get(0) instanceof List) {
                        out.add((List<?>) row.get(0));
                    }
                }
            }
            return out;
        }
        // 单行字段 list
        out.add(level1);
        return out;
    }
}
