defmodule RefugeWeb.BuildLive do
  use RefugeWeb, :live_view
  require Logger

  # Default example directory and bazel path
  @default_example_dir "/Users/osayame/trace/examples/go-tutorial/stage3"
  @default_target "//..."
  @bazel_path "/usr/local/bin/bazel"

  # Build event service connection details
  # gRPC settings
  @grpc_port 1985

  @impl true
  def mount(_params, _session, socket) do
    {:ok,
     assign(socket,
       build_status: nil,
       build_output: nil,
       build_events: [],
       run_output: nil,
       page_title: "Build Runner",
       example_dir: @default_example_dir,
       target: @default_target,
       streaming: false
     )}
  end

  @impl true
  def handle_event("set_directory", %{"build_dir" => build_dir, "target" => target}, socket) do
    # Verify that the directory exists
    if File.dir?(build_dir) do
      {:noreply,
       socket
       |> assign(:example_dir, build_dir)
       |> assign(:target, target)
       |> assign(:build_output, nil)
       |> assign(:run_output, nil)
       |> assign(:build_status, nil)}
    else
      {:noreply,
       socket
       |> put_flash(:error, "The specified directory does not exist: #{build_dir}")}
    end
  end

  @impl true
  def handle_event("set_directory", %{"build_dir" => build_dir}, socket) do
    # Same as above but without changing target
    if File.dir?(build_dir) do
      {:noreply,
       socket
       |> assign(:example_dir, build_dir)
       |> assign(:build_output, nil)
       |> assign(:run_output, nil)
       |> assign(:build_status, nil)}
    else
      {:noreply,
       socket
       |> put_flash(:error, "The specified directory does not exist: #{build_dir}")}
    end
  end

  @impl true
  def handle_event("run_build", _params, socket) do
    # Run the build process asynchronously
    pid = self()

    # Start streaming logs from NativeLink server
    spawn_link(fn ->
      # Start log streaming
      send(pid, {:stream_started})

      # Subscribe to gRPC EventStore for build events
      :ok = GenServer.call(Refuge.GRPC.EventStore, :subscribe)

      # Log that we're using gRPC for build events
      Logger.info("subscribed to gRPC event store")

      # Run build and execute
      example_dir = socket.assigns.example_dir
      target = socket.assigns.target

      # Stream build events by running with BES flags configured for NativeLink
      {build_output, build_status} =
        System.cmd(
          @bazel_path,
          ["build", target],
          cd: example_dir,
          stderr_to_stdout: true
        )

      # Run the target if build was successful and it's not a pattern target like //...
      {run_output, _run_status} =
        if build_status == 0 and !String.contains?(target, "...") do
          System.cmd(
            @bazel_path,
            ["run", target],
            cd: example_dir
          )
        else
          message = if build_status == 0, do: "No run output for pattern target", else: "Failed to run binary"
          {message, 1}
        end

      # Send results back to LiveView process
      send(
        pid,
        {:build_complete,
         %{
           build_status: build_status,
           build_output: build_output,
           run_output: run_output
         }}
      )
    end)

    {:noreply,
     assign(socket,
       build_status: :building,
       build_output: "Building...",
       run_output: nil,
       build_events: [],
       streaming: true
     )}
  end

  # The EventStore will send build events directly to our LiveView process
  # via the handle_info callback below. No need for polling.

  @impl true
  def handle_info({:stream_started}, socket) do
    Logger.info("Stream started")
    {:noreply, assign(socket, streaming: true)}
  end

  @impl true
  def handle_info({:build_status, status}, socket) do
    Logger.info("Build status update: #{inspect(status)}")
    {:noreply, assign(socket, build_status: status)}
  end

  @impl true
  def handle_info({:new_build_event, event}, socket) when is_binary(event) do
    # Try to parse the event JSON string
    case Jason.decode(event) do
      {:ok, parsed_event} ->
        handle_build_event(parsed_event, socket)

      {:error, error} ->
        Logger.error("Failed to parse build event: #{inspect(error)}")

        # Add error message to build events
        formatted_error = "Error parsing build event: #{inspect(error)} - Raw data: #{String.slice(event, 0, 200)}..."
        updated_events = [formatted_error | socket.assigns.build_events]
        {:noreply, assign(socket, build_events: updated_events)}
    end
  end

  @impl true
  def handle_info({:new_build_event, event}, socket) when is_map(event) do
    handle_build_event(event, socket)
  end

  @impl true
  def handle_info({:build_event_error, error_message}, socket) do
    # Add error message to build events
    formatted_error = "Build event service error: #{error_message} at #{DateTime.utc_now()}"
    updated_events = [formatted_error | socket.assigns.build_events]

    {:noreply, assign(socket, build_events: updated_events)}
  end

  @impl true
  def handle_info({:streaming_status, streaming}, socket) do
    {:noreply, assign(socket, streaming: streaming)}
  end

  # Helper to format and process build events
  defp handle_build_event(event, socket) do
    # Format the build event for display
    timestamp = event["timestamp"] || DateTime.utc_now() |> DateTime.to_string()
    event_type = event["eventType"] || event["event_type"] || "unknown"

    formatted_entry = """
    [#{timestamp}] #{event_type}
    #{Jason.encode!(event, pretty: true)}
    """

    # Add to our list of build events, newest first
    updated_events = [formatted_entry | socket.assigns.build_events] |> Enum.take(100)

    {:noreply, assign(socket, build_events: updated_events)}
  end

  # Stream started handler is already defined above

  @impl true
  def handle_info({:build_complete, results}, socket) do
    status_text = if results.build_status == 0, do: "Build completed", else: "Build failed"

    {:noreply,
     assign(socket,
       build_status: status_text,
       build_output: results.build_output,
       run_output: results.run_output,
       streaming: false
     )}
  end

  @impl true
  def handle_info({:set_directory, %{"build_dir" => build_dir, "target" => target}}, socket) do
    # Verify that the directory exists
    if File.dir?(build_dir) do
      {:noreply,
       socket
       |> assign(:example_dir, build_dir)
       |> assign(:target, target)
       |> assign(:build_output, nil)
       |> assign(:run_output, nil)
       |> assign(:build_status, nil)}
    else
      {:noreply,
       socket
       |> put_flash(:error, "The specified directory does not exist: #{build_dir}")}
    end
  end

  @impl true
  def handle_info({:set_directory, %{"build_dir" => build_dir}}, socket) do
    # Determine the target based on the directory
    # For stage1, use //:hello target
    target =
      cond do
        build_dir =~ ~r|/stage1/?$| -> "//:hello"
        true -> @default_target
      end

    # Verify that the directory exists
    if File.dir?(build_dir) do
      {:noreply,
       socket
       |> assign(:example_dir, build_dir)
       |> assign(:target, target)
       |> assign(:build_output, nil)
       |> assign(:run_output, nil)
       |> assign(:build_status, nil)}
    else
      {:noreply,
       socket
       |> put_flash(:error, "The specified directory does not exist: #{build_dir}")}
    end
  end
end
