import test from "node:test"
import assert from "node:assert/strict"
import { readFileSync } from "node:fs"

const sharedTypesSource = readFileSync(new URL("../../../packages/shared-types/src/index.ts", import.meta.url), "utf8")
const ipcSource = readFileSync(new URL("../src/main/ipc.ts", import.meta.url), "utf8")
const indexSource = readFileSync(new URL("../src/main/index.ts", import.meta.url), "utf8")
const preloadSource = readFileSync(new URL("../src/preload/index.ts", import.meta.url), "utf8")

function getAutomationExecutorBody(): string {
  const executorMatch = ipcSource.match(/automationTaskService\.setExecutor\(async \(\{ task, run \}\) => \{(?<body>[\s\S]*?)\n  \}\);/)
  assert.ok(executorMatch?.groups?.body)
  return executorMatch.groups.body
}

function getExecuteAutomationPromptBody(): string {
  const bodyMatch = ipcSource.match(/const executeAutomationPrompt = async \(sessionId: string, projectId: string, prompt: string\) => \{(?<body>[\s\S]*?)\n  \};\n\n  const streamPrompt = async/)
  assert.ok(bodyMatch?.groups?.body)
  return bodyMatch.groups.body
}

function getSendPromptBody(): string {
  const bodyMatch = ipcSource.match(/const sendPrompt = async \(sessionId: string, prompt: string, skillId\?: string, attachments\?: ChatAttachment\[\]\) => \{(?<body>[\s\S]*?)\n  \};\n\n  const executeAutomationPrompt = async/)
  assert.ok(bodyMatch?.groups?.body)
  return bodyMatch.groups.body
}

function getStreamPromptBody(): string {
  const bodyMatch = ipcSource.match(/const streamPrompt = async \(sessionId: string, prompt: string, skillId\?: string, attachments\?: ChatAttachment\[\], sender\?: WebContents\) => \{(?<body>[\s\S]*?)\n  \};\n  const cancelStream = async/)
  assert.ok(bodyMatch?.groups?.body)
  return bodyMatch.groups.body
}

test("shared automation task channels and desktop API are declared", () => {
  assert.match(sharedTypesSource, /automationTasksList:\s*"automation-tasks:list"/)
  assert.match(sharedTypesSource, /automationTasksCreate:\s*"automation-tasks:create"/)
  assert.match(sharedTypesSource, /automationTasksUpdate:\s*"automation-tasks:update"/)
  assert.match(sharedTypesSource, /automationTasksDelete:\s*"automation-tasks:delete"/)
  assert.match(sharedTypesSource, /automationTasksRunNow:\s*"automation-tasks:run-now"/)
  assert.match(sharedTypesSource, /automationTasksListRuns:\s*"automation-tasks:list-runs"/)
  assert.match(sharedTypesSource, /automationTasks:\s*\{/)
})

test("desktop IPC wires automation tasks to AutomationTaskService", () => {
  assert.match(indexSource, /new AutomationTaskService\(systemSummary\.userDataPath/)
  assert.match(indexSource, /automationTaskService\.start\(\)/)
  assert.match(ipcSource, /automationTaskService\.setExecutor/)
  assert.match(ipcSource, /ipcMain\.handle\(IPC_CHANNELS\.automationTasksList/)
  assert.match(ipcSource, /ipcMain\.handle\(IPC_CHANNELS\.automationTasksRunNow/)
  assert.match(preloadSource, /automationTasks:\s*\{/)
  assert.match(preloadSource, /IPC_CHANNELS\.automationTasksCreate/)
})

test("automation executor runs in the background without creating visible chat sessions", () => {
  const executorBody = getAutomationExecutorBody()

  assert.match(executorBody, /const sessionId = `automation:\$\{projectId\}:\$\{task\.id\}:\$\{run\.id\}`/)
  assert.doesNotMatch(executorBody, /projectStore\.createSession/)
  assert.doesNotMatch(executorBody, /sendPrompt\(/)
  assert.doesNotMatch(executorBody, /\[自动化\]/)
})

test("automation executor archives completed runs to the task panel", () => {
  const executorBody = getAutomationExecutorBody()

  assert.match(executorBody, /const expertName = await resolveAutomationTaskPanelExpertName\(task, projectId\)/)
  assert.match(executorBody, /taskPanelService\.recordWorkspaceExecution\(\{/)
  assert.match(executorBody, /sessionId: result\.sessionId/)
  assert.match(executorBody, /runId: run\.id/)
  assert.match(executorBody, /expertName/)
  assert.match(executorBody, /taskTitle: task\.title/)
  assert.match(executorBody, /messageCount: 2/)
  assert.match(executorBody, /artifacts: toAutomationTaskPanelArtifacts\(result\.artifacts, result\.reply\.content, run\.id\)/)
})

test("automation task panel archive uses general assistant fallback and reply artifacts", () => {
  const artifactHelperMatch = ipcSource.match(/const toAutomationTaskPanelArtifacts = \([\s\S]*?\n  \};/)
  assert.ok(artifactHelperMatch?.[0])
  const artifactHelper = artifactHelperMatch[0]

  assert.match(ipcSource, /task\.expertName\?\.trim\(\)/)
  assert.match(ipcSource, /project\?\.displayName\?\.trim\(\)/)
  assert.match(ipcSource, /project\?\.name\?\.trim\(\)/)
  assert.match(ipcSource, /projectId === BUILTIN_HOME_PROJECT_ID \? "通用助手" : projectId/)
  assert.match(artifactHelper, /name: "自动化执行结果"/)
  assert.match(artifactHelper, /kind: "回复"/)
  assert.doesNotMatch(artifactHelper, /path:/)
  assert.doesNotMatch(artifactHelper, /url:/)
})

test("automation prompt execution refreshes project context after completion without binding a visible session", () => {
  const automationPromptBody = getExecuteAutomationPromptBody()
  const refreshCallMatch = automationPromptBody.match(/refreshProjectContextAfterExecution\(\{(?<args>[\s\S]*?)\}\)/)
  assert.ok(refreshCallMatch?.groups?.args)

  assert.match(automationPromptBody, /const shouldScheduleContextRefresh = shouldRefreshProjectContextAfterExecution\(decision\)/)
  assert.match(automationPromptBody, /finally\s*\{/)
  assert.match(refreshCallMatch.groups.args, /projectId: project\.id/)
  assert.match(refreshCallMatch.groups.args, /projectContextService/)
  assert.match(refreshCallMatch.groups.args, /projectStore/)
  assert.doesNotMatch(refreshCallMatch.groups.args, /sessionId/)
})

test("automation prompts restrict online research to domestic China sources", () => {
  const automationPromptBody = getExecuteAutomationPromptBody()

  assert.match(ipcSource, /AUTOMATION_DOMESTIC_SOURCE_CONSTRAINT/)
  assert.match(ipcSource, /中国境内网站资料/)
  assert.match(ipcSource, /未找到可用国内资料/)
  assert.match(ipcSource, /Google、Wikipedia、YouTube、Twitter\/X、Reddit/)
  assert.match(ipcSource, /baidu\.com、qq\.com、163\.com、sina\.com\.cn、sohu\.com、people\.com\.cn、xinhuanet\.com、cctv\.com/)
  assert.match(automationPromptBody, /const automationPrompt = withAutomationDomesticSourceConstraint\(prompt\)/)
  assert.match(automationPromptBody, /userPrompt: automationPrompt/)
  assert.match(automationPromptBody, /projectSkillRouter\.resolve\(project\.id, prompt\)/)
  assert.match(automationPromptBody, /userPrompt: automationPrompt/)
  assert.match(automationPromptBody, /userPrompt: automationPrompt,\n\s+attachments: \[\]/)
  assert.match(automationPromptBody, /gatewayClient\.sendPrompt\(sessionId, withAutomationDomesticSourceConstraint\(result\.handoff\.content\)\)/)
  assert.match(automationPromptBody, /gatewayClient\.sendPrompt\(sessionId, gatewayPrompt \?\? automationPrompt\)/)
})

test("automation domestic source guard cannot be bypassed by marker text alone", () => {
  assert.doesNotMatch(ipcSource, /trimmed\.includes\(AUTOMATION_DOMESTIC_SOURCE_CONSTRAINT_MARKER\)/)
  assert.match(ipcSource, /trimmed\.endsWith\(AUTOMATION_DOMESTIC_SOURCE_CONSTRAINT\)/)
})

test("ordinary chat prompts do not receive automation domestic source constraints", () => {
  assert.doesNotMatch(getSendPromptBody(), /withAutomationDomesticSourceConstraint/)
  assert.doesNotMatch(getStreamPromptBody(), /withAutomationDomesticSourceConstraint/)
})
