import { beforeEach, describe, expect, it, vi } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { useConversationStore } from '@/stores/conversation'

const mocks = vi.hoisted(() => ({
  getHierarchyStats: vi.fn(),
  getRuntimeSummary: vi.fn(),
  getVisibilityTopology: vi.fn(),
  getVisibilityVerifier: vi.fn(),
  getVisibilityCost: vi.fn(),
  getVisibilityTools: vi.fn(),
  getConfig: vi.fn(),
  updateConfig: vi.fn(),
  listSkills: vi.fn(),
}))

vi.mock('@/api/agent', () => ({
  agentApi: {
    getHierarchyStats: mocks.getHierarchyStats,
    getRuntimeSummary: mocks.getRuntimeSummary,
    getVisibilityTopology: mocks.getVisibilityTopology,
    getVisibilityVerifier: mocks.getVisibilityVerifier,
    getVisibilityCost: mocks.getVisibilityCost,
    getVisibilityTools: mocks.getVisibilityTools,
    getConfig: mocks.getConfig,
    updateConfig: mocks.updateConfig,
  },
}))

vi.mock('@/api/skill', () => ({
  skillApi: {
    list: mocks.listSkills,
  },
}))

import AgentsPage from './index.vue'

const hierarchyStats = {
  main_agents: [
    {
      agent_id: 'schedule_planner',
      call_count: 12,
      current_task: null,
      status: 'idle',
      sub_commanders: [
        { agent_id: 'schedule_analysis', call_count: 4, current_task: null, status: 'idle' },
        { agent_id: 'schedule_planning', call_count: 9, current_task: '生成今日排期建议', status: 'active' },
      ],
    },
    {
      agent_id: 'executor',
      call_count: 8,
      current_task: null,
      status: 'idle',
      sub_commanders: [
        { agent_id: 'executor_tasks', call_count: 8, current_task: null, status: 'idle' },
        { agent_id: 'executor_forum', call_count: 4, current_task: null, status: 'idle' },
      ],
    },
    {
      agent_id: 'librarian',
      call_count: 5,
      current_task: null,
      status: 'idle',
      sub_commanders: [
        { agent_id: 'librarian_retrieval', call_count: 5, current_task: null, status: 'idle' },
        { agent_id: 'librarian_graph', call_count: 2, current_task: null, status: 'idle' },
      ],
    },
    {
      agent_id: 'analyst',
      call_count: 3,
      current_task: null,
      status: 'idle',
      sub_commanders: [
        { agent_id: 'analyst_progress', call_count: 2, current_task: null, status: 'idle' },
        { agent_id: 'analyst_insights', call_count: 3, current_task: null, status: 'idle' },
      ],
    },
  ],
}

const runtimeSummaryFixture = {
  conversation_id: 'conv-1',
  execution_mode: 'collaboration',
  current_phase: 'phase_4_visibility_and_verification',
  current_checkpoint: 'visibility.runtime_summary_ready',
  phase_history: [],
  checkpoint_history: [],
  verifier: {
    conversation_id: 'conv-1',
    status: 'passed',
    summary: 'Runtime summary is available.',
    evidence: [],
  },
  isolation: {
    mode: 'worktree',
    isolation_id: 'iso-1',
    workspace_path: '/tmp/jarvis/worktree-1',
    parent_conversation_id: 'parent-1',
    metadata: { branch: 'jarvis/test-worker' },
  },
  cost: {
    input_tokens: 120,
    output_tokens: 80,
    total_tokens: 200,
    estimated_cost: 0.00156,
    budget_warning: true,
    currency: 'USD',
  },
  topology_node_count: 2,
  active_task_count: 2,
  completed_task_count: 1,
  recent_events: [
    {
      event_id: 'evt-1',
      event_type: 'agent.cost.warning',
      timestamp: '2026-04-04T10:00:00Z',
      severity: 'warning',
      payload: {},
    },
  ],
}

const topologyFixture = {
  conversation_id: 'conv-1',
  root_agent_id: 'master',
  current_agent: 'analyst-1234abcd',
  nodes: [
    {
      agent_id: 'master',
      role: 'master',
      parent_agent_id: null,
      source: 'root',
      task_count: 0,
      completed_task_count: 0,
    },
    {
      agent_id: 'analyst-1234abcd',
      role: 'analyst',
      parent_agent_id: 'master',
      source: 'spawned',
      task_count: 2,
      completed_task_count: 1,
    },
  ],
  edges: [{ parent_agent_id: 'master', child_agent_id: 'analyst-1234abcd' }],
  tasks: [],
  task_hierarchy: {},
}

const verifierFixture = {
  conversation_id: 'conv-1',
  status: 'passed',
  summary: 'Runtime summary is available.',
  evidence: [
    { task_id: 'task-1', status: 'passed' },
  ],
}

const costFixture = {
  conversation_id: 'conv-1',
  total: runtimeSummaryFixture.cost,
  thresholds: {
    total_tokens: 300,
    estimated_cost: 0.01,
  },
  by_agent: [
    {
      agent_id: 'analyst-1234abcd',
      input_tokens: 80,
      output_tokens: 70,
      total_tokens: 150,
      estimated_cost: 0.00129,
      budget_warning: true,
    },
  ],
}

const toolGovernanceFixture = {
  conversation_id: 'conv-1',
  total_tools: 12,
  used_tools: 2,
  items: [
    {
      capability_id: 'web_search',
      tool_name: 'web_search',
      permission_class: 'external',
      side_effect_scope: 'network',
      supports_retry: true,
      idempotent: true,
      safe_for_parallel_use: true,
      requires_confirmation: false,
      usage_count: 1,
      last_result_preview: 'ok',
    },
    {
      capability_id: 'create_reminder',
      tool_name: 'create_reminder',
      permission_class: 'write',
      side_effect_scope: 'local_state',
      supports_retry: false,
      idempotent: false,
      safe_for_parallel_use: false,
      requires_confirmation: true,
      usage_count: 1,
      last_result_preview: 'created',
    },
  ],
  upgrade_candidates: ['worktree_manager', 'cost_inspector'],
}

const skillFixtures = [
  {
    id: 'skill-schedule-1',
    name: 'Priority Router',
    description: 'Aligns planner priorities.',
    instructions: 'Prioritize schedule risks.',
    agent_type: 'schedule_planner',
    tools: ['calendar', 'tasks'],
    required_context: [],
    output_format: null,
    visibility: 'private' as const,
    team_id: null,
    is_active: true,
    owner_id: 'user-1',
    created_at: '2026-03-26T00:00:00Z',
    updated_at: '2026-03-26T00:00:00Z',
  },
  {
    id: 'skill-schedule-2',
    name: 'Planning Synthesizer',
    description: 'Builds the next schedule plan.',
    instructions: 'Synthesize a practical plan.',
    agent_type: 'schedule_planner',
    tools: ['planning'],
    required_context: [],
    output_format: null,
    visibility: 'private' as const,
    team_id: null,
    is_active: true,
    owner_id: 'user-1',
    created_at: '2026-03-26T00:00:00Z',
    updated_at: '2026-03-26T00:00:00Z',
  },
  {
    id: 'skill-executor-1',
    name: 'Task Runner',
    description: 'Executes tool actions.',
    instructions: 'Run execution tasks.',
    agent_type: 'executor',
    tools: ['shell'],
    required_context: [],
    output_format: null,
    visibility: 'private' as const,
    team_id: null,
    is_active: true,
    owner_id: 'user-1',
    created_at: '2026-03-26T00:00:00Z',
    updated_at: '2026-03-26T00:00:00Z',
  },
]

describe('agents page pcb command center', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    vi.useFakeTimers()
    setActivePinia(createPinia())
    useConversationStore().setCurrentConversation('conv-1')
    vi.stubGlobal('ResizeObserver', class {
      observe() {}
      disconnect() {}
      unobserve() {}
    })
    Object.defineProperty(window, 'matchMedia', {
      writable: true,
      value: vi.fn().mockImplementation(() => ({
        matches: true,
        media: '(prefers-reduced-motion: no-preference)',
        onchange: null,
        addListener: vi.fn(),
        removeListener: vi.fn(),
        addEventListener: vi.fn(),
        removeEventListener: vi.fn(),
        dispatchEvent: vi.fn(),
      })),
    })
    mocks.getHierarchyStats.mockResolvedValue(hierarchyStats)
    mocks.getRuntimeSummary.mockResolvedValue(runtimeSummaryFixture)
    mocks.getVisibilityTopology.mockResolvedValue(topologyFixture)
    mocks.getVisibilityVerifier.mockResolvedValue(verifierFixture)
    mocks.getVisibilityCost.mockResolvedValue(costFixture)
    mocks.getVisibilityTools.mockResolvedValue(toolGovernanceFixture)
    mocks.getConfig.mockImplementation(async (id: string) => ({
      id,
      name: id === 'schedule_planner' ? 'SCHEDULE PLANNER' : id.toUpperCase(),
      role: id,
      description: 'config description',
      system_prompt: 'config prompt',
      enabled: true,
      selected_skill_ids: id === 'schedule_planner' ? ['skill-schedule-1', 'skill-schedule-2'] : [],
    }))
    mocks.updateConfig.mockResolvedValue({ selected_skill_ids: ['skill-schedule-1'] })
    mocks.listSkills.mockResolvedValue({ data: skillFixtures })
  })

  it('shows active route telemetry only when hierarchy stats report an active sub commander path', async () => {
    const wrapper = mount(AgentsPage)
    await Promise.resolve()
    await Promise.resolve()

    expect(mocks.getRuntimeSummary).toHaveBeenCalledWith('conv-1')
    expect(mocks.getVisibilityTopology).toHaveBeenCalledWith('conv-1')
    expect(wrapper.find('[data-testid="commander-skills"]').exists()).toBe(false)

    const plannerBus = wrapper.get('[data-testid="bus-link-schedule_planner"]')
    expect(plannerBus.classes()).toContain('energized')

    const plannerStepsBranch = wrapper.get('[data-testid="sub-link-schedule_planning"]')
    expect(plannerStepsBranch.classes()).toContain('energized')

    const routeTelemetry = wrapper.get('[data-testid="route-telemetry"]')
    expect(routeTelemetry.text()).toContain('SCHEDULE PLANNER')
    expect(routeTelemetry.text()).toContain('PLANNING')
  })

  it('keeps route telemetry in standby when hierarchy stats contain no active path', async () => {
    mocks.getHierarchyStats.mockResolvedValue({
      main_agents: hierarchyStats.main_agents.map((main) => ({
        ...main,
        status: 'idle',
        sub_commanders: main.sub_commanders.map((child) => ({ ...child, status: 'idle' })),
      })),
    })

    const wrapper = mount(AgentsPage)
    await Promise.resolve()
    await Promise.resolve()

    expect(wrapper.get('[data-testid="route-telemetry"]').text()).toContain('STANDBY')
    expect(wrapper.get('[data-testid="bus-link-schedule_planner"]').classes()).not.toContain('energized')
    expect(wrapper.get('[data-testid="sub-link-schedule_planning"]').classes()).not.toContain('energized')
  })

  it('renders child agents beneath the four main roles in the same hierarchy canvas', async () => {
    const wrapper = mount(AgentsPage)
    await Promise.resolve()
    await Promise.resolve()

    expect(wrapper.get('[data-testid="agent-chip-schedule_analysis"]').text()).toContain('ANALYSIS')
    expect(wrapper.get('[data-testid="agent-chip-schedule_planning"]').text()).toContain('PLANNING')
    expect(wrapper.get('[data-testid="agent-chip-executor_tasks"]').text()).toContain('TASK OPS')
    expect(wrapper.get('[data-testid="agent-chip-analyst_insights"]').text()).toContain('INSIGHTS')
  })

  it('keeps the configuration drawer available when selecting a main agent chip', async () => {
    const wrapper = mount(AgentsPage)
    await Promise.resolve()
    await Promise.resolve()

    await wrapper.get('[data-testid="agent-chip-schedule_planner"]').trigger('click')
    await Promise.resolve()
    await Promise.resolve()

    expect(wrapper.text()).toContain('AGENT CONFIGURATION')
    expect(wrapper.find('input').exists()).toBe(true)
  })

  it('cycles the energized demo path when hierarchy stats are offline', async () => {
    mocks.getHierarchyStats.mockRejectedValue(new Error('offline'))
    const wrapper = mount(AgentsPage)
    await Promise.resolve()
    await Promise.resolve()

    expect(wrapper.get('[data-testid="bus-link-schedule_planner"]').classes()).toContain('energized')
    expect(wrapper.get('[data-testid="sub-link-schedule_analysis"]').classes()).toContain('energized')

    await vi.advanceTimersByTimeAsync(1700)

    expect(wrapper.get('[data-testid="sub-link-schedule_analysis"]').classes()).not.toContain('energized')
    expect(wrapper.get('[data-testid="sub-link-schedule_planning"]').classes()).toContain('energized')
  })

  it('loads related skills into the configuration drawer for the selected node', async () => {
    const wrapper = mount(AgentsPage)
    await Promise.resolve()
    await Promise.resolve()

    await wrapper.get('[data-testid="agent-chip-schedule_planner"]').trigger('click')
    await Promise.resolve()
    await Promise.resolve()

    expect(mocks.listSkills).toHaveBeenCalled()
    expect(wrapper.get('[data-testid="linked-skills-section"]').text()).toContain('Priority Router')
    expect(wrapper.get('[data-testid="linked-skills-section"]').text()).toContain('Planning Synthesizer')
    expect(wrapper.get('[data-testid="linked-skills-package-skill_planner"]').text()).toContain('日程规划')
  })

  it('keeps linked skill selections after save and restores draft changes on reset', async () => {
    let persistedSelections = ['skill-schedule-1', 'skill-schedule-2']
    mocks.getConfig.mockImplementation(async (id: string) => ({
      id,
      name: id === 'schedule_planner' ? 'SCHEDULE PLANNER' : id.toUpperCase(),
      role: id,
      description: 'config description',
      system_prompt: 'config prompt',
      enabled: true,
      selected_skill_ids: id === 'schedule_planner' ? [...persistedSelections] : [],
    }))
    mocks.updateConfig.mockImplementation(async (_id: string, payload: { selected_skill_ids?: string[] }) => {
      persistedSelections = payload.selected_skill_ids ? [...payload.selected_skill_ids] : persistedSelections
      return { selected_skill_ids: [...persistedSelections] }
    })

    const wrapper = mount(AgentsPage)
    await Promise.resolve()
    await Promise.resolve()

    await wrapper.get('[data-testid="agent-chip-schedule_planner"]').trigger('click')
    await Promise.resolve()
    await Promise.resolve()

    const firstCheckbox = wrapper.get('[data-testid="linked-skill-checkbox-skill-schedule-1"]')
    const secondCheckbox = wrapper.get('[data-testid="linked-skill-checkbox-skill-schedule-2"]')

    expect((firstCheckbox.element as HTMLInputElement).checked).toBe(true)
    expect((secondCheckbox.element as HTMLInputElement).checked).toBe(true)

    await firstCheckbox.setValue(false)
    expect((firstCheckbox.element as HTMLInputElement).checked).toBe(false)

    await wrapper.get('[data-testid="linked-skills-reset"]').trigger('click')
    expect((wrapper.get('[data-testid="linked-skill-checkbox-skill-schedule-1"]').element as HTMLInputElement).checked).toBe(true)

    await wrapper.get('[data-testid="linked-skill-checkbox-skill-schedule-2"]').setValue(false)
    await wrapper.get('[data-testid="linked-skills-save"]').trigger('click')
    await Promise.resolve()

    await wrapper.get('[data-testid="agent-chip-schedule_planner"]').trigger('click')
    await Promise.resolve()
    await Promise.resolve()

    expect((wrapper.get('[data-testid="linked-skill-checkbox-skill-schedule-1"]').element as HTMLInputElement).checked).toBe(true)
    expect((wrapper.get('[data-testid="linked-skill-checkbox-skill-schedule-2"]').element as HTMLInputElement).checked).toBe(false)
  })

  it('sends selected skill ids when saving agent config', async () => {
    const wrapper = mount(AgentsPage)
    await Promise.resolve()
    await Promise.resolve()

    await wrapper.get('[data-testid="agent-chip-schedule_planner"]').trigger('click')
    await Promise.resolve()
    await Promise.resolve()

    await wrapper.get('[data-testid="linked-skill-checkbox-skill-schedule-2"]').setValue(false)
    await wrapper.get('[data-testid="linked-skills-save"]').trigger('click')
    await Promise.resolve()

    expect(mocks.updateConfig).toHaveBeenCalledWith(
      'schedule_planner',
      expect.objectContaining({ selected_skill_ids: ['skill-schedule-1'] }),
    )
  })

  it('restores persisted selected skill ids after remount', async () => {
    let persistedSelections = ['skill-schedule-1', 'skill-schedule-2']
    mocks.getConfig.mockImplementation(async (id: string) => ({
      id,
      name: id === 'schedule_planner' ? 'SCHEDULE PLANNER' : id.toUpperCase(),
      role: id,
      description: 'config description',
      system_prompt: 'config prompt',
      enabled: true,
      selected_skill_ids: id === 'schedule_planner' ? [...persistedSelections] : [],
    }))
    mocks.updateConfig.mockImplementation(async (_id: string, payload: { selected_skill_ids?: string[] }) => {
      persistedSelections = payload.selected_skill_ids ? [...payload.selected_skill_ids] : persistedSelections
      return { selected_skill_ids: [...persistedSelections] }
    })

    const firstWrapper = mount(AgentsPage)
    await Promise.resolve()
    await Promise.resolve()

    await firstWrapper.get('[data-testid="agent-chip-schedule_planner"]').trigger('click')
    await Promise.resolve()
    await Promise.resolve()

    await firstWrapper.get('[data-testid="linked-skill-checkbox-skill-schedule-2"]').setValue(false)
    await firstWrapper.get('[data-testid="linked-skills-save"]').trigger('click')
    await Promise.resolve()
    firstWrapper.unmount()

    const secondWrapper = mount(AgentsPage)
    await Promise.resolve()
    await Promise.resolve()

    await secondWrapper.get('[data-testid="agent-chip-schedule_planner"]').trigger('click')
    await Promise.resolve()
    await Promise.resolve()

    expect((secondWrapper.get('[data-testid="linked-skill-checkbox-skill-schedule-1"]').element as HTMLInputElement).checked).toBe(true)
    expect((secondWrapper.get('[data-testid="linked-skill-checkbox-skill-schedule-2"]').element as HTMLInputElement).checked).toBe(false)
  })

  it('shows an empty linked skills state when no matching skills are available', async () => {
    mocks.listSkills.mockResolvedValue({ data: [] })

    const wrapper = mount(AgentsPage)
    await Promise.resolve()
    await Promise.resolve()

    await wrapper.get('[data-testid="agent-chip-schedule_planner"]').trigger('click')
    await Promise.resolve()
    await Promise.resolve()

    expect(wrapper.get('[data-testid="linked-skills-empty"]').text()).toContain('暂无可关联技能')
  })

  it('renders runtime summary from the active conversation', async () => {
    const wrapper = mount(AgentsPage)
    await flushPromises()
    await flushPromises()

    expect(mocks.getRuntimeSummary).toHaveBeenCalledWith('conv-1')
    const runtimeSummary = wrapper.get('[data-testid="runtime-summary"]')
    expect(runtimeSummary.text()).toContain('collaboration')
    expect(runtimeSummary.text()).toContain('phase_4_visibility_and_verification')
    expect(runtimeSummary.text()).toContain('visibility.runtime_summary_ready')
    expect(runtimeSummary.text()).toContain('passed')
    expect(runtimeSummary.text()).toContain('worktree')
    expect(runtimeSummary.text()).toContain('200')
    expect(runtimeSummary.text()).toContain('Cost $0.001560')
    expect(runtimeSummary.text()).toContain('Tasks 1/2')
    expect(runtimeSummary.text()).toContain('Nodes 2')
    expect(runtimeSummary.text()).toContain('Budget warning')
    expect(wrapper.text()).toContain('/tmp/jarvis/worktree-1')
  })

  it('renders operator drilldown panels for events topology verifier and tool governance', async () => {
    const wrapper = mount(AgentsPage)
    await flushPromises()
    await flushPromises()

    expect(wrapper.get('[data-testid="runtime-events-panel"]').text()).toContain('agent.cost.warning')
    expect(wrapper.get('[data-testid="runtime-drilldown-panel"]').text()).toContain('analyst-1234abcd')
    expect(wrapper.get('[data-testid="runtime-drilldown-panel"]').text()).toContain('task-1')
    expect(wrapper.get('[data-testid="runtime-governance-panel"]').text()).toContain('web_search')
    expect(wrapper.get('[data-testid="runtime-governance-panel"]').text()).toContain('worktree_manager')
    expect(wrapper.get('[data-testid="runtime-governance-panel"]').text()).toContain('Thresholds: 300 tk / $0.010000')
  })

  it('shows a prompt when no conversation is selected', async () => {
    useConversationStore().setCurrentConversation(null)
    const wrapper = mount(AgentsPage)
    await flushPromises()
    await flushPromises()

    expect(mocks.getRuntimeSummary).not.toHaveBeenCalled()
    expect(mocks.getVisibilityTopology).not.toHaveBeenCalled()
    expect(wrapper.get('[data-testid="runtime-summary"]').text()).toContain('请选择一条会话以查看运行时摘要')
  })
})

