defmodule Towerops.Monitoring.Executors.DnsExecutor do
  @moduledoc """
  Executes DNS resolution checks using :inet_res.

  Config format:
  %{
    "hostname" => "example.com",
    "server" => "8.8.8.8",  # optional, DNS server (uses system default if nil)
    "record_type" => "A",  # optional, default: A
    "expected" => "93.184.216.34"  # optional, expected result
  }
  """

  require Logger

  @default_timeout 5000

  @doc """
  Executes a DNS check.

  Returns:
  - {:ok, response_time_ms, output} on success
  - {:error, reason} on failure
  """
  def execute(config, timeout_ms \\ @default_timeout) do
    hostname = Map.fetch!(config, "hostname")
    server = Map.get(config, "server")
    record_type = Map.get(config, "record_type", "A")
    expected = Map.get(config, "expected")

    # Convert to Erlang atoms/charlists
    hostname_charlist = String.to_charlist(hostname)
    record_type_atom = record_type_to_atom(record_type)

    start_time = System.monotonic_time(:millisecond)

    result =
      if server do
        # Use specific DNS server
        server_tuple = parse_server(server)

        :inet_res.resolve(hostname_charlist, :in, record_type_atom, [
          {:nameservers, [server_tuple]},
          {:timeout, timeout_ms}
        ])
      else
        # Use system default DNS
        :inet_res.resolve(hostname_charlist, :in, record_type_atom, [{:timeout, timeout_ms}])
      end

    case result do
      {:ok, dns_rec} ->
        response_time = System.monotonic_time(:millisecond) - start_time
        process_dns_response(dns_rec, record_type_atom, expected, response_time)

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

      {:error, :nxdomain} ->
        {:error, "Domain not found (NXDOMAIN)"}

      {:error, :servfail} ->
        {:error, "DNS server failure (SERVFAIL)"}

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

  defp record_type_to_atom("A"), do: :a
  defp record_type_to_atom("AAAA"), do: :aaaa
  defp record_type_to_atom("CNAME"), do: :cname
  defp record_type_to_atom("MX"), do: :mx
  defp record_type_to_atom("TXT"), do: :txt
  defp record_type_to_atom("NS"), do: :ns
  defp record_type_to_atom("PTR"), do: :ptr
  defp record_type_to_atom(_), do: :a

  defp parse_server(server_str) do
    case String.split(server_str, ".") do
      [a, b, c, d] ->
        {String.to_integer(a), String.to_integer(b), String.to_integer(c), String.to_integer(d), 53}

      _ ->
        {8, 8, 8, 8, 53}
    end
  end

  defp process_dns_response(dns_rec, record_type, expected, response_time) do
    answers = extract_answers(dns_rec, record_type)

    cond do
      Enum.empty?(answers) ->
        {:error, "No #{record_type} records found"}

      expected && !Enum.any?(answers, &(&1 == expected)) ->
        {:error, "Expected '#{expected}', got: #{Enum.join(answers, ", ")}"}

      true ->
        {:ok, response_time, "Resolved to: #{Enum.join(answers, ", ")}"}
    end
  end

  defp extract_answers(dns_rec, :a) do
    dns_rec
    |> :inet_dns.msg(:anlist)
    |> Enum.filter(fn rr -> :inet_dns.rr(rr, :type) == :a end)
    |> Enum.map(fn rr ->
      {a, b, c, d} = :inet_dns.rr(rr, :data)
      "#{a}.#{b}.#{c}.#{d}"
    end)
  end

  defp extract_answers(dns_rec, :aaaa) do
    dns_rec
    |> :inet_dns.msg(:anlist)
    |> Enum.filter(fn rr -> :inet_dns.rr(rr, :type) == :aaaa end)
    |> Enum.map(fn rr ->
      data = :inet_dns.rr(rr, :data)
      format_ipv6(data)
    end)
  end

  defp extract_answers(dns_rec, :cname) do
    dns_rec
    |> :inet_dns.msg(:anlist)
    |> Enum.filter(fn rr -> :inet_dns.rr(rr, :type) == :cname end)
    |> Enum.map(fn rr ->
      rr |> :inet_dns.rr(:data) |> to_string()
    end)
  end

  defp extract_answers(dns_rec, :mx) do
    dns_rec
    |> :inet_dns.msg(:anlist)
    |> Enum.filter(fn rr -> :inet_dns.rr(rr, :type) == :mx end)
    |> Enum.map(fn rr ->
      {priority, exchange} = :inet_dns.rr(rr, :data)
      "#{priority} #{exchange}"
    end)
  end

  defp extract_answers(dns_rec, :txt) do
    dns_rec
    |> :inet_dns.msg(:anlist)
    |> Enum.filter(fn rr -> :inet_dns.rr(rr, :type) == :txt end)
    |> Enum.map(fn rr ->
      rr |> :inet_dns.rr(:data) |> Enum.join("")
    end)
  end

  defp extract_answers(_dns_rec, _type), do: []

  defp format_ipv6({a, b, c, d, e, f, g, h}) do
    [a, b, c, d, e, f, g, h]
    |> Enum.map_join(":", &Integer.to_string(&1, 16))
    |> String.downcase()
  end
end
