import { describe, it, expect, vi, beforeEach } from 'vitest'

const { mockGetSession } = vi.hoisted(() => ({ mockGetSession: vi.fn() }))

vi.mock('@/lib/auth', () => ({ getSession: mockGetSession }))
vi.mock('@/lib/product-access', () => ({
  getAccessibleProduct: vi.fn(),
}))

import { getAccessibleProduct } from '@/lib/product-access'
import type { NextRequest } from 'next/server'
import { GET } from '@/app/api/realtime/backlog/route'
import { useBacklogStore } from '@/stores/backlog-store'

const mockGetAccessibleProduct = getAccessibleProduct as ReturnType<typeof vi.fn>

function makeReq(productId?: string): NextRequest {
  const url = productId
    ? `http://localhost/api/realtime/backlog?product_id=${productId}`
    : 'http://localhost/api/realtime/backlog'
  return {
    signal: new AbortController().signal,
    nextUrl: new URL(url),
  } as unknown as NextRequest
}

beforeEach(() => {
  vi.clearAllMocks()
})

describe('GET /api/realtime/backlog', () => {
  it('401 when not authenticated', async () => {
    mockGetSession.mockResolvedValue({ userId: undefined, isDemo: false })
    const res = await GET(makeReq('prod-1'))
    expect(res.status).toBe(401)
    expect(mockGetAccessibleProduct).not.toHaveBeenCalled()
  })

  it('400 when product_id is missing', async () => {
    mockGetSession.mockResolvedValue({ userId: 'user-1', isDemo: false })
    const res = await GET(makeReq())
    expect(res.status).toBe(400)
  })

  it('403 when user has no access to the product', async () => {
    mockGetSession.mockResolvedValue({ userId: 'user-1', isDemo: false })
    mockGetAccessibleProduct.mockResolvedValue(null)
    const res = await GET(makeReq('prod-1'))
    expect(res.status).toBe(403)
    expect(mockGetAccessibleProduct).toHaveBeenCalledWith('prod-1', 'user-1')
  })

  it('500 when DIRECT_URL and DATABASE_URL are absent', async () => {
    mockGetSession.mockResolvedValue({ userId: 'user-1', isDemo: false })
    mockGetAccessibleProduct.mockResolvedValue({ id: 'prod-1' })

    const before = { DIRECT_URL: process.env.DIRECT_URL, DATABASE_URL: process.env.DATABASE_URL }
    delete process.env.DIRECT_URL
    delete process.env.DATABASE_URL
    try {
      const res = await GET(makeReq('prod-1'))
      expect(res.status).toBe(500)
    } finally {
      if (before.DIRECT_URL !== undefined) process.env.DIRECT_URL = before.DIRECT_URL
      if (before.DATABASE_URL !== undefined) process.env.DATABASE_URL = before.DATABASE_URL
    }
  })

  it('demo user is allowed (no 403) when product is accessible', async () => {
    mockGetSession.mockResolvedValue({ userId: 'demo-user', isDemo: true })
    mockGetAccessibleProduct.mockResolvedValue({ id: 'prod-1' })

    const before = { DIRECT_URL: process.env.DIRECT_URL, DATABASE_URL: process.env.DATABASE_URL }
    delete process.env.DIRECT_URL
    delete process.env.DATABASE_URL
    try {
      const res = await GET(makeReq('prod-1'))
      // Fails at 500 (no DB URL) — not 403, confirming demo user is not blocked
      expect(res.status).toBe(500)
    } finally {
      if (before.DIRECT_URL !== undefined) process.env.DIRECT_URL = before.DIRECT_URL
      if (before.DATABASE_URL !== undefined) process.env.DATABASE_URL = before.DATABASE_URL
    }
  })
})

// shouldEmit scope filter — white-box unit tests
describe('shouldEmit scope filter (via backlog-store reducer)', () => {
  it('applyChange: pbi INSERT adds to pbis array', () => {
    useBacklogStore.setState({ pbis: [], storiesByPbi: {}, tasksByStory: {} })
    const pbi = { id: 'pbi-1', code: 'PBI-1', title: 'Test', priority: 2, created_at: new Date(), status: 'ready' as const }
    useBacklogStore.getState().applyChange('pbi', 'I', pbi)
    expect(useBacklogStore.getState().pbis).toHaveLength(1)
    expect(useBacklogStore.getState().pbis[0].id).toBe('pbi-1')
  })

  it('applyChange: pbi UPDATE patches existing pbi', () => {
    const pbi = { id: 'pbi-1', code: 'PBI-1', title: 'Old', priority: 2, created_at: new Date(), status: 'ready' as const }
    useBacklogStore.setState({ pbis: [pbi], storiesByPbi: {}, tasksByStory: {} })
    useBacklogStore.getState().applyChange('pbi', 'U', { id: 'pbi-1', title: 'New' })
    expect(useBacklogStore.getState().pbis[0].title).toBe('New')
  })

  it('applyChange: pbi DELETE removes pbi', () => {
    const pbi = { id: 'pbi-1', code: 'PBI-1', title: 'Test', priority: 2, created_at: new Date(), status: 'ready' as const }
    useBacklogStore.setState({ pbis: [pbi], storiesByPbi: {}, tasksByStory: {} })
    useBacklogStore.getState().applyChange('pbi', 'D', { id: 'pbi-1' })
    expect(useBacklogStore.getState().pbis).toHaveLength(0)
  })

  it('applyChange: story INSERT adds to storiesByPbi', () => {
    useBacklogStore.setState({ pbis: [], storiesByPbi: { 'pbi-1': [] }, tasksByStory: {} })
    const story = { id: 'story-1', code: 'ST-1', title: 'S', description: null, acceptance_criteria: null, priority: 2, status: 'OPEN', pbi_id: 'pbi-1', created_at: new Date() }
    useBacklogStore.getState().applyChange('story', 'I', story)
    expect(useBacklogStore.getState().storiesByPbi['pbi-1']).toHaveLength(1)
  })

  it('applyChange: story DELETE removes from correct pbi bucket', () => {
    const story = { id: 'story-1', code: 'ST-1', title: 'S', description: null, acceptance_criteria: null, priority: 2, status: 'OPEN', pbi_id: 'pbi-1', created_at: new Date() }
    useBacklogStore.setState({ pbis: [], storiesByPbi: { 'pbi-1': [story] }, tasksByStory: {} })
    useBacklogStore.getState().applyChange('story', 'D', { id: 'story-1' })
    expect(useBacklogStore.getState().storiesByPbi['pbi-1']).toHaveLength(0)
  })

  it('applyChange: task UPDATE patches task across story buckets', () => {
    const task = { id: 'task-1', title: 'Old', description: null, priority: 2, status: 'TO_DO', sort_order: 1, story_id: 'story-1', created_at: new Date() }
    useBacklogStore.setState({ pbis: [], storiesByPbi: {}, tasksByStory: { 'story-1': [task] } })
    useBacklogStore.getState().applyChange('task', 'U', { id: 'task-1', status: 'IN_PROGRESS' })
    expect(useBacklogStore.getState().tasksByStory['story-1'][0].status).toBe('IN_PROGRESS')
  })
})
