import EventMacroHelpers

defmodule Raxol.UI.Components.EdgeCases.ComponentEdgeCasesTest do
  use ExUnit.Case, async: false
  import Raxol.Test.TestUtils, only: [create_test_component: 2]

  import Raxol.ComponentTestHelpers,
    only: [simulate_event_sequence: 2, simulate_lifecycle: 2]

  # import Raxol.ComponentTestHelpers # Not needed for create_test_component
  import Raxol.Test.PerformanceHelper
  alias Raxol.Test.Unit

  # Component that simulates heavy computation
  defmodule HeavyComponent do
    @behaviour Raxol.UI.Components.Base.Component

    defstruct id: :test_id,
              type: :heavy_component,
              value: nil,
              render_count: 0,
              mounted: false,
              position: {0, 0},
              style: %{}

    def new(props \\ %{}) do
      init(props)
    end

    @impl Raxol.UI.Components.Base.Component
    def init(props) do
      Map.merge(
        %{
          data: [],
          computation_time: 0,
          render_count: 0,
          position: {0, 0},
          type: :heavy_component,
          style: %{}
        },
        props
      )
    end

    @impl Raxol.UI.Components.Base.Component
    def mount(state) do
      {state, []}
    end

    def update(:add_data, state) do
      # Simulate heavy computation
      start_time = System.monotonic_time()
      new_data = Enum.map(1..1000, &%{id: &1, value: &1 * 2})
      end_time = System.monotonic_time()

      computation_time =
        System.convert_time_unit(end_time - start_time, :native, :millisecond)

      new_state = %{state | data: new_data, computation_time: computation_time}

      {new_state, []}
    end

    @impl Raxol.UI.Components.Base.Component
    def render(state, _context) do
      {state,
       %{
         type: :heavy_component,
         data_length: length(state.data),
         computation_time: state.computation_time
       }}
    end

    def handle_event(%Raxol.Core.Events.Event{type: :add_data}, state) do
      # Simulate heavy computation
      start_time = System.monotonic_time()
      new_data = Enum.map(1..1000, &%{id: &1, value: &1 * 2})
      end_time = System.monotonic_time()

      computation_time =
        System.convert_time_unit(end_time - start_time, :native, :microsecond)

      # Accumulate computation time instead of resetting
      total_computation_time =
        Map.get(state, :computation_time, 0) + computation_time

      new_state = Map.put(state, :data, new_data)
      new_state = Map.put(new_state, :computation_time, total_computation_time)

      {new_state, []}
    end

    def handle_event(_event, state) do
      {state, []}
    end

    @impl Raxol.UI.Components.Base.Component
    def handle_event(event, state, _context) do
      handle_event(event, state)
    end

    @impl Raxol.UI.Components.Base.Component
    def unmount(state) do
      state
    end
  end

  # Component that simulates error conditions
  defmodule ErrorProneComponent do
    @behaviour Raxol.UI.Components.Base.Component

    defstruct id: :test_id,
              type: :error_prone,
              value: nil,
              render_count: 0,
              mounted: false,
              position: {0, 0},
              style: %{}

    def new(props \\ %{}) do
      init(props)
    end

    @impl Raxol.UI.Components.Base.Component
    def init(props) do
      Map.merge(
        %{
          error_count: 0,
          last_error: nil,
          render_count: 0,
          position: {0, 0},
          type: :error_prone,
          style: %{}
        },
        props
      )
    end

    @impl Raxol.UI.Components.Base.Component
    def mount(state) do
      {state, []}
    end

    def update(:trigger_error, state) do
      new_state = %{
        state
        | error_count: Map.get(state, :error_count, 0) + 1,
          last_error: :simulated_error
      }

      {new_state, []}
    end

    @impl Raxol.UI.Components.Base.Component
    def render(state, _context) do
      if Map.get(state, :error_count, 0) > 2 do
        raise "Simulated render error"
      end

      {state,
       %{
         type: :error_prone,
         error_count: Map.get(state, :error_count, 0),
         last_error: Map.get(state, :last_error, nil)
       }}
    end

    def handle_event(%Raxol.Core.Events.Event{type: :error_event}, _state) do
      raise "Simulated event handling error"
    end

    def handle_event(
          %Raxol.Core.Events.Event{type: :trigger_error, data: _data},
          state
        ) do
      new_state = %{
        state
        | error_count: Map.get(state, :error_count, 0) + 1,
          last_error: :simulated_error
      }

      # Raise error if error_count > 2 (same as render function)
      if Map.get(new_state, :error_count, 0) > 2 do
        raise "Simulated render error"
      end

      {new_state, []}
    end

    def handle_event(_event, state) do
      {state, []}
    end

    @impl Raxol.UI.Components.Base.Component
    def handle_event(event, state, _context) do
      handle_event(event, state)
    end

    @impl Raxol.UI.Components.Base.Component
    def unmount(state) do
      state
    end
  end

  # Component that simulates invalid mount
  defmodule InvalidMountComponent do
    @behaviour Raxol.UI.Components.Base.Component

    def new(props \\ %{}) do
      init(props)
    end

    @impl Raxol.UI.Components.Base.Component
    def init(props), do: props

    @impl Raxol.UI.Components.Base.Component
    def mount(_state) do
      raise "Simulated mount error"
    end

    @impl Raxol.UI.Components.Base.Component
    def update(_msg, state), do: {state, []}

    @impl Raxol.UI.Components.Base.Component
    def handle_event(_event, state, _context), do: {state, []}

    @impl Raxol.UI.Components.Base.Component
    def render(state, _context), do: {state, %{type: :invalid_mount}}
    @impl Raxol.UI.Components.Base.Component
    def handle_event(_event, state), do: {state, []}
    @impl Raxol.UI.Components.Base.Component
    def unmount(state), do: state
  end

  # Component that simulates invalid unmount
  defmodule InvalidUnmountComponent do
    @behaviour Raxol.UI.Components.Base.Component

    def new(props \\ %{}) do
      init(props)
    end

    @impl Raxol.UI.Components.Base.Component
    def init(props), do: props
    @impl Raxol.UI.Components.Base.Component
    def mount(state), do: {state, []}
    @impl Raxol.UI.Components.Base.Component
    def update(_msg, state), do: {state, []}
    @impl Raxol.UI.Components.Base.Component
    def render(state, _context), do: {state, %{type: :invalid_unmount}}
    @impl Raxol.UI.Components.Base.Component
    def handle_event(_event, state, _context), do: {state, []}

    @impl Raxol.UI.Components.Base.Component
    def unmount(_state) do
      raise "Simulated unmount error"
    end
  end

  describe "Performance Edge Cases" do
    test "handles large data sets" do
      component = create_test_component(HeavyComponent, %{})

      # Measure performance with large data set
      {time_ms, _result} =
        measure_time(fn ->
          {updated, _} =
            Unit.simulate_event(
              component,
              Raxol.Core.Events.Event.new(:add_data, %{})
            )

          assert length(updated.state.data) == 1000
        end)

      # Verify performance metrics
      # Less than 1 second for the operation
      assert time_ms < 1000

      # (If you want to check total time for multiple iterations, use measure_average_time)
    end

    test "handles rapid state updates" do
      component = create_test_component(HeavyComponent, %{})

      # Simulate rapid state updates
      events =
        Enum.map(1..100, fn _ -> Raxol.Core.Events.Event.new(:add_data, %{}) end)

      updated = simulate_event_sequence(component, events)

      # Verify component remains stable
      assert length(updated.state.data) == 1000
      assert updated.state.computation_time > 0
    end

    test "handles memory usage" do
      component = create_test_component(HeavyComponent, %{})

      # Simulate memory-intensive operations
      events =
        Enum.map(1..10, fn _ -> Raxol.Core.Events.Event.new(:add_data, %{}) end)

      updated = simulate_event_sequence(component, events)

      # Verify memory usage is reasonable
      assert length(updated.state.data) == 1000
      # Less than 5 seconds
      assert updated.state.computation_time < 5000
    end
  end

  describe "Error Handling Edge Cases" do
    test "handles render errors gracefully" do
      component = create_test_component(ErrorProneComponent, %{})

      # Trigger render errors
      assert_raise RuntimeError, "Simulated render error", fn ->
        simulate_event_sequence(component, [
          Raxol.Core.Events.Event.new(:trigger_error, %{}),
          Raxol.Core.Events.Event.new(:trigger_error, %{}),
          Raxol.Core.Events.Event.new(:trigger_error, %{})
        ])
      end
    end

    test "handles event handling errors gracefully" do
      component = create_test_component(ErrorProneComponent, %{})

      # Trigger event handling error
      assert_raise RuntimeError, "Simulated event handling error", fn ->
        Unit.simulate_event(
          component,
          Raxol.Core.Events.Event.new(:error_event, %{})
        )
      end
    end

    test "recovers from errors" do
      component = create_test_component(ErrorProneComponent, %{})

      # Trigger and recover from error
      assert_raise RuntimeError, "Simulated event handling error", fn ->
        Unit.simulate_event(
          component,
          Raxol.Core.Events.Event.new(:error_event, %{})
        )
      end

      # Verify component can still handle normal events
      {updated, _} =
        Unit.simulate_event(
          component,
          Raxol.Core.Events.Event.new(:normal_event, %{})
        )

      assert updated.state.error_count == 0
    end
  end

  describe "State Management Edge Cases" do
    test "handles nil state values" do
      component = create_test_component(ErrorProneComponent, %{last_error: nil})

      # Verify component handles nil values
      {updated, _} =
        Unit.simulate_event(
          component,
          Raxol.Core.Events.Event.new(:trigger_error, %{})
        )

      assert updated.state.last_error == :simulated_error
    end

    test "handles invalid state updates" do
      component = create_test_component(ErrorProneComponent, %{})

      # Attempt invalid state update
      {updated, _} =
        Unit.simulate_event(
          component,
          Raxol.Core.Events.Event.new(:invalid_update, %{})
        )

      assert updated.state == component.state
    end

    test "handles state type mismatches" do
      component = create_test_component(ErrorProneComponent, %{})

      # Attempt to update with wrong type
      {updated, _} =
        Unit.simulate_event(
          component,
          Raxol.Core.Events.Event.new(:trigger_error, %{value: "string"})
        )

      assert updated.state.error_count == 1
    end
  end

  describe "Lifecycle Edge Cases" do
    test "handles multiple mount/unmount cycles" do
      component = create_test_component(ErrorProneComponent, %{})

      # Simulate multiple mount/unmount cycles
      Enum.each(1..5, fn _ ->
        {mounted, _} = simulate_lifecycle(component, & &1)
        assert mounted.state.error_count == 0
      end)
    end

    test "handles mount errors" do
      # Create the component first (this should not raise an error)
      component = create_test_component(InvalidMountComponent, %{})

      # Verify mount error is handled during lifecycle simulation
      assert_raise RuntimeError, "Simulated mount error", fn ->
        simulate_lifecycle(component, & &1)
      end
    end

    test ~c"handles unmount errors" do
      component = create_test_component(InvalidUnmountComponent, %{})

      # Verify unmount error is handled
      assert_raise RuntimeError, "Simulated unmount error", fn ->
        simulate_lifecycle(component, & &1)
      end
    end
  end
end
