import { fireEvent, render, screen } from "@testing-library/react"
import { describe, expect, it, vi } from "vitest"
import React from "react"

// Error Boundary component for testing
class ApiErrorBoundary extends React.Component<
  { children: React.ReactNode; fallback?: React.ReactNode },
  { hasError: boolean; error: Error | null }
> {
  constructor(props: { children: React.ReactNode; fallback?: React.ReactNode }) {
    super(props)
    this.state = { hasError: false, error: null }
  }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error }
  }

  handleRetry = () => {
    this.setState({ hasError: false, error: null })
  }

  render() {
    if (this.state.hasError) {
      return (
        <div role="alert" data-testid="error-boundary">
          <h2>Something went wrong</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={this.handleRetry}>Retry</button>
        </div>
      )
    }
    return this.props.children
  }
}

// Test component that throws
function ThrowingComponent({ shouldThrow }: { shouldThrow: boolean }) {
  if (shouldThrow) {
    throw new Error("Test error message")
  }
  return <div data-testid="child-content">Child content</div>
}

describe("ApiErrorBoundary", () => {
  // Suppress React error boundary console.error in tests
  const originalConsoleError = console.error
  beforeAll(() => {
    console.error = (...args: unknown[]) => {
      if (
        typeof args[0] === "string" &&
        args[0].includes("React will try to recreate")
      ) {
        return
      }
      originalConsoleError(...args)
    }
  })
  afterAll(() => {
    console.error = originalConsoleError
  })

  it("renders children normally when no error", () => {
    render(
      <ApiErrorBoundary>
        <ThrowingComponent shouldThrow={false} />
      </ApiErrorBoundary>
    )

    expect(screen.getByTestId("child-content")).toBeInTheDocument()
    expect(screen.getByText("Child content")).toBeInTheDocument()
  })

  it("shows error UI when child throws", () => {
    render(
      <ApiErrorBoundary>
        <ThrowingComponent shouldThrow={true} />
      </ApiErrorBoundary>
    )

    expect(screen.getByTestId("error-boundary")).toBeInTheDocument()
    expect(screen.getByText("Something went wrong")).toBeInTheDocument()
    expect(screen.getByText("Test error message")).toBeInTheDocument()
  })

  it("retry button resets error state", () => {
    // Use a mutable ref to control throwing
    let shouldThrow = true

    function ConditionalThrower() {
      if (shouldThrow) throw new Error("Temp error")
      return <div data-testid="recovered">Recovered!</div>
    }

    render(
      <ApiErrorBoundary>
        <ConditionalThrower />
      </ApiErrorBoundary>
    )

    // Error state shown
    expect(screen.getByTestId("error-boundary")).toBeInTheDocument()

    // Fix the condition
    shouldThrow = false

    // Click retry
    fireEvent.click(screen.getByRole("button", { name: /retry/i }))

    // Should now render children again
    expect(screen.getByTestId("recovered")).toBeInTheDocument()
  })
})
