// Unit tests for the sessions.$sessionId route component — SessionRoute.
//
// Verifies the useEffect in SessionRoute fires the correct store actions for
// the task-session attach path:
//   - attachToSession(sessionId, 'chat', undefined, agentId) when the WS is connected
//   - setAttachedContext('task', title) when session.type === 'task'
//
// These tests replace the previous inline-copy loader-logic tests. The
// inline-copy approach was false-confidence: it tested a re-implementation
// of the loader body, not the real SessionRoute component, so it could not
// catch regressions in the useEffect that controls task-banner visibility.
//
// Design note: ChatScreen is mocked to a no-op div to avoid pulling in
// WebSocket store init, AssistantUI hooks, and the full component tree —
// those side effects caused test-environment hangs (original comment in the
// old test). TanStack Router primitives and react-query useQuery are mocked
// so SessionRoute can be rendered in isolation.
//
// These tests WILL FAIL on a version of SessionRoute that only calls
// setActiveSession (without calling attachToSession or setAttachedContext),
// proving they catch the regression they were written to prevent.

import React from 'react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, act, waitFor } from '@testing-library/react'

// ── Mock heavy dependencies before any SPA imports ────────────────────────────

// Mock ChatScreen to avoid WebSocket store init and full component tree load.
vi.mock('@/components/chat/ChatScreen', () => ({
  ChatScreen: () => null,
}))

// Mock framer-motion (imported transitively by some components)
vi.mock('framer-motion', () => {
  return {
    motion: new Proxy({}, {
      get: (_: object, prop: string) =>
        React.forwardRef(({ children, ...props }: Record<string, unknown>, ref: React.Ref<unknown>) =>
          React.createElement(prop as string, { ...props, ref }, children as React.ReactNode)),
    }),
    AnimatePresence: ({ children }: { children: React.ReactNode }) => <>{children}</>,
  }
})

// Mock @tanstack/react-query useQuery — returns the loader data as initial data
// so the component's useEffect gets real-looking session detail without a network
// request.
let _mockUseQueryData: unknown = null

vi.mock('@tanstack/react-query', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@tanstack/react-query')>()
  return {
    ...actual,
    useQuery: vi.fn().mockImplementation(() => ({
      data: _mockUseQueryData,
      isLoading: false,
      isError: false,
    })),
  }
})

// Mock TanStack Router primitives used by sessions.$sessionId.tsx
const mockSessionId = 'sess-task-001'

vi.mock('@tanstack/react-router', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@tanstack/react-router')>()
  return {
    ...actual,
    createFileRoute: (_path: string) => (opts: { component: React.ComponentType; loader?: unknown }) => ({
      ...opts,
      // The Route object returned by createFileRoute needs .useParams and
      // .useLoaderData so SessionRoute can call them. In the real app these
      // are generated by TanStack Router; here we stub them.
      useParams: () => ({ sessionId: mockSessionId }),
      useLoaderData: () => _mockUseQueryData,
    }),
    useParams: () => ({ sessionId: mockSessionId }),
  }
})

// ── Store imports (real Zustand stores) ───────────────────────────────────────
import { useSessionStore } from '@/store/session'
import { useConnectionStore } from '@/store/connection'
import type { SessionDetail } from '@/lib/api'

// ── Helpers ───────────────────────────────────────────────────────────────────

function makeTaskSession(overrides?: Partial<SessionDetail['session']>): SessionDetail {
  return {
    session: {
      id: mockSessionId,
      agent_id: 'jim',
      title: 'Fix bug #42',
      type: 'task',
      created_at: '2026-01-01T00:00:00Z',
      updated_at: '2026-01-01T00:00:00Z',
      message_count: 2,
      ...overrides,
    },
    messages: [],
    agent_removed: false,
  }
}

function makeChatSession(overrides?: Partial<SessionDetail['session']>): SessionDetail {
  return {
    session: {
      id: mockSessionId,
      agent_id: 'jim',
      title: 'General chat',
      type: 'chat',
      created_at: '2026-01-01T00:00:00Z',
      updated_at: '2026-01-01T00:00:00Z',
      message_count: 0,
      ...overrides,
    },
    messages: [],
    agent_removed: false,
  }
}

function makeMockConnection() {
  return {
    send: vi.fn().mockReturnValue(true),
    close: vi.fn(),
    isConnected: true,
  }
}

function resetStores() {
  useSessionStore.setState({
    activeSessionId: null,
    activeAgentId: null,
    activeAgentType: null,
    attachedSessionType: null,
    attachedTaskTitle: null,
  })
  useConnectionStore.setState({
    connection: null,
    isConnected: false,
    connectionError: null,
    reconnectPhase: null,
    reconnectAttempt: 0,
    liteMode: false,
  })
}

// ── Component under test (imported AFTER all mocks are set up) ───────────────
let SessionRoute: React.ComponentType | null = null

// ── Tests ─────────────────────────────────────────────────────────────────────

describe('SessionRoute component — task-session attach path (useEffect)', () => {
  beforeEach(async () => {
    vi.clearAllMocks()
    resetStores()

    if (!SessionRoute) {
      const mod = await import('./sessions.$sessionId')
      // Route.component is the React component created by createFileRoute
      SessionRoute = (mod.Route as unknown as { component: React.ComponentType }).component
    }
  })

  it(
    'calls attachToSession when WS is connected and navigating to a session',
    async () => {
      // BDD: Given a connected WS, When SessionRoute mounts for a task session,
      // Then attachToSession(sessionId, 'chat', undefined, agentId) fires.
      //
      // Traces to: sessions.$sessionId.tsx useEffect — attachToSession path.
      // This test FAILS on a version that only calls setActiveSession.
      const detail = makeTaskSession()
      _mockUseQueryData = detail

      const mockConn = makeMockConnection()
      useConnectionStore.setState({ connection: mockConn as never, isConnected: true })

      const attachToSession = vi.spyOn(useSessionStore.getState(), 'attachToSession')

      const Route = SessionRoute
      if (!Route) throw new Error('SessionRoute not loaded')
      await act(async () => {
        render(<Route />)
      })

      await waitFor(() => {
        expect(attachToSession).toHaveBeenCalledWith(
          mockSessionId,
          'chat',
          undefined,
          'jim',
        )
      })
    },
  )

  it(
    'calls setAttachedContext("task", title) when session.type is "task" and WS is connected',
    async () => {
      // BDD: Given a task-type session, When SessionRoute mounts with WS connected,
      // Then setAttachedContext('task', session.title) fires so ChatScreen shows the Task banner.
      //
      // Traces to: sessions.$sessionId.tsx useEffect lines 61-63.
      // This test FAILS on a version that does not call setAttachedContext.
      const detail = makeTaskSession({ title: 'Fix bug #42' })
      _mockUseQueryData = detail

      const mockConn = makeMockConnection()
      useConnectionStore.setState({ connection: mockConn as never, isConnected: true })

      const setAttachedContext = vi.spyOn(useSessionStore.getState(), 'setAttachedContext')

      const Route = SessionRoute
      if (!Route) throw new Error('SessionRoute not loaded')
      await act(async () => {
        render(<Route />)
      })

      await waitFor(() => {
        expect(setAttachedContext).toHaveBeenCalledWith('task', 'Fix bug #42')
      })
    },
  )

  it(
    'does NOT call setAttachedContext when session.type is "chat"',
    async () => {
      // BDD: Given a chat-type session, When SessionRoute mounts,
      // Then setAttachedContext is NOT called (no task banner should appear).
      //
      // Traces to: sessions.$sessionId.tsx useEffect — type === 'task' guard.
      const detail = makeChatSession()
      _mockUseQueryData = detail

      const mockConn = makeMockConnection()
      useConnectionStore.setState({ connection: mockConn as never, isConnected: true })

      const setAttachedContext = vi.spyOn(useSessionStore.getState(), 'setAttachedContext')

      const Route = SessionRoute
      if (!Route) throw new Error('SessionRoute not loaded')
      await act(async () => {
        render(<Route />)
      })

      // Give effects time to flush, then assert setAttachedContext was NOT called
      await waitFor(() => {
        // We expect attachToSession to have been called (proves effects ran)
        // but setAttachedContext must NOT have been called for a 'chat' session.
        expect(setAttachedContext).not.toHaveBeenCalled()
      }, { timeout: 2000 })
    },
  )

  it(
    'falls back to setActiveSession (not attachToSession) when WS is not connected',
    async () => {
      // BDD: Given NO WS connection, When SessionRoute mounts,
      // Then setActiveSession is called so the store stays consistent.
      // WsLifecycle sends attach_session once the WS connects (onConnected path).
      //
      // Traces to: sessions.$sessionId.tsx useEffect — !connection else branch (line 55-58).
      const detail = makeTaskSession()
      _mockUseQueryData = detail

      // No connection in store (default from resetStores)
      const setActiveSession = vi.spyOn(useSessionStore.getState(), 'setActiveSession')
      const attachToSession = vi.spyOn(useSessionStore.getState(), 'attachToSession')

      const Route = SessionRoute
      if (!Route) throw new Error('SessionRoute not loaded')
      await act(async () => {
        render(<Route />)
      })

      await waitFor(() => {
        expect(setActiveSession).toHaveBeenCalledWith(mockSessionId, 'jim', null)
        expect(attachToSession).not.toHaveBeenCalled()
      })
    },
  )
})
