/**
 * Execute a cron task: run through the Claude Agent SDK and optionally notify via IM.
 * Each execution creates a new Session visible in the chat list.
 */

import { cronTaskRepo } from '@/lib/database/repositories/cron-task.repo'
import { sessionRepo } from '@/lib/database/repositories/session.repo'
import { messageRepo } from '@/lib/database/repositories/message.repo'
import { channelBindingRepo } from '@/lib/database/repositories/channel-binding.repo'
import crypto from 'crypto'
import { EngineRegistry } from '@/lib/engine'
import { readWorkspaceFile } from '@/lib/workspace-fs'
import { getBridgeManager } from '@/lib/im/core/bridge-manager'
import { emitImEvent } from '@/lib/im/shared/im-events'
import { getSetting } from '@/lib/settings-store'
import { modelRegistry } from '@/lib/engine/model-registry'
import { getMostRecentAccessibleWorkspace } from '@/lib/auth/workspace-queries'

type CronTask = NonNullable<ReturnType<typeof cronTaskRepo.findById>>

interface TaskConfig {
  check_interval?: string
  notify_channel?: string
  checklist_path?: string
}

interface TaskResult {
  status: 'ok' | 'alert' | 'error'
  result: string
  sessionId: string
}

export async function executeTask(task: CronTask): Promise<TaskResult> {
  try {
    if (task.isHeartbeat) {
      return await executeHeartbeat(task)
    }

    const actionType = task.actionType || 'custom-prompt'

    switch (actionType) {
      case 'run-agent':
        return await executeRunAgent(task)
      case 'run-skill':
        return await executeRunSkill(task)
      case 'custom-prompt':
      default:
        return await executeCustomPrompt(task)
    }
  } catch (err) {
    const errorMsg = err instanceof Error ? err.message : 'Unknown error'
    return { status: 'error', result: errorMsg, sessionId: '' }
  }
}

/**
 * Resolve the workspace_id to use for this task.
 * For heartbeat: uses the task's workspace_id or falls back to most recently opened.
 * For others: uses the task's workspace_id (required).
 */
function resolveWorkspaceId(task: CronTask): string | null {
  if (task.workspaceId) return task.workspaceId

  // Fallback for heartbeat: use most recently opened workspace
  if (task.isHeartbeat) {
    const recentWs = getMostRecentAccessibleWorkspace(task.ownerUserId)
    return recentWs?.id || null
  }

  return null
}

async function executeHeartbeat(task: CronTask): Promise<TaskResult> {
  const config = parseConfig(task.config)
  const workspaceId = resolveWorkspaceId(task)
  if (!workspaceId) {
    return { status: 'ok', result: 'No workspace configured', sessionId: '' }
  }

  // Load HEARTBEAT.md checklist
  const checklistPath = config.checklist_path || 'HEARTBEAT.md'
  const checklist = readWorkspaceFile(workspaceId, checklistPath, task.ownerUserId)
  if (!checklist || !checklist.trim()) {
    return { status: 'ok', result: 'HEARTBEAT_OK (no checklist configured)', sessionId: '' }
  }

  const systemPrompt = [
    'You are a heartbeat agent performing routine checks.',
    'Execute each item in the checklist below using the available tools.',
    'After checking all items, summarize your findings.',
    'If everything is normal, respond with exactly: HEARTBEAT_OK',
    'If you find issues or items that need attention, list them clearly.',
    'Be concise — your output will be automatically delivered via IM notification.',
    'Do NOT try to send messages yourself or ask for API credentials.',
  ].join('\n')

  const userMessage = `Run the following heartbeat checklist:\n\n${checklist}`
  const prefix = task.isHeartbeat ? 'Heartbeat' : task.webhookEnabled ? 'Webhook' : 'Scheduled'
  const sessionTitle = `[${prefix}] ${task.name}`

  const { text, sessionId } = await runAgentTask(systemPrompt, userMessage, workspaceId, sessionTitle, task.ownerUserId)

  const isOk = text.includes('HEARTBEAT_OK')
  const status = isOk ? 'ok' : 'alert'

  if (!isOk && config.notify_channel) {
    await notifyIm(config.notify_channel, `📢 Heartbeat Alert\n\n${text}`)
  }

  return { status, result: text.slice(0, 500), sessionId }
}

async function executeRunAgent(task: CronTask): Promise<TaskResult> {
  const config = parseConfig(task.config)
  const workspaceId = resolveWorkspaceId(task)
  if (!workspaceId) {
    return { status: 'error', result: 'No workspace configured for this task', sessionId: '' }
  }

  // Load the agent's AGENT.md (or CLAUDE.md) from workspace or global
  const agentName = task.agentName
  if (!agentName) {
    return { status: 'error', result: 'No agent specified', sessionId: '' }
  }

  // Try loading agent file from project, then global
  let agentPrompt = readWorkspaceFile(workspaceId, `agents/${agentName}/AGENT.md`, task.ownerUserId)
  if (!agentPrompt) {
    agentPrompt = readWorkspaceFile(workspaceId, `agents/${agentName}/CLAUDE.md`, task.ownerUserId)
  }

  const systemPrompt = agentPrompt
    ? `You are running as the "${agentName}" agent.\n\n${agentPrompt}`
    : `You are running as the "${agentName}" scheduled agent. Execute the task and report results concisely.`

  const userMessage = task.action || `Execute the ${agentName} agent task.`
  const prefix = task.isHeartbeat ? 'Heartbeat' : task.webhookEnabled ? 'Webhook' : 'Scheduled'
  const sessionTitle = `[${prefix}] ${task.name}`

  const { text, sessionId } = await runAgentTask(systemPrompt, userMessage, workspaceId, sessionTitle, task.ownerUserId)

  if (config.notify_channel) {
    await notifyIm(config.notify_channel, `⏰ ${task.name}\n\n${text.slice(0, 1000)}`)
  }

  return { status: 'ok', result: text.slice(0, 500), sessionId }
}

async function executeRunSkill(task: CronTask): Promise<TaskResult> {
  const config = parseConfig(task.config)
  const workspaceId = resolveWorkspaceId(task)
  if (!workspaceId) {
    return { status: 'error', result: 'No workspace configured for this task', sessionId: '' }
  }

  const skillName = task.skillName
  if (!skillName) {
    return { status: 'error', result: 'No skill specified', sessionId: '' }
  }

  // Load skill file
  const skillContent = readWorkspaceFile(workspaceId, `skills/${skillName}/SKILL.md`, task.ownerUserId)

  const systemPrompt = skillContent
    ? `You are executing the "${skillName}" skill.\n\n${skillContent}`
    : `You are executing the "${skillName}" scheduled skill. Execute the task and report results concisely.`

  const userMessage = task.action || `Execute the ${skillName} skill.`
  const prefix = task.isHeartbeat ? 'Heartbeat' : task.webhookEnabled ? 'Webhook' : 'Scheduled'
  const sessionTitle = `[${prefix}] ${task.name}`

  const { text, sessionId } = await runAgentTask(systemPrompt, userMessage, workspaceId, sessionTitle, task.ownerUserId)

  if (config.notify_channel) {
    await notifyIm(config.notify_channel, `⏰ ${task.name}\n\n${text.slice(0, 1000)}`)
  }

  return { status: 'ok', result: text.slice(0, 500), sessionId }
}

async function executeCustomPrompt(task: CronTask): Promise<TaskResult> {
  const config = parseConfig(task.config)
  const workspaceId = resolveWorkspaceId(task)
  if (!workspaceId) {
    return { status: 'error', result: 'No workspace configured for this task', sessionId: '' }
  }

  const notifyTarget = config.notify_channel ? ` Your output will be automatically delivered to the user via ${config.notify_channel} — do NOT try to send messages yourself, just produce the content.` : ''
  const systemPrompt = [
    'You are a scheduled task agent running on behalf of the user.',
    'Execute the following action and report the result concisely.',
    `IMPORTANT: Your text output IS the deliverable.${notifyTarget}`,
    'Do NOT ask for API credentials, webhooks, or try to call any IM APIs.',
    'If the task is to send a message, just output that message directly.',
  ].join('\n')

  const sessionTitle = `[Scheduled] ${task.name}`
  const { text, sessionId } = await runAgentTask(systemPrompt, task.action, workspaceId, sessionTitle, task.ownerUserId)

  if (config.notify_channel) {
    await notifyIm(config.notify_channel, `⏰ ${task.name}\n\n${text.slice(0, 1000)}`)
  }

  return { status: 'ok', result: text.slice(0, 500), sessionId }
}

/**
 * Run a task via the Claude Agent SDK.
 * Creates a persistent session (visible in chat list) with [Scheduled]/[Webhook]/[Heartbeat] prefix.
 */
async function runAgentTask(
  systemPrompt: string,
  userMessage: string,
  workspaceId: string,
  sessionTitle: string,
  userId?: string,
): Promise<{ text: string; sessionId: string }> {
  const sessionId = crypto.randomUUID()

  // Resolve default model from registry; fallback chain is supported.
  const model = await modelRegistry.getDefaultModel(userId ?? '', getSetting('default_model', userId ?? ''))

  // Create a persistent session in the database so it appears in the chat list
  sessionRepo.create({
    id: sessionId,
    title: sessionTitle,
    workspace: workspaceId,
    model,
    userId: userId ?? '',
  })

  // Store the user message
  const userMsgId = crypto.randomUUID()
  messageRepo.create({
    id: userMsgId,
    sessionId,
    role: 'user',
    content: JSON.stringify([{ type: 'text', text: userMessage }]),
  })

  const q = EngineRegistry.get().createQuery({
    prompt: userMessage,
    sessionId,
    model,
    workspaceId,
    userId,
    bypassPermissions: true,
    customSystemPrompt: systemPrompt,
  })

  const allTextBlocks: string[] = []

  for await (const msg of q) {
    if (msg.type === 'assistant') {
      for (const block of msg.message.content) {
        if (block.type === 'text') allTextBlocks.push(block.text)
      }
    }
  }

  const resultText = allTextBlocks.join('\n\n') || '(no output)'

  // Store the assistant response
  const assistantMsgId = crypto.randomUUID()
  messageRepo.create({
    id: assistantMsgId,
    sessionId,
    role: 'assistant',
    content: JSON.stringify([{ type: 'text', text: resultText }]),
  })

  // Notify desktop UI to refresh session list via SSE
  emitImEvent('im:session-changed', { sessionId, workspaceId })

  return { text: resultText, sessionId }
}

/**
 * Send a notification message through IM.
 * Resolves the target chat from channel_bindings:
 *   1. Prefer DM (p2p) bindings for that channel
 *   2. Fall back to any bound chat (group)
 */
async function notifyIm(channelId: string, message: string): Promise<void> {
  const manager = getBridgeManager()
  if (!manager.isConnected(channelId)) {
    console.log(`[Cron] IM channel ${channelId} not connected, skipping notification`)
    return
  }

  // Find target chat from bindings
  const bindings = channelBindingRepo.listChatIdsByChannel(channelId)

  if (bindings.length === 0) {
    console.log(`[Cron] No chat bindings for channel ${channelId}, skipping notification`)
    return
  }

  // Send to the first (most recent) bound chat
  const targetChatId = bindings[0].chatId

  try {
    await manager.deliverPlatformFinal(channelId, targetChatId, message)
    console.log(`[Cron] Notification sent to ${channelId}:${targetChatId}: ${message.slice(0, 80)}`)
  } catch (err) {
    console.error(`[Cron] Failed to send notification to ${channelId}:`, err instanceof Error ? err.message : err)
  }
}

function parseConfig(configStr: string): TaskConfig {
  try {
    return JSON.parse(configStr) as TaskConfig
  } catch {
    return {}
  }
}
