import { homedir } from 'node:os'
import { join } from 'node:path'
import { appendFileSync, mkdirSync } from 'node:fs'
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import * as z from 'zod'
import {
  cmdAddComment,
  cmdGetComments,
  cmdGetMatter,
  cmdGetProjectDetail,
  cmdGetUserInfo,
  cmdListMatterStatuses,
  cmdListMatterTypes,
  cmdListMatters,
  cmdListProjectMembers,
  cmdListProjects,
  cmdUpdateMatter,
} from './geelib-api.mjs'
import {
  extractMatterTitle,
  markMatterInProgress,
  markMatterProcessed,
  rollbackDispatch,
  syncUnstartedTaskQueue,
  takeNextForDispatch,
  resolveSelfTestStatusId,
} from './poll.mjs'
import { GeelibStateStore } from './state.mjs'

const stateDir =
  process.env.GEELIB_STATE_DIR ||
  join(homedir(), '.claude', 'channels', 'geelib')
const bootLogFile = join(stateDir, 'boot.log')
const DEFAULT_POLL_MS = 60_000
const pollIntervalMs = Math.max(
  10_000,
  Number(process.env.GEELIB_POLL_INTERVAL_MS) || DEFAULT_POLL_MS,
)

mkdirSync(stateDir, { recursive: true })

function logBoot(line) {
  try {
    appendFileSync(bootLogFile, `${new Date().toISOString()} ${line}\n`, {
      encoding: 'utf8',
      mode: 0o600,
      flag: 'a',
    })
  } catch {}
}

logBoot(`[BOOT] geelib-channel index.mjs start pid=${process.pid}`)

process.on('uncaughtException', err => {
  logBoot(`[BOOT] uncaughtException ${err?.stack || err}`)
})
process.on('unhandledRejection', err => {
  logBoot(`[BOOT] unhandledRejection ${err?.stack || err}`)
})

const store = new GeelibStateStore(stateDir)
const server = new McpServer(
  {
    name: 'geelib-channel',
    version: '0.1.0',
  },
  {
    capabilities: {
      experimental: {
        'claude/channel': {},
      },
      tools: {},
    },
    instructions: [
      'GeeLib 新任务通过 notifications/claude/channel 入站。',
      '处理完成后使用 add_comment 写回说明；geelib_mark_processed 标记已处理。',
      '也可主动调用 list_matters / get_matter_detail 等工具查询。',
    ].join('\n'),
  },
)

let pollTimer = null
let pollInFlight = false

function jsonText(data) {
  return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
}

async function emitClaudeChannelNotification(envelope) {
  const matterId = String(envelope.matterId ?? '')
  const meta = Object.fromEntries(
    Object.entries({
      matter_id: matterId,
      message_id: matterId,
      sub_id: envelope.subId != null ? String(envelope.subId) : '',
      type_id: envelope.typeId != null ? String(envelope.typeId) : '',
      title: envelope.title ?? '',
    }).filter(([, v]) => v !== ''),
  )
  const params = {
    content: envelope.content,
    meta,
  }
  logBoot(
    `[NOTIFY] matter_id=${matterId} title=${String(envelope.title || '').slice(0, 80)}`,
  )
  await server.server.notification({
    method: 'notifications/claude/channel',
    params,
  })
}

/** 推送单条任务 channel 通知，并写入 inbox。 */
async function notifyTaskItem(item) {
  const matterId = Number(item.matterId)
  const subId = Number(item.subId)
  const typeId = Number(item.typeId)
  const title = item.title ?? extractMatterTitle(item.matter ?? {})
  const content = [
    `[GeeLib 新任务] #${matterId} ${title}`,
    `sub_id=${subId} type_id=${typeId}`,
  ].join('\n')

  store.appendInbox({
    matterId: String(matterId),
    subId,
    typeId,
    title,
    text: content,
    matter: item.matter,
  })

  await emitClaudeChannelNotification({
    matterId,
    subId,
    typeId,
    title,
    content,
  })
}

/** 空闲时从队列头 dispatch 下一条任务：先改「处理中」，再 NOTIFY 启动 agent。 */
async function dispatchOneIfIdle() {
  const item = takeNextForDispatch(store)
  if (!item) {
    return false
  }
  const matterId = Number(item.matterId)
  try {
    const progress = await markMatterInProgress(store, item)
    logBoot(
      `[DISPATCH] matter_id=${matterId} status=处理中 status_id=${progress.inProgressStatusId}`,
    )
    await notifyTaskItem(item)
    return true
  } catch (err) {
    rollbackDispatch(store, item)
    logBoot(
      `[DISPATCH] matter_id=${matterId} rollback err=${err?.message || err}`,
    )
    throw err
  }
}

/** 后台轮询：一次拉全量未开始任务入队，再串行 dispatch 一条。 */
async function pollAndNotify() {
  if (pollInFlight) {
    return
  }
  pollInFlight = true
  try {
    const sync = await syncUnstartedTaskQueue(store)
    logBoot(
      `[POLL] queue=${sync.queueLength} newly=${sync.newlyEnqueued} active=${sync.activeMatterId ?? 'none'}`,
    )
    await dispatchOneIfIdle()
  } catch (err) {
    logBoot(`[POLL] error ${err?.stack || err}`)
    console.error('[geelib-channel] poll error:', err?.message || err)
  } finally {
    pollInFlight = false
  }
}

/** 任务完成后立即尝试 dispatch 队列中下一条。 */
async function continueQueueAfterComplete(matterId) {
  markMatterProcessed(store, matterId)
  logBoot(`[POLL] completed matter_id=${matterId}, try dispatch next`)
  try {
    await dispatchOneIfIdle()
  } catch (err) {
    logBoot(`[POLL] dispatch-after-complete error ${err?.stack || err}`)
  }
}

function startPollLoop() {
  if (pollTimer) {
    return
  }
  logBoot(`[POLL] start intervalMs=${pollIntervalMs}`)
  void pollAndNotify()
  pollTimer = setInterval(() => {
    void pollAndNotify()
  }, pollIntervalMs)
  pollTimer.unref?.()
}

server.registerTool(
  'get_user_info',
  { description: '获取当前 GeeLib 登录用户信息', inputSchema: {} },
  async () => jsonText(await cmdGetUserInfo()),
)

server.registerTool(
  'list_projects',
  {
    description: '列出当前用户可访问的项目',
    inputSchema: { query: z.string().optional().default('') },
  },
  async ({ query }) => jsonText(await cmdListProjects(query)),
)

server.registerTool(
  'get_project_detail',
  {
    description: '获取项目详情',
    inputSchema: { sub_id: z.number() },
  },
  async ({ sub_id }) => jsonText(await cmdGetProjectDetail(sub_id)),
)

server.registerTool(
  'list_project_members',
  {
    description: '列出项目成员',
    inputSchema: { sub_id: z.number() },
  },
  async ({ sub_id }) => jsonText(await cmdListProjectMembers(sub_id)),
)

server.registerTool(
  'list_matter_types',
  {
    description: '列出项目下的工作项类型',
    inputSchema: { sub_id: z.number() },
  },
  async ({ sub_id }) => jsonText(await cmdListMatterTypes(sub_id)),
)

server.registerTool(
  'list_matter_statuses',
  {
    description: '列出工作项类型下的状态列表',
    inputSchema: { sub_id: z.number(), type_id: z.number() },
  },
  async ({ sub_id, type_id }) =>
    jsonText(await cmdListMatterStatuses(sub_id, type_id)),
)

server.registerTool(
  'list_matters',
  {
    description: '分页查询工作项列表',
    inputSchema: {
      sub_id: z.number(),
      type_id: z.number(),
      page: z.number().optional().default(1),
      page_size: z.number().optional().default(20),
      keyword: z.string().optional(),
      status_id: z.number().optional(),
      assignee_id: z.number().optional(),
    },
  },
  async ({
    sub_id,
    type_id,
    page,
    page_size,
    keyword,
    status_id,
    assignee_id,
  }) =>
    jsonText(
      await cmdListMatters({
        subId: sub_id,
        typeId: type_id,
        page,
        pageSize: page_size,
        keyword,
        statusId: status_id,
        assigneeId: assignee_id,
      }),
    ),
)

server.registerTool(
  'get_matter_detail',
  {
    description: '获取工作项详情',
    inputSchema: { sub_id: z.number(), matter_id: z.number() },
  },
  async ({ sub_id, matter_id }) =>
    jsonText(await cmdGetMatter(sub_id, matter_id)),
)

server.registerTool(
  'update_matter',
  {
    description: '更新工作项字段或状态',
    inputSchema: {
      sub_id: z.number(),
      matter_id: z.number(),
      title: z.string().optional(),
      description: z.string().optional(),
      status_id: z.number().optional(),
      assignee_id: z.number().optional(),
      priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
      progress: z.number().optional(),
      sprint_id: z.number().optional(),
    },
  },
  async args =>
    jsonText(
      await cmdUpdateMatter({
        subId: args.sub_id,
        matterId: args.matter_id,
        title: args.title,
        description: args.description,
        statusId: args.status_id,
        assigneeId: args.assignee_id,
        priority: args.priority,
        progress: args.progress,
        sprintId: args.sprint_id,
      }),
    ),
)

server.registerTool(
  'get_comments',
  {
    description: '获取工作项评论列表',
    inputSchema: { sub_id: z.number(), matter_id: z.number() },
  },
  async ({ sub_id, matter_id }) =>
    jsonText(await cmdGetComments(sub_id, matter_id)),
)

server.registerTool(
  'add_comment',
  {
    description: '向工作项添加评论',
    inputSchema: {
      sub_id: z.number(),
      matter_id: z.number(),
      content: z.string(),
    },
  },
  async ({ sub_id, matter_id, content }) => {
    const resp = await cmdAddComment(sub_id, matter_id, content)
    return {
      content: [{ type: 'text', text: JSON.stringify(resp, null, 2) }],
      structuredContent: {
        matter_id,
        comment_id: resp?.data?.id ?? resp?.data?.comment_id,
      },
    }
  },
)

server.registerTool(
  'geelib_check_new',
  {
    description: '检查本地 inbox 是否有未读 GeeLib 新任务',
    inputSchema: { after_cursor: z.number().optional().default(0) },
  },
  async ({ after_cursor }) => {
    const rows = store.listInbox(20, true, after_cursor)
    return {
      content: [
        {
          type: 'text',
          text: rows.length
            ? `There are ${rows.length} unread GeeLib tasks.`
            : 'No unread GeeLib tasks.',
        },
      ],
      structuredContent: { unread: rows.length, tasks: rows },
    }
  },
)

server.registerTool(
  'geelib_mark_processed',
  {
    description:
      'agent 处理完成后调用：将工作项状态改为「自测&联调中」并标记已处理，停止重复轮询推送',
    inputSchema: {
      sub_id: z.number(),
      matter_id: z.number(),
    },
  },
  async ({ sub_id, matter_id }) => {
    const pollerState = store.loadPollerState()
    const selfTestStatusId = resolveSelfTestStatusId(pollerState, sub_id)
    if (selfTestStatusId === null) {
      throw new Error(
        `未找到 sub_id=${sub_id} 的完成后目标状态，请确认 GEELIB_SUB_ID 已配置且 poll 已完成发现`,
      )
    }
    const statusResult = await cmdUpdateMatter({
      subId: sub_id,
      matterId: matter_id,
      statusId: selfTestStatusId,
    })
    await continueQueueAfterComplete(matter_id)
    return {
      content: [
        {
          type: 'text',
          text: `Matter ${matter_id} moved to 自测&联调中 and marked processed.`,
        },
      ],
      structuredContent: {
        matter_id,
        sub_id,
        self_test_status_id: selfTestStatusId,
        status_result: statusResult,
      },
    }
  },
)

async function main() {
  logBoot('[BOOT] main() starting')
  const transport = new StdioServerTransport()
  await server.connect(transport)
  startPollLoop()
  logBoot('[BOOT] server.connect completed')
  console.error('[geelib-channel] MCP server running')
}

main()
  .then(() => {
    logBoot('[BOOT] main resolved')
  })
  .catch(err => {
    logBoot(`[BOOT] fatal ${err?.stack || err}`)
    console.error('[geelib-channel] fatal', err)
    process.exit(1)
  })
