import test from "node:test"
import assert from "node:assert/strict"
import { mkdtemp, rm } from "node:fs/promises"
import { tmpdir } from "node:os"
import path from "node:path"

import {
  AutomationTaskService,
  computeAutomationNextRunAt
} from "../src/main/services/automation-task-service.ts"

async function withService<T>(
  run: (service: AutomationTaskService, userDataPath: string) => Promise<T>,
  now: Date | (() => Date) = new Date(2026, 4, 19, 9, 0, 0, 0)
) {
  const userDataPath = await mkdtemp(path.join(tmpdir(), "qjc-automation-"))
  try {
    const getNow = () => typeof now === "function" ? now() : now
    const service = new AutomationTaskService(userDataPath, {
      now: () => new Date(getNow()),
      autoStart: false
    })
    service.setExecutor(async ({ task, run: taskRun }) => ({
      sessionId: `session-${task.id}`,
      runId: taskRun.id,
      replyText: `done:${task.prompt}`,
      artifacts: []
    }))
    return await run(service, userDataPath)
  } finally {
    await rm(userDataPath, { recursive: true, force: true })
  }
}

test("computes next run for one-time, daily, and weekly schedules", () => {
  const from = new Date(2026, 4, 19, 9, 30, 0, 0)

  assert.equal(
    computeAutomationNextRunAt({ kind: "once", runAt: "2026-05-19T10:00:00.000Z" }, from),
    "2026-05-19T10:00:00.000Z"
  )
  assert.equal(
    computeAutomationNextRunAt({ kind: "once", runAt: new Date(2026, 4, 19, 9, 0, 0, 0).toISOString() }, from),
    undefined
  )
  assert.equal(
    computeAutomationNextRunAt({ kind: "daily", time: "10:15" }, from),
    new Date(2026, 4, 19, 10, 15, 0, 0).toISOString()
  )
  assert.equal(
    computeAutomationNextRunAt({ kind: "daily", time: "08:15" }, from),
    new Date(2026, 4, 20, 8, 15, 0, 0).toISOString()
  )
  assert.equal(
    computeAutomationNextRunAt({ kind: "weekly", time: "10:00", weekdays: [2] }, from),
    new Date(2026, 4, 19, 10, 0, 0, 0).toISOString()
  )
  assert.equal(
    computeAutomationNextRunAt({ kind: "weekly", time: "08:00", weekdays: [2] }, from),
    new Date(2026, 4, 26, 8, 0, 0, 0).toISOString()
  )
})

test("creates, updates, deletes, and persists automation tasks", async () => {
  await withService(async (service, userDataPath) => {
    const created = await service.create({
      title: "每日复盘",
      prompt: "整理昨天内容表现",
      expertId: "zhihu",
      expertName: "知乎专家",
      enabled: true,
      schedule: { kind: "daily", time: "10:00" }
    })
    assert.equal(created.title, "每日复盘")
    assert.equal(created.nextRunAt, new Date(2026, 4, 19, 10, 0, 0, 0).toISOString())

    const reloaded = new AutomationTaskService(userDataPath, {
      now: () => new Date(2026, 4, 19, 9, 0, 0, 0),
      autoStart: false
    })
    assert.equal((await reloaded.list()).length, 1)

    const updated = await service.update(created.id, {
      enabled: false,
      title: "每日复盘更新"
    })
    assert.equal(updated?.enabled, false)
    assert.equal(updated?.nextRunAt, undefined)

    assert.equal(await service.delete(created.id), true)
    assert.deepEqual(await service.list(), [])
  })
})

test("rejects enabled one-time tasks scheduled in the past", async () => {
  await withService(async (service) => {
    await assert.rejects(
      service.create({
        title: "过期一次性任务",
        prompt: "不应启用",
        enabled: true,
        schedule: { kind: "once", runAt: new Date(2026, 4, 18, 9, 0, 0, 0).toISOString() }
      }),
      /future/
    )

    const disabledPastTask = await service.create({
      title: "已停用过期任务",
      prompt: "允许保存",
      enabled: false,
      schedule: { kind: "once", runAt: new Date(2026, 4, 18, 9, 0, 0, 0).toISOString() }
    })

    await assert.rejects(
      service.update(disabledPastTask.id, { enabled: true }),
      /future/
    )
  })
})

test("marks missed app-runtime schedules without auto backfill", async () => {
  await withService(async (service, userDataPath) => {
    await service.create({
      title: "早报",
      prompt: "生成早报",
      enabled: true,
      schedule: { kind: "daily", time: "08:00" }
    })

    const restarted = new AutomationTaskService(userDataPath, {
      now: () => new Date(2026, 4, 19, 9, 0, 0, 0),
      autoStart: false
    })
    await restarted.recoverMissedRuns()

    const [task] = await restarted.list()
    assert.equal(task?.lastRunStatus, "missed")
    assert.equal(task?.nextRunAt, new Date(2026, 4, 20, 8, 0, 0, 0).toISOString())

    const runs = await restarted.listRuns(task!.id)
    assert.equal(runs.length, 1)
    assert.equal(runs[0]?.status, "missed")
  }, new Date(2026, 4, 18, 7, 0, 0, 0))
})

test("manual runNow executes the configured executor and records output", async () => {
  await withService(async (service) => {
    service.setExecutor(async ({ task, run: taskRun }) => ({
      sessionId: `automation:home-chat:${task.id}:${taskRun.id}`,
      runId: taskRun.id,
      replyText: `done:${task.prompt}`,
      artifacts: []
    }))
    const task = await service.create({
      title: "线索整理",
      prompt: "整理线索",
      enabled: false,
      schedule: { kind: "once", runAt: "2026-05-20T09:00:00.000Z" }
    })

    const run = await service.runNow(task.id)
    assert.equal(run.status, "completed")
    assert.equal(run.replyText, "done:整理线索")
    assert.equal(run.sessionId, `automation:home-chat:${task.id}:${run.id}`)

    const runs = await service.listRuns(task.id)
    assert.equal(runs[0]?.id, run.id)
    assert.equal((await service.list())[0]?.lastRunStatus, "completed")
  })
})

test("scheduled one-time task disables itself after completing", async () => {
  let now = new Date(2026, 4, 19, 8, 0, 0, 0)
  await withService(async (service) => {
    const task = await service.create({
      title: "一次性整理",
      prompt: "执行一次",
      enabled: true,
      schedule: { kind: "once", runAt: new Date(2026, 4, 19, 9, 0, 0, 0).toISOString() }
    })

    now = new Date(2026, 4, 19, 9, 0, 0, 0)
    await service["runDueTasks"]()

    const [updated] = await service.list()
    assert.equal(updated?.id, task.id)
    assert.equal(updated?.enabled, false)
    assert.equal(updated?.nextRunAt, undefined)
    assert.equal(updated?.lastRunStatus, "completed")
  }, () => now)
})

test("missed one-time task disables itself and keeps a missed run", async () => {
  await withService(async (service, userDataPath) => {
    const task = await service.create({
      title: "错过的一次性任务",
      prompt: "错过后保留",
      enabled: true,
      schedule: { kind: "once", runAt: new Date(2026, 4, 18, 9, 0, 0, 0).toISOString() }
    })

    const restarted = new AutomationTaskService(userDataPath, {
      now: () => new Date(2026, 4, 19, 9, 0, 0, 0),
      autoStart: false
    })
    await restarted.recoverMissedRuns()

    const [updated] = await restarted.list()
    assert.equal(updated?.id, task.id)
    assert.equal(updated?.enabled, false)
    assert.equal(updated?.nextRunAt, undefined)
    assert.equal(updated?.lastRunStatus, "missed")

    const runs = await restarted.listRuns(task.id)
    assert.equal(runs.length, 1)
    assert.equal(runs[0]?.status, "missed")
  }, new Date(2026, 4, 18, 7, 0, 0, 0))
})
