import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { vi } from 'vitest'
import { ReactElement } from 'react'

/**
 * Common test utilities and helpers for Jomblo e-commerce platform
 */

/**
 * Render component with all necessary providers and context
 */
export function renderWithProviders(
  ui: ReactElement,
  options: {
    initialProps?: any
    user?: any
    theme?: 'light' | 'dark'
  } = {}
) {
  const { initialProps = {}, user, theme = 'light' } = options

  // Mock theme provider
  const ThemeProvider = ({ children }: { children: React.ReactNode }) => (
    <div data-theme={theme}>{children}</div>
  )

  // Mock session provider
  const SessionProvider = ({ children }: { children: React.ReactNode }) => (
    <div data-user={user ? JSON.stringify(user) : 'null'}>{children}</div>
  )

  const AllTheProviders = ({ children }: { children: React.ReactNode }) => (
    <ThemeProvider>
      <SessionProvider>{children}</SessionProvider>
    </ThemeProvider>
  )

  return {
    user: userEvent.setup(),
    ...render(ui, { wrapper: AllTheProviders, ...initialProps }),
  }
}

/**
 * Create a mock API response
 */
export function createMockApiResponse<T>(
  data: T,
  status: number = 200,
  headers: Record<string, string> = {}
) {
  return {
    ok: status >= 200 && status < 300,
    status,
    statusText: status === 200 ? 'OK' : 'Error',
    headers: new Headers({
      'Content-Type': 'application/json',
      ...headers,
    }),
    json: () => Promise.resolve(data),
    text: () => Promise.resolve(JSON.stringify(data)),
  } as Response
}

/**
 * Mock fetch globally for API testing
 */
export function mockFetch(response: any, status: number = 200) {
  global.fetch = vi.fn(() =>
    Promise.resolve(createMockApiResponse(response, status))
  ) as any
}

/**
 * Reset all mocks
 */
export function resetAllMocks() {
  vi.clearAllMocks()
  vi.resetAllMocks()
  vi.restoreAllMocks()
}

/**
 * Wait for element to be removed from DOM
 */
export async function waitForElementToBeRemoved(element: HTMLElement) {
  await waitFor(() => {
    expect(element).not.toBeInTheDocument()
  })
}

/**
 * Create a mock user event
 */
export function createMockUserEvent() {
  return userEvent.setup()
}

/**
 * Mock console methods to avoid noise in tests
 */
export function mockConsole() {
  const originalConsole = { ...console }
  
  beforeEach(() => {
    console.log = vi.fn()
    console.warn = vi.fn()
    console.error = vi.fn()
  })

  afterEach(() => {
    Object.assign(console, originalConsole)
  })
}

/**
 * Mock window.location
 */
export function mockLocation() {
  delete (window as any).location
  window.location = {
    href: 'http://localhost:3000',
    origin: 'http://localhost:3000',
    protocol: 'http:',
    host: 'localhost:3000',
    hostname: 'localhost',
    port: '3000',
    pathname: '/',
    search: '',
    hash: '',
    assign: vi.fn(),
    replace: vi.fn(),
    reload: vi.fn(),
    toString: () => 'http://localhost:3000',
  } as any
}

/**
 * Mock localStorage
 */
export function mockLocalStorage() {
  const store: Record<string, string> = {}

  beforeEach(() => {
    Object.defineProperty(window, 'localStorage', {
      value: {
        getItem: vi.fn((key: string) => store[key] || null),
        setItem: vi.fn((key: string, value: string) => {
          store[key] = value
        }),
        removeItem: vi.fn((key: string) => {
          delete store[key]
        }),
        clear: vi.fn(() => {
          Object.keys(store).forEach(key => delete store[key])
        }),
        length: 0,
        key: vi.fn((index: number) => Object.keys(store)[index]),
      },
      writable: true,
    })
  })
}

/**
 * Mock sessionStorage
 */
export function mockSessionStorage() {
  const store: Record<string, string> = {}

  beforeEach(() => {
    Object.defineProperty(window, 'sessionStorage', {
      value: {
        getItem: vi.fn((key: string) => store[key] || null),
        setItem: vi.fn((key: string, value: string) => {
          store[key] = value
        }),
        removeItem: vi.fn((key: string) => {
          delete store[key]
        }),
        clear: vi.fn(() => {
          Object.keys(store).forEach(key => delete store[key])
        }),
        length: 0,
        key: vi.fn((index: number) => Object.keys(store)[index]),
      },
      writable: true,
    })
  })
}

/**
 * Mock IntersectionObserver
 */
export function mockIntersectionObserver() {
  beforeEach(() => {
    global.IntersectionObserver = vi.fn().mockImplementation(() => ({
      observe: vi.fn(),
      unobserve: vi.fn(),
      disconnect: vi.fn(),
    }))
  })
}

/**
 * Mock ResizeObserver
 */
export function mockResizeObserver() {
  beforeEach(() => {
    global.ResizeObserver = vi.fn().mockImplementation(() => ({
      observe: vi.fn(),
      unobserve: vi.fn(),
      disconnect: vi.fn(),
    }))
  })
}

/**
 * Create a mock form data
 */
export function createMockFormData(data: Record<string, any>) {
  const formData = new FormData()
  
  Object.entries(data).forEach(([key, value]) => {
    if (value instanceof File) {
      formData.append(key, value)
    } else {
      formData.append(key, String(value))
    }
  })

  return formData
}

/**
 * Mock file upload
 */
export function createMockFile(
  name: string = 'test.jpg',
  type: string = 'image/jpeg',
  size: number = 1024
) {
  return new File(['mock file content'], name, { type, size })
}

/**
 * Wait for next tick
 */
export function waitForNextTick() {
  return new Promise(resolve => setTimeout(resolve, 0))
}

/**
 * Create a mock router
 */
export function createMockRouter(initialRoute: string = '/') {
  return {
    route: initialRoute,
    pathname: initialRoute,
    query: {},
    asPath: initialRoute,
    push: vi.fn(),
    replace: vi.fn(),
    reload: vi.fn(),
    back: vi.fn(),
    prefetch: vi.fn(),
    beforePopState: vi.fn(),
    events: {
      on: vi.fn(),
      off: vi.fn(),
      emit: vi.fn(),
    },
    isFallback: false,
    isLocaleDomain: true,
    isReady: true,
    isPreview: false,
  }
}

/**
 * Mock Next.js router
 */
export function mockNextRouter(router: any = {}) {
  vi.mock('next/router', () => ({
    useRouter: () => ({
      route: '/',
      pathname: '/',
      query: {},
      asPath: '/',
      push: vi.fn(),
      replace: vi.fn(),
      reload: vi.fn(),
      back: vi.fn(),
      prefetch: vi.fn(),
      beforePopState: vi.fn(),
      events: {
        on: vi.fn(),
        off: vi.fn(),
        emit: vi.fn(),
      },
      isFallback: false,
      isLocaleDomain: true,
      isReady: true,
      isPreview: false,
      ...router,
    }),
  }))
}

/**
 * Mock Next.js navigation
 */
export function mockNextNavigation() {
  vi.mock('next/navigation', () => ({
    useRouter: () => ({
      push: vi.fn(),
      replace: vi.fn(),
      refresh: vi.fn(),
      back: vi.fn(),
      forward: vi.fn(),
      prefetch: vi.fn(),
    }),
    useSearchParams: () => new URLSearchParams(),
    usePathname: () => '/',
    redirect: vi.fn(),
    notFound: vi.fn(),
  }))
}

/**
 * Create a mock error boundary
 */
export function createMockErrorBoundary() {
  return {
    ErrorBoundary: ({ children }: { children: React.ReactNode }) => {
      try {
        return <>{children}</>
      } catch (error) {
        return <div data-testid="error-boundary">Error occurred</div>
      }
    },
  }
}