package com.example.iot_controlhost.utils.database;

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import com.example.iot_controlhost.MyApp;
import com.example.iot_controlhost.model.LogDao;
import com.example.iot_controlhost.utils.log.MyLog;
import com.example.iot_controlhost.utils.log.UserLog;

import org.greenrobot.greendao.query.QueryBuilder;

import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 日志数据库访问入口。
 *
 * <p>所有日志数据库操作都在同一个线程中串行执行，避免高频日志写入时创建大量线程，
 * 同时保证用户日志在上传服务器前已经完成本地落库。</p>
 */
public final class LogDBManager {

    private static final String LOGCAT_TAG = "LogDBManager";
    private static final int PAGE_SIZE = 20;
    private static final int RETENTION_DAYS = 15;
    private static final int CLEANUP_BATCH_SIZE = 500;
    private static final long CLEANUP_BATCH_DELAY_MS = 30L;
    private static final long SYNC_WAIT_SECONDS = 10L;

    private static volatile Thread databaseThread;
    private static final AtomicBoolean CLEANUP_RUNNING = new AtomicBoolean(false);

    private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(runnable -> {
        Thread thread = new Thread(() -> {
            databaseThread = Thread.currentThread();
            runnable.run();
        }, "log-database");
        thread.setPriority(Thread.NORM_PRIORITY - 1);
        return thread;
    });

    private static final LogDao LOG_DAO = MyApp.getDaoSession().getLogDao();

    private LogDBManager() {
    }

    /**
     * 异步增加普通诊断日志。
     */
    public static void insertLog(com.example.iot_controlhost.model.Log log) {
        EXECUTOR.execute(() -> insertInternal(log));
    }

    /**
     * 异步落库并回调结果。回调在日志数据库线程执行，不允许执行耗时或UI操作。
     */
    public static void insertLog(com.example.iot_controlhost.model.Log log, InsertCallback callback) {
        EXECUTOR.execute(() -> {
            boolean saved = insertInternal(log);
            if (callback != null) {
                callback.onComplete(saved);
            }
        });
    }

    private static boolean insertInternal(com.example.iot_controlhost.model.Log log) {
        try {
            LOG_DAO.insert(log);
            return true;
        } catch (Exception e) {
            Log.e(LOGCAT_TAG, "日志写入数据库失败", e);
            return false;
        }
    }

    /**
     * 查询最新一页日志。
     */
    public static List<com.example.iot_controlhost.model.Log> queryLatest(String filter) {
        return queryPage(filter, null);
    }

    /**
     * 使用最后一条日志作为游标查询下一页，避免 OFFSET 在并发插入时出现重复或漏项。
     */
    public static List<com.example.iot_controlhost.model.Log> queryNext(String filter,
                                                                        com.example.iot_controlhost.model.Log cursor) {
        if (cursor == null) {
            return Collections.emptyList();
        }
        return queryPage(filter, cursor);
    }

    public static boolean isFullPage(List<?> logs) {
        return logs != null && logs.size() == PAGE_SIZE;
    }

    private static List<com.example.iot_controlhost.model.Log> queryPage(
            String filter, com.example.iot_controlhost.model.Log cursor) {
        return executeSync(() -> {
            QueryBuilder<com.example.iot_controlhost.model.Log> qb = LOG_DAO.queryBuilder();
            List<String> tags = UserLog.getStorageTagsForFilter(filter);
            qb.where(LogDao.Properties.Tag.in(tags));
            if (cursor != null && cursor.getId() != null) {
                qb.where(LogDao.Properties.Id.lt(cursor.getId()));
            }
            return qb.orderDesc(LogDao.Properties.Id)
                    .limit(PAGE_SIZE)
                    .list();
        }, Collections.emptyList());
    }

    /**
     * 兼容旧调用；新界面应使用游标分页。
     */
    public static List<com.example.iot_controlhost.model.Log> queryList(String filter, int currentPage) {
        return executeSync(() -> {
            return LOG_DAO.queryBuilder()
                    .where(LogDao.Properties.Tag.in(UserLog.getStorageTagsForFilter(filter)))
                    .orderDesc(LogDao.Properties.Id)
                    .offset(Math.max(0, currentPage - 1) * PAGE_SIZE)
                    .limit(PAGE_SIZE)
                    .list();
        }, Collections.emptyList());
    }

    public static List<com.example.iot_controlhost.model.Log> queryDateRange(Date startDate, Date endDate) {
        return executeSync(() -> {
            return LOG_DAO.queryBuilder()
                    .where(LogDao.Properties.Datetime.between(startDate, endDate))
                    .orderDesc(LogDao.Properties.Id)
                    .list();
        }, Collections.emptyList());
    }

    /**
     * 导出最近七天系统和网络诊断日志。
     */
    public static List<com.example.iot_controlhost.model.Log> queryAllList() {
        return executeSync(() -> {
            Calendar calendar = Calendar.getInstance();
            Date currentDate = calendar.getTime();
            calendar.add(Calendar.DAY_OF_YEAR, -7);
            Date oneWeekAgo = calendar.getTime();
            return LOG_DAO.queryBuilder()
                    .where(LogDao.Properties.Datetime.between(oneWeekAgo, currentDate))
                    .where(LogDao.Properties.Tag.in(
                            MyLog.TAG_APP,
                            MyLog.TAG_APP + MyLog.TAG_ERROR,
                            MyLog.TAG_NETWORK,
                            MyLog.TAG_NETWORK + MyLog.TAG_ERROR))
                    .orderDesc(LogDao.Properties.Id)
                    .list();
        }, Collections.emptyList());
    }

    public static List<com.example.iot_controlhost.model.Log> queryWorkLogs(int currentPage) {
        return queryList(UserLog.FILTER_ALL, currentPage);
    }

    public static void removeLog(Long id) {
        executeSync(() -> {
            LOG_DAO.deleteByKey(id);
            return null;
        }, null);
    }

    public static void removeAll() {
        executeSync(() -> {
            LOG_DAO.deleteAll();
            return null;
        }, null);
    }

    public static void update(com.example.iot_controlhost.model.Log log) {
        executeSync(() -> {
            LOG_DAO.update(log);
            return null;
        }, null);
    }

    /**
     * 每日后台分批清理十五天前的日志。每批之间主动让出数据库线程，
     * 避免巨量历史数据形成大事务或长时间独占SQLite连接。
     */
    public static void clearExpiredLogsAsync() {
        if (!CLEANUP_RUNNING.compareAndSet(false, true)) {
            Log.i(LOGCAT_TAG, "日志清理任务已在执行，本次跳过");
            return;
        }
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_YEAR, -RETENTION_DAYS);
        CleanupState state = new CleanupState(calendar.getTimeInMillis(), System.currentTimeMillis());
        EXECUTOR.execute(() -> clearNextBatch(state));
    }

    private static void clearNextBatch(CleanupState state) {
        try {
            SQLiteDatabase database = getRawDatabase();
            Batch batch = findExpiredBatch(database, state.lastId, state.cutoffMillis);
            if (batch.count == 0) {
                LOG_DAO.detachAll();
                CLEANUP_RUNNING.set(false);
                long duration = System.currentTimeMillis() - state.startMillis;
                MyLog.app("日志维护完成：保留" + RETENTION_DAYS + "天，分批清理"
                        + state.deletedCount + "条，耗时" + duration + "ms");
                return;
            }

            database.execSQL(
                    "DELETE FROM LOG WHERE _id > ? AND _id <= ? AND DATETIME < ?",
                    new Object[]{state.lastId, batch.lastId, state.cutoffMillis});
            state.lastId = batch.lastId;
            state.deletedCount += batch.count;
            EXECUTOR.schedule(() -> clearNextBatch(state), CLEANUP_BATCH_DELAY_MS, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            CLEANUP_RUNNING.set(false);
            Log.e(LOGCAT_TAG, "分批清理过期日志失败", e);
        }
    }

    private static Batch findExpiredBatch(SQLiteDatabase database, long lastId, long cutoffMillis) {
        long batchLastId = lastId;
        int count = 0;
        String sql = "SELECT _id FROM LOG WHERE _id > ? AND DATETIME < ? ORDER BY _id LIMIT "
                + CLEANUP_BATCH_SIZE;
        try (Cursor cursor = database.rawQuery(sql,
                new String[]{String.valueOf(lastId), String.valueOf(cutoffMillis)})) {
            while (cursor.moveToNext()) {
                batchLastId = cursor.getLong(0);
                count++;
            }
        }
        return new Batch(batchLastId, count);
    }

    private static SQLiteDatabase getRawDatabase() {
        Object rawDatabase = LOG_DAO.getDatabase().getRawDatabase();
        if (!(rawDatabase instanceof SQLiteDatabase)) {
            throw new IllegalStateException("日志数据库类型不受支持：" + rawDatabase);
        }
        return (SQLiteDatabase) rawDatabase;
    }

    private static <T> T executeSync(Callable<T> task, T fallback) {
        if (Thread.currentThread() == databaseThread) {
            try {
                return task.call();
            } catch (Exception e) {
                Log.e(LOGCAT_TAG, "日志数据库操作失败", e);
                return fallback;
            }
        }
        try {
            return EXECUTOR.submit(task).get(SYNC_WAIT_SECONDS, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            Log.e(LOGCAT_TAG, "等待日志数据库操作时被中断", e);
        } catch (ExecutionException e) {
            Log.e(LOGCAT_TAG, "日志数据库操作失败", e.getCause());
        } catch (TimeoutException e) {
            Log.e(LOGCAT_TAG, "等待日志数据库操作超时", e);
        }
        return fallback;
    }

    public interface InsertCallback {
        void onComplete(boolean saved);
    }

    private static final class CleanupState {
        private final long cutoffMillis;
        private final long startMillis;
        private long lastId;
        private long deletedCount;

        private CleanupState(long cutoffMillis, long startMillis) {
            this.cutoffMillis = cutoffMillis;
            this.startMillis = startMillis;
        }
    }

    private static final class Batch {
        private final long lastId;
        private final int count;

        private Batch(long lastId, int count) {
            this.lastId = lastId;
            this.count = count;
        }
    }
}
