/**
 * 日程执行器
 * 项目启动时加载有效期内的日程，通过关联设备表获取执行设备，
 * 按 schedule_time 每天定时触发，调用飞奕 controlIndoorUnit API 控制空调设备。
 *
 * 特性：
 * - 启动时加载所有有效日程并设置 setTimeout
 * - 执行失败时自动重试（最多 3 次，间隔 30s/60s/120s）
 * - 执行后自动安排下一次（第二天同一时间）
 * - 每天凌晨 00:05 自动重载，处理新增/过期日程
 * - 增删改日程后可通过 reloadScheduleTasks() 立即生效
 * - 设备为空则跳过执行（不安排定时器）
 */

import { selectDataListByParam } from "../data/findData";
import { addData } from "../data/addData";
import { TABLENAME } from "../config/dbEnum";
import { controlIndoorUnit } from "./feiyiClient";

// ======================= 常量 =======================

/** 重试配置 */
const RETRY_CONFIG = {
    maxRetries: 3,
    delays: [30_000, 60_000, 120_000], // 30s, 60s, 120s
};

/**
 * 空调参数中文到 API 数字的映射
 * 与 running.ts 中的 acDeviceKeyMap 保持一致
 */
const AC_KEY_MAP: Record<string, number> = {
    // 模式
    "制冷": 1,
    "制热": 2,
    "送风": 3,
    "除湿": 4,
    // 开关
    "on": 1,
    "off": 0,
    // 风速
    "超强": 1,
    "高风": 1,
    "强": 2,
    "中风": 2,
    "弱": 4,
    "低风": 4,
    // 锁定
    "解除": 1,
    "生效": 0,
};

// ======================= 数据结构 =======================

/** 活跃定时器记录 */
interface ActiveTimer {
    scheduleId: number;
    timer: NodeJS.Timeout;
    title: string;
}

/** 所有活跃的定时器 Map<scheduleId, ActiveTimer> */
const activeTimers: Map<number, ActiveTimer> = new Map();

/** 每日重载定时器 */
let dailyReloadTimer: NodeJS.Timeout | null = null;

// ======================= 工具函数 =======================

/** 格式化为本地时间字符串 YYYY-MM-DD HH:mm:ss */
function formatLocalTime(d: Date): string {
    const pad = (n: number) => String(n).padStart(2, "0");
    return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}

/**
 * 根据 schedule_time（HH:mm:ss）计算下一次执行时间
 * 如果今天的时间已过，安排到明天
 */
function calcNextExecutionTime(timeStr: string): Date {
    const now = new Date();
    const parts = timeStr.split(":").map(Number);
    const next = new Date(now);
    next.setHours(parts[0] || 0, parts[1] || 0, parts[2] || 0, 0);
    if (next.getTime() <= now.getTime()) {
        next.setDate(next.getDate() + 1);
    }
    return next;
}

/** 判断日程是否在有效期内 */
function isInValidity(begin: string | null | undefined, end: string | null | undefined): boolean {
    const now = new Date();
    if (begin && new Date(begin) > now) return false;
    if (end && new Date(end) < now) return false;
    return true;
}

/** 判断日程是否已过期（end_validity 已过，不应再排下次） */
function isExpired(end: string | null | undefined): boolean {
    if (!end) return false;
    return new Date(end) < new Date();
}

// ======================= 核心逻辑 =======================

/**
 * 构建 controlIndoorUnit 的请求参数
 */
function buildControlParams(
    deviceIds: string[],
    acControl: string,
    todoItem: Record<string, string> | null | undefined
): Record<string, any> {
    const params: Record<string, any> = {
        indoorUnitAddressFull: deviceIds,
        onOff: AC_KEY_MAP[acControl] ?? 1,
        openApiAction: "control",
        workMode: 1 // 传一个默认模式
    };

    // 关机只发送 onOff=0，不传模式/温度/风速
    if (acControl === "off") {
        return params;
    }

    if (todoItem) {
        if (todoItem["温度"] != null) {
            params.tempSet = Number(todoItem["温度"]);
        }
        if (todoItem["模式"] && AC_KEY_MAP[todoItem["模式"]] != null) {
            params.workMode = AC_KEY_MAP[todoItem["模式"]];
        }
        if (todoItem["风速"] && AC_KEY_MAP[todoItem["风速"]] != null) {
            params.fanSpeed = AC_KEY_MAP[todoItem["风速"]];
        }
    }

    return params;
}

/**
 * 执行单次设备控制 + 写入设备日志
 * @returns 执行结果 { success, message }
 */
async function executeControl(
    scheduleId: number,
    title: string,
    deviceIds: string[],
    acControl: string,
    todoItem: Record<string, string> | null | undefined
): Promise<{ success: boolean; message: string }> {
    console.log(`[日程执行器] 执行日程 #${scheduleId} "${title}", 设备: ${deviceIds.join(",")}, 操作: ${acControl}`);

    // 设备为空直接跳过
    if (deviceIds.length === 0) {
        console.log(`[日程执行器] 日程 #${scheduleId} 没有关联设备，跳过执行`);
        return { success: true, message: "无关联设备，跳过" };
    }

    const params = buildControlParams(deviceIds, acControl, todoItem);
    const now = new Date();

    // 调用第三方 API
    let result: { success: boolean; message: string };
    try {
        const res = await controlIndoorUnit(params);
        result = { success: res.success, message: res.data ?? "" };
    } catch (err: any) {
        result = { success: false, message: err.message || "控制异常" };
    }

    // 批量写入设备日志
    const logs = deviceIds.map((deviceId) => ({
        device_id: deviceId,
        device_type: "空调",
        operation_content: {
            option: `日程定时执行: ${title}`,
            scheduleId,
            time: formatLocalTime(now),
            acControl,
            params: { ...params, indoorUnitAddressFull: deviceIds.join(",") },
            success: result.success,
            message: result.message,
        },
        status: result.success ? 1 : 0,
        created_at: now,
        updated_at: now,
    }));

    try {
        await addData(TABLENAME.设备日志表, logs);
    } catch (err) {
        console.error(`[日程执行器] 写入设备日志失败:`, err);
    }

    if (result.success) {
        console.log(`[日程执行器] 日程 #${scheduleId} "${title}" 执行成功`);
    } else {
        console.error(`[日程执行器] 日程 #${scheduleId} "${title}" 执行失败: ${result.message}`);
    }

    return result;
}

/**
 * 带重试的执行逻辑
 * 最多重试 RETRY_CONFIG.maxRetries 次，间隔递增
 */
async function executeWithRetry(
    scheduleId: number,
    title: string,
    deviceIds: string[],
    acControl: string,
    todoItem: Record<string, string> | null | undefined
): Promise<void> {
    let lastResult: { success: boolean; message: string } | null = null;

    for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
        if (attempt > 0) {
            const delay = RETRY_CONFIG.delays[attempt - 1] ?? 120_000;
            console.log(`[日程执行器] 日程 #${scheduleId} 第 ${attempt} 次重试，等待 ${delay / 1000}s...`);
            await new Promise((resolve) => setTimeout(resolve, delay));
        }

        const result = await executeControl(scheduleId, title, deviceIds, acControl, todoItem);
        lastResult = result;

        if (result.success) {
            return; // 成功，结束
        }

        console.error(`[日程执行器] 日程 #${scheduleId} 第 ${attempt} 次执行失败: ${result.message}`);
    }

    console.error(
        `[日程执行器] 日程 #${scheduleId} "${title}" 重试 ${RETRY_CONFIG.maxRetries} 次后仍失败: ${lastResult?.message}`
    );
}

/**
 * 为单个日程安排下一次执行
 * 执行完成后自动排下一次（第二天同一时间）
 */
function scheduleNextRun(schedule: any, deviceIds: string[]): void {
    const { id, title, schedule_time, ac_control, todo_item, end_validity } = schedule;

    // 已过期不再调度
    if (isExpired(end_validity)) {
        console.log(`[日程执行器] 日程 #${id} "${title}" 已过有效期，不再调度`);
        return;
    }

    const nextTime = calcNextExecutionTime(schedule_time);
    const delayMs = nextTime.getTime() - Date.now();

    console.log(
        `[日程执行器] 日程 #${id} "${title}" 下次执行: ${formatLocalTime(nextTime)} (${Math.round(delayMs / 1000)}s 后)`
    );

    const timer = setTimeout(async () => {
        // 执行前再次检查有效期（可能在等待期间越过 end_validity）
        if (isExpired(end_validity)) {
            console.log(`[日程执行器] 日程 #${id} "${title}" 在等待期间过期，取消执行`);
            activeTimers.delete(id);
            return;
        }

        // 带重试执行
        await executeWithRetry(id, title, deviceIds, ac_control, todo_item);

        // 安排下一次
        activeTimers.delete(id);
        scheduleNextRun(schedule, deviceIds);
    }, delayMs);

    activeTimers.set(id, { scheduleId: id, timer, title });
}

// ======================= 公开接口 =======================

/** 清除所有活跃定时器 */
function clearAllTimers(): void {
    console.log(`[日程执行器] 清除 ${activeTimers.size} 个活跃定时器`);
    for (const [, entry] of activeTimers) {
        clearTimeout(entry.timer);
    }
    activeTimers.clear();
}

/**
 * 从数据库加载所有有效期内的日程，安排定时执行
 */
async function loadAndScheduleAll(): Promise<void> {
    console.log("[日程执行器] 开始加载日程...");

    try {
        const scheduleRes = await selectDataListByParam(
            TABLENAME.日程表,
            {},
            ["id", "title", "ac_control", "todo_item", "schedule_time", "begin_validity", "end_validity"]
        );
        const schedules: any[] = scheduleRes.data || [];

        let scheduledCount = 0;

        for (const schedule of schedules) {
            const { id, title, schedule_time, begin_validity, end_validity } = schedule;

            // 没有 schedule_time 的跳过
            if (!schedule_time) {
                console.log(`[日程执行器] 日程 #${id} "${title}" 未设置 schedule_time，跳过`);
                continue;
            }

            // 不在有效期内跳过
            if (!isInValidity(begin_validity, end_validity)) {
                console.log(`[日程执行器] 日程 #${id} "${title}" 不在有效期内，跳过`);
                continue;
            }

            // 查询关联设备
            const sdRes = await selectDataListByParam(
                TABLENAME.日程设备关联表,
                { schedule_id: id },
                ["device_id"]
            );
            const sdRows: any[] = sdRes.data || [];
            const deviceIds = sdRows
                .map((r: any) => r.device_id)
                .filter((did: string) => did && did.trim() !== "");

            // 没有有效设备则跳过
            if (deviceIds.length === 0) {
                console.log(`[日程执行器] 日程 #${id} "${title}" 没有有效设备，跳过`);
                continue;
            }

            // 安排执行
            scheduleNextRun(schedule, deviceIds);
            scheduledCount++;
        }

        console.log(`[日程执行器] 加载完成，共安排 ${scheduledCount} 个日程`);
    } catch (err) {
        console.error("[日程执行器] 加载日程失败:", err);
    }
}

/**
 * 安排每日凌晨 00:05 重载
 * 用于自动处理新增、过期日程
 */
function scheduleDailyReload(): void {
    if (dailyReloadTimer) {
        clearTimeout(dailyReloadTimer);
    }

    const now = new Date();
    const reloadTime = new Date(now);
    reloadTime.setDate(reloadTime.getDate() + 1);
    reloadTime.setHours(0, 5, 0, 0);

    const delayMs = reloadTime.getTime() - now.getTime();
    console.log(`[日程执行器] 每日重载将在 ${formatLocalTime(reloadTime)} 执行 (${Math.round(delayMs / 1000)}s 后)`);

    dailyReloadTimer = setTimeout(async () => {
        console.log("[日程执行器] 开始每日重载...");
        clearAllTimers();
        await loadAndScheduleAll();
        scheduleDailyReload();
    }, delayMs);
}

/**
 * 初始化日程任务（项目启动时调用）
 */
export async function initScheduleTasks(): Promise<void> {
    console.log("[日程执行器] ========== 初始化日程任务 ==========");
    clearAllTimers();
    await loadAndScheduleAll();
    scheduleDailyReload();
    console.log("[日程执行器] ========== 初始化完成 ==========");
}

/**
 * 重载日程任务（增/删/改日程后调用，立即生效）
 */
export async function reloadScheduleTasks(): Promise<void> {
    console.log("[日程执行器] ========== 重载日程任务 ==========");
    clearAllTimers();
    if (dailyReloadTimer) {
        clearTimeout(dailyReloadTimer);
        dailyReloadTimer = null;
    }
    await loadAndScheduleAll();
    scheduleDailyReload();
    console.log("[日程执行器] ========== 重载完成 ==========");
}

/**
 * 获取当前活跃的定时任务信息（调试用）
 */
export function getActiveScheduleTasks(): Array<{ scheduleId: number; title: string }> {
    const result: Array<{ scheduleId: number; title: string }> = [];
    for (const [, entry] of activeTimers) {
        result.push({ scheduleId: entry.scheduleId, title: entry.title });
    }
    return result;
}
