/**
 * 自动乐跑月度规则：在 auto_day 中均匀挑选最多 8 天跑 2 公里，其余自动日为 1 公里。
 * 【30 公里】仅为学校规定的「本月最低乐跑里程」参考值（用于进度展示等），不是可跑里程上限。
 */
const Redis = require('../DataBase/Redis.js')
const { fetchJkesMonthKm } = require('./stats.js')
const jkesRedisKeys = require('./redisKeys.js')
const { getJkesSettings } = require('./jkesSettings')

/** 学校要求的本月最低乐跑总距离（公里），仅作展示/达标参考，不限制继续跑步 */
const MONTH_TARGET_KM = 30
const DOUBLE_RUNS_CAP = 8

function parseAutoDay(raw) {
    if (Array.isArray(raw)) return raw.map(Number).filter((n) => n >= 0 && n <= 6)
    if (typeof raw === 'string') {
        try {
            const a = JSON.parse(raw)
            return parseAutoDay(a)
        } catch {
            return []
        }
    }
    return []
}

function normalizeMonthTargetKm(targetKm) {
    const n = Number(targetKm)
    if (!Number.isFinite(n) || n <= 0) return MONTH_TARGET_KM
    return Math.max(1, Math.round(n * 100) / 100)
}

/** 本月所有「落在 auto_day 星期」的日历日（1..31） */
function candidateRunDates(year, month1to12, autoDays) {
    const set = new Set(autoDays.map(Number))
    const last = new Date(year, month1to12, 0).getDate()
    const out = []
    for (let dom = 1; dom <= last; dom++) {
        const wd = new Date(year, month1to12 - 1, dom).getDay()
        if (set.has(wd)) out.push(dom)
    }
    return out
}

/** 在候选日中均匀挑选最多 8 个作为 2 公里日 */
function pickDoubleDays(candidates) {
    if (candidates.length === 0) return new Set()
    if (candidates.length <= DOUBLE_RUNS_CAP) return new Set(candidates)
    const out = []
    for (let i = 0; i < DOUBLE_RUNS_CAP; i++) {
        const idx = Math.min(candidates.length - 1, Math.floor((i + 0.5) * candidates.length / DOUBLE_RUNS_CAP))
        out.push(candidates[idx])
    }
    return new Set(out)
}

function monthKey(account, d = new Date()) {
    return jkesRedisKeys.monthState(account, d.getFullYear(), d.getMonth() + 1)
}

function monthTtlSec(d = new Date()) {
    const y = d.getFullYear()
    const m = d.getMonth()
    const end = new Date(y, m + 1, 7, 23, 59, 59)
    return Math.max(86400, Math.floor((end.getTime() - Date.now()) / 1000))
}

async function readState(account, d = new Date()) {
    const key = monthKey(account, d)
    const raw = await Redis.get(key)
    if (!raw) {
        return { km: 0, doubles: 0 }
    }
    try {
        const o = JSON.parse(raw)
        return {
            km: Number(o.km) || 0,
            doubles: Number(o.doubles) || 0
        }
    } catch {
        return { km: 0, doubles: 0 }
    }
}

async function writeState(account, state, d = new Date()) {
    const key = monthKey(account, d)
    const ttl = monthTtlSec(d)
    await Redis.set(key, JSON.stringify(state), { EX: ttl })
}

/**
 * 用官方列表刷新本月已跑千米数（不覆盖 doubles 计数）
 */
async function refreshMonthKmFromApi(account, token, d = new Date()) {
    if (!token) return
    const y = d.getFullYear()
    const m = d.getMonth() + 1
    const apiKm = await fetchJkesMonthKm(token, y, m)
    const prev = await readState(account, d)
    /**
     * 官方 mylist 里程、配速往往延迟数分钟～数十分钟才更新。
     * 但本地 Redis 可能因历史累计/异常写入变得非常大，不能无条件取 max，
     * 否则会导致“未达标却被判定已达标”。
     * 仅在差距较小（<=5km）时才允许用本地值兜底。
     */
    const prevKm = Number(prev.km) || 0
    const apiKmNum = Number(apiKm) || 0
    let km = apiKmNum
    if (apiKmNum <= 0 && prevKm > 0) {
        km = prevKm
    } else {
        const drift = prevKm - apiKmNum
        if (drift > 0 && drift <= 5) {
            km = prevKm
        }
    }
    await writeState(account, { km, doubles: prev.doubles }, d)
    return km
}

/**
 * 成功跑完后更新本地月度缓存（千米，来自 end 接口或回退目标距离）
 * @param {number} actualKm 本次计入里程
 * @param {{ autoDoubleSlot?: boolean }} [options] 自动任务且为计划中的 2 公里日时递增 doubles
 */
async function recordSuccess(account, actualKm, options = {}, d = new Date()) {
    const prev = await readState(account, d)
    const add = Math.max(0, Number(actualKm) || 0)
    const km = prev.km + add
    let doubles = prev.doubles
    if (options.autoDoubleSlot) {
        doubles = prev.doubles + 1
    }
    await writeState(account, { km, doubles }, d)
}

function isDoubleKmDom(year, month1to12, autoDays, dom) {
    const c = candidateRunDates(year, month1to12, autoDays)
    const doubles = pickDoubleDays(c)
    return doubles.has(dom)
}

function roundToStep(value, step = 0.5) {
    const s = Number(step)
    if (!Number.isFinite(s) || s <= 0) return Math.round(value * 100) / 100
    return Math.round(Math.ceil(value / s) * s * 100) / 100
}

/**
 * 自动任务：在 auto_day 且当日可跑时投递；2 公里日最多占满 DOUBLE_RUNS_CAP 次，之后均为 1 公里。
 * stopAfterMinimum=true 时，达到 monthTargetKm 后不再自动跑。
 */
async function planJkesAutoRun(account, autoDayRaw, token, options = {}, d = new Date()) {
    const cfg = getJkesSettings()
    const monthTargetKm = normalizeMonthTargetKm(options.monthTargetKm)
    const stopAfterMinimum = options.stopAfterMinimum !== false
    const maxAutoSingleKm = Math.max(2, Number(cfg.autoSingleRunMaxKm) || 10)
    const stepKm = Number(cfg.autoDistanceStepKm) || 0.1

    const autoDays = parseAutoDay(autoDayRaw)
    const wd = d.getDay()
    if (autoDays.length > 0 && !autoDays.includes(wd)) {
        return { run: false, reason: '非自动乐跑星期' }
    }

    await refreshMonthKmFromApi(account, token, d)
    const state = await readState(account, d)
    if (stopAfterMinimum && state.km >= monthTargetKm) {
        return { run: false, reason: `本月已达目标 ${monthTargetKm}km` }
    }

    const y = d.getFullYear()
    const m = d.getMonth() + 1
    const dom = d.getDate()
    const candidates = candidateRunDates(y, m, autoDays).filter((x) => x >= dom)
    const remainingDays = candidates.length || 1
    const remainingKm = Math.max(0, monthTargetKm - state.km)

    // 不补跑前提下，为保证月底前达标，按剩余自动日动态放大单次公里
    const requiredBySchedule = remainingKm > 0 ? remainingKm / remainingDays : 1
    let targetKm = Math.max(1, roundToStep(requiredBySchedule, stepKm))

    // 至少 8 天达到 >=2km：按均匀双倍日 + 末期兜底两层保障
    const doubleRemaining = Math.max(0, DOUBLE_RUNS_CAP - (Number(state.doubles) || 0))
    const mustDoubleToday = doubleRemaining > 0 && doubleRemaining >= remainingDays
    const plannedDoubleToday = isDoubleKmDom(y, m, autoDays, dom)
    if (mustDoubleToday || plannedDoubleToday) {
        targetKm = Math.max(targetKm, 2.5)
    }
    targetKm = Math.min(maxAutoSingleKm, Math.max(1, Math.round(targetKm * 100) / 100))
    const autoDoubleSlot = mustDoubleToday || plannedDoubleToday
    return { run: true, targetKm, monthTargetKm, autoDoubleSlot }
}

/**
 * 手动发起：不校验星期；单次 distanceKm 为 1–10。本月已跑里程不设上限（30km 仅为学校最低要求参考）。
 */
async function planJkesManualRun(account, token, distanceKm = 1, d = new Date()) {
    const kmReq = Number(distanceKm)
    if (!Number.isFinite(kmReq) || kmReq < 1 || kmReq > 10) {
        return { run: false, reason: '单次距离需在 1–10 公里' }
    }
    await refreshMonthKmFromApi(account, token, d)
    return { run: true, targetKm: Math.round(kmReq * 1000) / 1000 }
}

module.exports = {
    MONTH_TARGET_KM,
    DOUBLE_RUNS_CAP,
    parseAutoDay,
    normalizeMonthTargetKm,
    monthKey,
    readState,
    writeState,
    refreshMonthKmFromApi,
    recordSuccess,
    planJkesAutoRun,
    planJkesManualRun
}
