/**
 * Tests for ResourceContext - resource-context.tsx
 *
 * TDD RED phase: Write failing tests first.
 *
 * This includes tests for the stale registration bug identified in code review:
 * Resource component uses useEffect with only [name] in dependency array,
 * which can cause stale definitions when other props change.
 */

import { describe, it, expect, vi } from 'vitest'
import { render, screen, act } from '@testing-library/react'
import { useState, type FC } from 'react'
import {
  ResourceProvider,
  useResource,
  ResourcesProvider,
  useResources,
  type ResourcesContextValue,
} from '../../context/resource-context'
import type { ResourceDefinition, Identifier, ResourceContextValue } from '../../types'

// Helper component to consume single resource context
function ResourceConsumer({ onResource }: { onResource: (ctx: ResourceContextValue) => void }) {
  const ctx = useResource()
  onResource(ctx)
  return (
    <div>
      <span data-testid="resource">{ctx.resource}</span>
      <span data-testid="id">{ctx.id?.toString() ?? 'none'}</span>
      <span data-testid="has-list">{ctx.hasList.toString()}</span>
      <span data-testid="has-edit">{ctx.hasEdit.toString()}</span>
    </div>
  )
}

// Helper component to consume resources collection context
function ResourcesConsumer({ onResources }: { onResources: (ctx: ResourcesContextValue) => void }) {
  const ctx = useResources()
  onResources(ctx)
  return (
    <div>
      <span data-testid="count">{ctx.resources.length}</span>
      <span data-testid="names">{ctx.resources.map((r) => r.name).join(',')}</span>
    </div>
  )
}

const MockListComponent: FC = () => <div>List</div>
const MockEditComponent: FC<{ id: Identifier }> = ({ id }) => <div>Edit {id}</div>

describe('ResourceContext', () => {
  describe('ResourceProvider', () => {
    it('should provide resource name to children', () => {
      render(
        <ResourceProvider resource="users">
          <ResourceConsumer onResource={() => {}} />
        </ResourceProvider>
      )

      expect(screen.getByTestId('resource').textContent).toBe('users')
    })

    it('should provide id when viewing/editing a record', () => {
      render(
        <ResourceProvider resource="users" id="123">
          <ResourceConsumer onResource={() => {}} />
        </ResourceProvider>
      )

      expect(screen.getByTestId('id').textContent).toBe('123')
    })

    it('should indicate available views based on definition', () => {
      const definition: ResourceDefinition = {
        name: 'users',
        list: MockListComponent,
        edit: MockEditComponent,
      }

      render(
        <ResourceProvider resource="users" definition={definition}>
          <ResourceConsumer onResource={() => {}} />
        </ResourceProvider>
      )

      expect(screen.getByTestId('has-list').textContent).toBe('true')
      expect(screen.getByTestId('has-edit').textContent).toBe('true')
    })

    it('should indicate missing views when not in definition', () => {
      const definition: ResourceDefinition = {
        name: 'users',
        // No list, edit, show, or create
      }

      render(
        <ResourceProvider resource="users" definition={definition}>
          <ResourceConsumer onResource={() => {}} />
        </ResourceProvider>
      )

      expect(screen.getByTestId('has-list').textContent).toBe('false')
      expect(screen.getByTestId('has-edit').textContent).toBe('false')
    })

    it('should handle numeric IDs', () => {
      render(
        <ResourceProvider resource="products" id={42}>
          <ResourceConsumer onResource={() => {}} />
        </ResourceProvider>
      )

      expect(screen.getByTestId('id').textContent).toBe('42')
    })

    it('should memoize context value when props are stable', () => {
      let renderCount = 0
      const contextValues: ResourceContextValue[] = []

      function TrackedConsumer() {
        const ctx = useResource()
        renderCount++
        contextValues.push(ctx)
        return <div>render {renderCount}</div>
      }

      const { rerender } = render(
        <ResourceProvider resource="users" id="1">
          <TrackedConsumer />
        </ResourceProvider>
      )

      rerender(
        <ResourceProvider resource="users" id="1">
          <TrackedConsumer />
        </ResourceProvider>
      )

      // Context value should be the same object when props don't change
      expect(contextValues[0]).toBe(contextValues[1])
    })
  })

  describe('useResource', () => {
    it('should throw error when used outside ResourceProvider', () => {
      const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})

      expect(() => {
        render(<ResourceConsumer onResource={() => {}} />)
      }).toThrow('useResource must be used within a ResourceProvider')

      consoleError.mockRestore()
    })
  })
})

describe('ResourcesContext', () => {
  describe('ResourcesProvider', () => {
    it('should provide empty resources array by default', () => {
      render(
        <ResourcesProvider>
          <ResourcesConsumer onResources={() => {}} />
        </ResourcesProvider>
      )

      expect(screen.getByTestId('count').textContent).toBe('0')
    })

    it('should initialize with provided resources', () => {
      const resources: ResourceDefinition[] = [
        { name: 'users', label: 'Users' },
        { name: 'products', label: 'Products' },
      ]

      render(
        <ResourcesProvider resources={resources}>
          <ResourcesConsumer onResources={() => {}} />
        </ResourcesProvider>
      )

      expect(screen.getByTestId('count').textContent).toBe('2')
      expect(screen.getByTestId('names').textContent).toBe('users,products')
    })

    it('should allow registering new resources', () => {
      let ctx: ResourcesContextValue | undefined

      render(
        <ResourcesProvider>
          <ResourcesConsumer onResources={(c) => { ctx = c }} />
        </ResourcesProvider>
      )

      expect(screen.getByTestId('count').textContent).toBe('0')

      act(() => {
        ctx!.registerResource({ name: 'orders', label: 'Orders' })
      })

      // Note: This tests imperative registration - the display may not update
      // because ResourcesProvider uses a ref-based state pattern
      expect(ctx!.getResource('orders')).toBeDefined()
      expect(ctx!.getResource('orders')?.label).toBe('Orders')
    })

    it('should allow unregistering resources', () => {
      let ctx: ResourcesContextValue | undefined
      const resources: ResourceDefinition[] = [
        { name: 'users', label: 'Users' },
        { name: 'products', label: 'Products' },
      ]

      render(
        <ResourcesProvider resources={resources}>
          <ResourcesConsumer onResources={(c) => { ctx = c }} />
        </ResourcesProvider>
      )

      act(() => {
        ctx!.unregisterResource('users')
      })

      expect(ctx!.getResource('users')).toBeUndefined()
      expect(ctx!.getResource('products')).toBeDefined()
    })

    it('should allow getting a resource by name', () => {
      let ctx: ResourcesContextValue | undefined
      const resources: ResourceDefinition[] = [
        { name: 'users', label: 'Users', labelPlural: 'All Users' },
      ]

      render(
        <ResourcesProvider resources={resources}>
          <ResourcesConsumer onResources={(c) => { ctx = c }} />
        </ResourcesProvider>
      )

      const resource = ctx!.getResource('users')
      expect(resource?.name).toBe('users')
      expect(resource?.label).toBe('Users')
      expect(resource?.labelPlural).toBe('All Users')
    })

    it('should return undefined for non-existent resource', () => {
      let ctx: ResourcesContextValue | undefined

      render(
        <ResourcesProvider>
          <ResourcesConsumer onResources={(c) => { ctx = c }} />
        </ResourcesProvider>
      )

      expect(ctx!.getResource('nonexistent')).toBeUndefined()
    })

    it('should handle resource registration with same name (update)', () => {
      let ctx: ResourcesContextValue | undefined
      const resources: ResourceDefinition[] = [
        { name: 'users', label: 'Users' },
      ]

      render(
        <ResourcesProvider resources={resources}>
          <ResourcesConsumer onResources={(c) => { ctx = c }} />
        </ResourcesProvider>
      )

      act(() => {
        ctx!.registerResource({ name: 'users', label: 'Updated Users', list: MockListComponent })
      })

      const resource = ctx!.getResource('users')
      expect(resource?.label).toBe('Updated Users')
      expect(resource?.list).toBe(MockListComponent)
    })
  })

  describe('useResources', () => {
    it('should throw error when used outside ResourcesProvider', () => {
      const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})

      expect(() => {
        render(<ResourcesConsumer onResources={() => {}} />)
      }).toThrow('useResources must be used within a ResourcesProvider')

      consoleError.mockRestore()
    })
  })

  describe('stale registration bug (issue ui-n710)', () => {
    /**
     * This test demonstrates the stale registration issue identified in code review:
     * When a resource's view components change after initial registration,
     * the registered definition may become stale because useEffect only
     * depends on [name].
     *
     * This is a KNOWN LIMITATION that should either be fixed or documented.
     */
    it('should update registered definition when view components change', () => {
      let ctx: ResourcesContextValue | undefined

      // Component that dynamically changes resource definition
      function DynamicResource() {
        const [hasEdit, setHasEdit] = useState(false)

        const definition: ResourceDefinition = {
          name: 'users',
          label: 'Users',
          list: MockListComponent,
          ...(hasEdit ? { edit: MockEditComponent } : {}),
        }

        return (
          <>
            <ResourcesProvider resources={[definition]}>
              <ResourcesConsumer onResources={(c) => { ctx = c }} />
            </ResourcesProvider>
            <button onClick={() => setHasEdit(true)} data-testid="add-edit">
              Add Edit
            </button>
          </>
        )
      }

      render(<DynamicResource />)

      // Initially no edit
      expect(ctx!.getResource('users')?.edit).toBeUndefined()

      // Add edit component
      act(() => {
        screen.getByTestId('add-edit').click()
      })

      // This test documents the expected behavior:
      // After changing the resource definition, getResource should return
      // the updated definition with the edit component.
      // If this fails, it means the stale registration bug exists.
      expect(ctx!.getResource('users')?.edit).toBe(MockEditComponent)
    })
  })
})
