defmodule Towerops.Monitoring.Executors.TcpExecutor do
  @moduledoc """
  Executes TCP port connectivity checks using :gen_tcp.

  Config format:
  %{
    "host" => "192.168.1.1",
    "port" => 3306,
    "send" => "...",  # optional, data to send after connection
    "expect" => "..."  # optional, expected response string
  }
  """

  require Logger

  @default_timeout 5000

  @doc """
  Executes a TCP check.

  Returns:
  - {:ok, response_time_ms, output} on success
  - {:error, reason} on failure
  """
  def execute(config, timeout_ms \\ @default_timeout) do
    host = Map.fetch!(config, "host")
    port = Map.fetch!(config, "port")
    send_data = Map.get(config, "send")
    expect_data = Map.get(config, "expect")

    # Convert host to charlist for :gen_tcp
    host_charlist = String.to_charlist(host)

    start_time = System.monotonic_time(:millisecond)

    case :gen_tcp.connect(host_charlist, port, [:binary, active: false], timeout_ms) do
      {:ok, socket} ->
        result =
          try do
            response_time = System.monotonic_time(:millisecond) - start_time

            if send_data do
              send_and_receive(socket, send_data, expect_data, timeout_ms)
            else
              {:ok, response_time, "TCP port #{port} open"}
            end
          after
            :gen_tcp.close(socket)
          end

        result

      {:error, :timeout} ->
        {:error, "Connection timeout after #{timeout_ms}ms"}

      {:error, :econnrefused} ->
        {:error, "Connection refused"}

      {:error, :ehostunreach} ->
        {:error, "Host unreachable"}

      {:error, :nxdomain} ->
        {:error, "Host not found (DNS resolution failed)"}

      {:error, reason} ->
        {:error, "Connection failed: #{inspect(reason)}"}
    end
  rescue
    e ->
      Logger.error("TCP check exception: #{inspect(e)}")
      {:error, "Exception: #{Exception.message(e)}"}
  end

  defp send_and_receive(socket, send_data, expect_data, timeout_ms) do
    case :gen_tcp.send(socket, send_data) do
      :ok ->
        if expect_data do
          receive_and_check(socket, expect_data, timeout_ms)
        else
          {:ok, 0, "Data sent successfully"}
        end

      {:error, reason} ->
        {:error, "Send failed: #{inspect(reason)}"}
    end
  end

  defp receive_and_check(socket, expect_data, timeout_ms) do
    case :gen_tcp.recv(socket, 0, timeout_ms) do
      {:ok, data} ->
        if String.contains?(data, expect_data) do
          {:ok, 0, "Received expected response"}
        else
          {:error, "Unexpected response: #{inspect(data)}"}
        end

      {:error, :timeout} ->
        {:error, "Receive timeout"}

      {:error, reason} ->
        {:error, "Receive failed: #{inspect(reason)}"}
    end
  end
end
