defmodule FarmbotOS.Lua.DataManipulationTest do
  use ExUnit.Case
  use Mimic
  setup :verify_on_exit!
  alias FarmbotOS.SysCalls.ResourceUpdate
  alias FarmbotOS.Lua.DataManipulation

  # Random tidbits needed for mocks / stubs.
  defstruct [:key, :value]

  def lua(test_name, lua_code) do
    FarmbotOS.Lua.perform_lua(lua_code, [], test_name)
  end

  test "take_photo_raw() - 0 return code" do
    expect(System, :cmd, 1, fn cmd, args ->
      assert cmd == "fswebcam"

      assert args == [
               "-r",
               "800x800",
               "-S",
               "10",
               "--no-banner",
               "--log",
               "/dev/null",
               "--save",
               "-"
             ]

      {"fake photo data", 0}
    end)

    name = "take_photo_raw() OK"
    code = "return take_photo_raw()"
    {:ok, [result, error]} = lua(name, code)
    assert result == "fake photo data"
    assert error == nil
  end

  test "take_photo_raw() - size" do
    expect(System, :cmd, 1, fn cmd, args ->
      assert cmd == "fswebcam"

      assert args == [
               "-r",
               "100x100",
               "-S",
               "10",
               "--no-banner",
               "--log",
               "/dev/null",
               "--save",
               "-"
             ]

      {"fake photo data", 0}
    end)

    name = "take_photo_raw() OK"
    code = "return take_photo_raw(100, 100)"
    {:ok, [result, error]} = lua(name, code)
    assert result == "fake photo data"
    assert error == nil
  end

  test "take_photo_raw() - args" do
    expect(System, :cmd, 1, fn cmd, args ->
      assert cmd == "fswebcam"

      assert args == [
               "-s",
               "brightness=100%",
               "-r",
               "800x800",
               "-S",
               "10",
               "--no-banner",
               "--log",
               "/dev/null",
               "--save",
               "-"
             ]

      {"fake photo data", 0}
    end)

    name = "take_photo_raw() OK"
    code = "return take_photo_raw({\"-s\", \"brightness=100%\"})"
    {:ok, [result, error]} = lua(name, code)
    assert result == "fake photo data"
    assert error == nil
  end

  test "take_photo_raw() - size and args" do
    expect(System, :cmd, 1, fn cmd, args ->
      assert cmd == "fswebcam"

      assert args == [
               "-s",
               "brightness=100%",
               "-r",
               "100x100",
               "-S",
               "10",
               "--no-banner",
               "--log",
               "/dev/null",
               "--save",
               "-"
             ]

      {"fake photo data", 0}
    end)

    name = "take_photo_raw() OK"
    code = "return take_photo_raw(100, 100, {\"-s\", \"brightness=100%\"})"
    {:ok, [result, error]} = lua(name, code)
    assert result == "fake photo data"
    assert error == nil
  end

  test "take_photo_raw() - non 0 return code" do
    expect(System, :cmd, 1, fn cmd, args ->
      assert cmd == "fswebcam"

      assert args == [
               "-r",
               "800x800",
               "-S",
               "10",
               "--no-banner",
               "--log",
               "/dev/null",
               "--save",
               "-"
             ]

      {"error", 1}
    end)

    name = "take_photo_raw() OK"
    code = "return take_photo_raw()"
    {:ok, [result, error]} = lua(name, code)
    assert result == nil
    assert error == "error"
  end

  test "base64.decode()" do
    ascii_text = "Hello, world!"
    b64_text = "SGVsbG8sIHdvcmxkIQ=="
    lua_decode = "return base64.decode(#{inspect(b64_text)})"
    {:ok, [actual]} = lua(lua_decode, lua_decode)
    assert actual == ascii_text
  end

  test "base64.encode()" do
    ascii_text = "Hello, world!"
    b64_text = "SGVsbG8sIHdvcmxkIQ=="
    lua_encode = "base64.encode(#{inspect(ascii_text)})"
    {:ok, [encode_result]} = lua(lua_encode, lua_encode)
    assert encode_result == b64_text
  end

  test "update_firmware_config([table], lua)" do
    expect(FarmbotOS.Asset, :update_firmware_config!, 1, fn resource ->
      assert Map.fetch!(resource, "movement_axis_stealth_x") == 1.0
      resource
    end)

    expect(FarmbotOS.Asset.Private, :mark_dirty!, 1, fn resource, opts ->
      assert Map.fetch!(resource, "movement_axis_stealth_x") == 1.0
      assert opts == %{}
      resource
    end)

    lua_code = """
    return update_firmware_config({movement_axis_stealth_x = 1.0})
    """

    assert {:ok, [true]} == lua("update_firmware_config", lua_code)
  end

  test "soil_height" do
    expect(FarmbotOS.Celery.SpecialValue, :soil_height, 1, fn params ->
      assert params.x == 9.9
      assert params.y == 8.8
      5.55
    end)

    result = DataManipulation.soil_height([9.9, 8.8], :fake_lua)
    assert result == {[5.55], :fake_lua}
  end

  test "update_device() name" do
    expect(ResourceUpdate, :update_resource, 1, fn "Device", nil, params ->
      assert %{"name" => "Test Farmbot"} == params
    end)

    lua_code = """
    update_device({name = "Test Farmbot"})
    return true
    """

    assert {:ok, [true]} == lua("update device test", lua_code)
  end

  test "update_device() mounted_tool_id" do
    expect(FarmbotOS.Asset.Repo, :all, 1, fn _ -> [%{id: 123}] end)

    expect(ResourceUpdate, :update_resource, 1, fn "Device", nil, params ->
      assert %{"mounted_tool_id" => 123} == params
    end)

    lua_code = "return update_device({mounted_tool_id = 123})"

    assert {:ok, [true]} == lua(lua_code, lua_code)
  end

  test "update_device() mounted_tool_id nil" do
    expect(ResourceUpdate, :update_resource, 1, fn "Device", nil, params ->
      assert %{"mounted_tool_id" => nil} == params
    end)

    lua_code = "return update_device({mounted_tool_id = 0})"

    assert {:ok, [true]} == lua(lua_code, lua_code)
  end

  test "update_device() mounted_tool_id not found with other param" do
    expect(FarmbotOS.Asset.Repo, :all, 1, fn _ -> [%{id: 123}] end)

    expect(FarmbotOS.Logger, :error, 1, fn _, error ->
      assert error == "Tool ID not found."
    end)

    expect(ResourceUpdate, :update_resource, 1, fn "Device", nil, params ->
      assert %{"name" => "name"} == params
    end)

    lua_code = "return update_device({name = \"name\", mounted_tool_id = 100})"

    assert {:ok, [true]} == lua(lua_code, lua_code)
  end

  test "update_device() mounted_tool_id not found" do
    expect(FarmbotOS.Asset.Repo, :all, 1, fn _ -> [%{id: 123}] end)

    expect(FarmbotOS.Logger, :error, 1, fn _, error ->
      assert error == "Tool ID not found."
    end)

    expect(ResourceUpdate, :update_resource, 1, fn "Device", nil, params ->
      assert %{} == params
    end)

    lua_code = "return update_device({mounted_tool_id = 1})"

    assert {:ok, [true]} == lua(lua_code, lua_code)
  end

  test "garden_size/0" do
    expect(FarmbotOS.BotState, :fetch, 2, fn ->
      %{
        informational_settings: %{
          locked: false,
          locked_at: 0
        },
        mcu_params: %{
          movement_axis_nr_steps_z: 1.1,
          movement_step_per_mm_z: 1.1,
          movement_axis_nr_steps_y: 2.3,
          movement_step_per_mm_y: 4.5,
          movement_axis_nr_steps_x: 6.7,
          movement_step_per_mm_x: 8.9
        }
      }
    end)

    expected = [
      [{"x", 0.7528089887640449}, {"y", 0.5111111111111111}, {"z", 1.0}]
    ]

    assert {:ok, expected} == lua("get garden size", "return garden_size()")
  end

  test "get_device/0" do
    fake_device = %{fake: :device}
    expect(FarmbotOS.Asset, :device, 1, fn -> fake_device end)
    expect(FarmbotOS.Asset.Device, :render, 1, fn dev -> dev end)

    lua_code = """
    get_device()
    return true
    """

    assert {:ok, [true]} == lua("get device test", lua_code)
  end

  test "get_device/1" do
    fake_device = %{name: "my farmbot", id: 23}
    expect(FarmbotOS.Asset, :device, 1, fn -> fake_device end)
    expect(FarmbotOS.Asset.Device, :render, 1, fn dev -> dev end)

    lua_code = """
    return get_device("id") == 23
    """

    assert {:ok, [true]} == lua("get device test/1", lua_code)
  end

  test "get_fbos_config/1" do
    fake_config = %{id: 47}
    expect(FarmbotOS.Asset, :fbos_config, 1, fn -> fake_config end)
    expect(FarmbotOS.Asset.FbosConfig, :render, 1, fn params -> params end)

    lua_code = "return 47 == get_fbos_config(\"id\")"

    assert {:ok, [true]} == lua("get_fbos_config", lua_code)
  end

  test "get_fbos_config/0" do
    fake_config = %{id: 47, foo: "bar"}
    expect(FarmbotOS.Asset, :fbos_config, 1, fn -> fake_config end)
    expect(FarmbotOS.Asset.FbosConfig, :render, 1, fn params -> params end)

    lua_code = """
    c = get_fbos_config()
    return (c.id == 47) and (c.foo == "bar")
    """

    assert {:ok, [true]} == lua("get_fbos_config/1", lua_code)
  end

  test "safe_z/0" do
    fake_config = %{safe_height: 100}
    expect(FarmbotOS.Asset, :fbos_config, 1, fn -> fake_config end)
    expect(FarmbotOS.Asset.FbosConfig, :render, 1, fn params -> params end)
    assert {[100], :lua} == DataManipulation.safe_z([], :lua)
  end

  test "get_firmware_config/1" do
    fake_config = %{id: 47}
    expect(FarmbotOS.Asset, :firmware_config, 1, fn -> fake_config end)

    expect(FarmbotOS.Asset.FirmwareConfig, :render, 1, fn params -> params end)

    lua_code = "return 47 == get_firmware_config(\"id\")"

    assert {:ok, [true]} == lua("get_firmware_config", lua_code)
  end

  test "get_firmware_config/0" do
    fake_config = %{id: 47, foo: "bar"}
    expect(FarmbotOS.Asset, :firmware_config, 1, fn -> fake_config end)

    expect(FarmbotOS.Asset.FirmwareConfig, :render, 1, fn params -> params end)

    lua_code = """
    c = get_firmware_config()
    return (c.id == 47) and (c.foo == "bar")
    """

    assert {:ok, [true]} == lua("get_firmware_config/1", lua_code)
  end

  test "get_tool() by id" do
    expect(FarmbotOS.Asset, :get_tool, 1, fn params ->
      assert params == %{:id => 1}

      %{
        :id => 1,
        :name => "tool",
        :flow_rate_ml_per_s => 0,
        :seeder_tip_z_offset => 100
      }
    end)

    lua_code = "return get_tool({id = 1})"

    expected = [
      {"flow_rate_ml_per_s", 0},
      {"id", 1},
      {"name", "tool"},
      {"seeder_tip_z_offset", 100}
    ]

    assert {:ok, [expected]} == lua(lua_code, lua_code)
  end

  test "get_tool() by name" do
    expect(FarmbotOS.Asset, :get_tool, 1, fn params ->
      assert params == %{:name => "tool"}

      %{
        :id => 1,
        :name => "tool",
        :flow_rate_ml_per_s => 0,
        :seeder_tip_z_offset => 100
      }
    end)

    lua_code = "return get_tool({name = \"tool\"})"

    expected = [
      {"flow_rate_ml_per_s", 0},
      {"id", 1},
      {"name", "tool"},
      {"seeder_tip_z_offset", 100}
    ]

    assert {:ok, [expected]} == lua(lua_code, lua_code)
  end

  test "get_tool() not found" do
    expect(FarmbotOS.Asset, :get_tool, 1, fn _ -> nil end)

    lua_code = "return get_tool({id = 1})"
    assert {:ok, [nil]} == lua(lua_code, lua_code)
  end

  test "get_weeds()" do
    expect(FarmbotOS.Asset, :get_all_points_by_type, 1, fn "Weed" ->
      [
        %{
          id: 1,
          x: 10,
          y: 20,
          radius: 5,
          plant_stage: "active",
          created_at: ~U[2023-01-01 00:00:00Z]
        },
        %{
          id: 2,
          x: 30,
          y: 40,
          radius: 10,
          plant_stage: "pending",
          created_at: ~U[2023-01-01 00:00:00Z]
        }
      ]
    end)

    lua_code = "return get_weeds()"

    {:ok, [actual]} = lua(lua_code, lua_code)
    [{1, weed_data}] = actual

    assert Enum.any?(weed_data, fn
             {"age", age} when is_integer(age) -> true
             _ -> false
           end)

    assert Enum.any?(weed_data, fn
             {"id", id} when id == 1 -> true
             _ -> false
           end)

    assert Enum.any?(weed_data, fn
             {"plant_stage", stage} when stage == "active" -> true
             _ -> false
           end)

    assert Enum.any?(weed_data, fn
             {"radius", radius} when radius == 5 -> true
             _ -> false
           end)

    assert Enum.any?(weed_data, fn
             {"x", x} when x == 10 -> true
             _ -> false
           end)

    assert Enum.any?(weed_data, fn
             {"y", y} when y == 20 -> true
             _ -> false
           end)
  end

  test "get_weeds(): params" do
    expect(FarmbotOS.Asset, :get_all_points_by_type, 1, fn "Weed" ->
      [
        %{
          id: 1,
          x: 10,
          y: 20,
          radius: 5,
          plant_stage: "active",
          created_at: ~U[2023-01-01 00:00:00Z]
        },
        %{
          id: 2,
          x: 30,
          y: 40,
          radius: 10,
          plant_stage: "pending",
          created_at: ~U[2023-01-01 00:00:00Z]
        }
      ]
    end)

    lua_code =
      "return get_weeds{ plant_stage = \"active\", min_radius = 5, max_radius = 6 }"

    {:ok, [actual]} = lua(lua_code, lua_code)
    [{1, weed_data}] = actual

    assert Enum.any?(weed_data, fn
             {"age", age} -> is_integer(age)
             _ -> false
           end)

    assert Enum.any?(weed_data, fn
             {"id", id} -> id == 1
             _ -> false
           end)

    assert Enum.any?(weed_data, fn
             {"plant_stage", stage} -> stage == "active"
             _ -> false
           end)

    assert Enum.any?(weed_data, fn
             {"radius", radius} -> radius == 5
             _ -> false
           end)

    assert Enum.any?(weed_data, fn
             {"x", x} -> x == 10
             _ -> false
           end)

    assert Enum.any?(weed_data, fn
             {"y", y} -> y == 20
             _ -> false
           end)
  end

  test "get_plants()" do
    expect(FarmbotOS.Asset, :get_all_points_by_type, 1, fn "Plant" ->
      [
        %{
          id: 1,
          x: 10,
          y: 20,
          plant_stage: "planted",
          planted_at: ~U[2023-01-01 00:00:00Z]
        },
        %{
          id: 2,
          x: 30,
          y: 40,
          plant_stage: "planned",
          planted_at: ~U[2023-01-01 00:00:00Z]
        }
      ]
    end)

    lua_code = "return get_plants()"

    {:ok, [actual]} = lua(lua_code, lua_code)
    [{1, plant_data}] = actual

    assert Enum.any?(plant_data, fn
             {"age", age} -> is_integer(age)
             _ -> false
           end)

    assert Enum.any?(plant_data, fn
             {"id", id} -> id == 1
             _ -> false
           end)

    assert Enum.any?(plant_data, fn
             {"plant_stage", stage} -> stage == "planted"
             _ -> false
           end)

    assert Enum.any?(plant_data, fn
             {"x", x} -> x == 10
             _ -> false
           end)

    assert Enum.any?(plant_data, fn
             {"y", y} -> y == 20
             _ -> false
           end)
  end

  test "get_plants(): params" do
    expect(FarmbotOS.Asset, :get_all_points_by_type, 1, fn "Plant" ->
      [
        %{
          id: 1,
          x: 10,
          y: 20,
          plant_stage: "planted",
          openfarm_slug: "mint",
          planted_at: ~U[2023-01-01 00:00:00Z]
        },
        %{
          id: 2,
          x: 30,
          y: 40,
          plant_stage: "planned",
          openfarm_slug: "mint",
          planted_at: nil
        }
      ]
    end)

    lua_code =
      "return get_plants{ plant_stage = \"planned\", openfarm_slug = \"mint\" }"

    {:ok, [actual]} = lua(lua_code, lua_code)
    [{1, plant_data}] = actual

    assert Enum.any?(plant_data, fn
             {"id", id} -> id == 2
             _ -> false
           end)

    assert Enum.any?(plant_data, fn
             {"plant_stage", stage} -> stage == "planned"
             _ -> false
           end)

    assert Enum.any?(plant_data, fn
             {"openfarm_slug", slug} -> slug == "mint"
             _ -> false
           end)

    assert Enum.any?(plant_data, fn
             {"x", x} -> x == 30
             _ -> false
           end)

    assert Enum.any?(plant_data, fn
             {"y", y} -> y == 40
             _ -> false
           end)
  end

  test "get_generic_points()" do
    expect(FarmbotOS.Asset, :get_all_points_by_type, 1, fn "GenericPointer" ->
      [
        %{
          id: 1,
          x: 10,
          y: 20,
          created_at: ~U[2023-01-01 00:00:00Z],
          updated_at: ~U[2023-01-01 00:00:00Z]
        }
      ]
    end)

    lua_code = "return get_generic_points()"

    {:ok, [actual]} = lua(lua_code, lua_code)
    [{1, point_data}] = actual

    assert Enum.any?(point_data, fn
             {"age", age} -> is_integer(age)
             _ -> false
           end)

    assert Enum.any?(point_data, fn
             {"created_at", created_at} -> is_binary(created_at)
             _ -> false
           end)

    assert Enum.any?(point_data, fn
             {"updated_at", updated_at} -> is_binary(updated_at)
             _ -> false
           end)

    assert Enum.any?(point_data, fn
             {"id", id} -> id == 1
             _ -> false
           end)

    assert Enum.any?(point_data, fn
             {"x", x} -> x == 10
             _ -> false
           end)

    assert Enum.any?(point_data, fn
             {"y", y} -> y == 20
             _ -> false
           end)
  end

  test "get_generic_points(): params" do
    expect(FarmbotOS.Asset, :get_all_points_by_type, 1, fn "GenericPointer" ->
      [
        %{
          id: 1,
          x: 10,
          y: 20,
          meta: %{"color" => "green"},
          created_at: ~U[2023-01-01 00:00:00Z],
          updated_at: ~U[2023-01-01 00:00:00Z]
        },
        %{
          id: 2,
          x: 30,
          y: 40,
          meta: %{"color" => "red"},
          created_at: ~U[2023-01-01 00:00:00Z],
          updated_at: ~U[2023-01-01 00:00:00Z]
        }
      ]
    end)

    lua_code =
      "return get_generic_points{ color = \"green\" }"

    {:ok, [actual]} = lua(lua_code, lua_code)
    [{1, point_data}] = actual

    assert Enum.any?(point_data, fn
             {"age", age} -> is_integer(age)
             _ -> false
           end)

    assert Enum.any?(point_data, fn
             {"id", id} -> id == 1
             _ -> false
           end)

    assert Enum.any?(point_data, fn
             {"x", x} -> x == 10
             _ -> false
           end)

    assert Enum.any?(point_data, fn
             {"y", y} -> y == 20
             _ -> false
           end)
  end

  @generic_points [
    %{
      id: 1,
      x: 10,
      y: 20,
      meta: %{"at_soil_level" => "true"},
      created_at: ~U[2023-01-01 00:00:00Z],
      updated_at: ~U[2023-01-01 00:00:00Z]
    },
    %{
      id: 2,
      x: 30,
      y: 40,
      meta: %{"color" => "red"},
      created_at: ~U[2023-01-01 00:00:00Z],
      updated_at: ~U[2023-01-01 00:00:00Z]
    },
    %{
      id: 3,
      x: 30,
      y: 40,
      meta: %{"at_soil_level" => "false"},
      created_at: ~U[2023-01-01 00:00:00Z],
      updated_at: ~U[2023-01-01 00:00:00Z]
    }
  ]

  test "get_generic_points(): at_soil_level false" do
    expect(FarmbotOS.Asset, :get_all_points_by_type, 1, fn "GenericPointer" ->
      @generic_points
    end)

    lua_code = "return get_generic_points{ at_soil_level = \"false\" }"
    {:ok, [actual]} = lua(lua_code, lua_code)

    ids =
      actual
      |> Enum.flat_map(fn {_, data} ->
        for {"id", id} <- List.flatten(data), do: id
      end)

    assert Enum.sort(ids) == [2, 3]
  end

  test "get_generic_points(): at_soil_level true" do
    expect(FarmbotOS.Asset, :get_all_points_by_type, 1, fn "GenericPointer" ->
      @generic_points
    end)

    lua_code = "return get_generic_points{ at_soil_level = \"true\" }"
    {:ok, [actual]} = lua(lua_code, lua_code)

    ids =
      actual
      |> Enum.flat_map(fn {_, data} ->
        for {"id", id} <- List.flatten(data), do: id
      end)

    assert Enum.sort(ids) == [1]
  end

  test "get_generic_points(): at_soil_level nil" do
    expect(FarmbotOS.Asset, :get_all_points_by_type, 1, fn "GenericPointer" ->
      @generic_points
    end)

    lua_code = "return get_generic_points{}"
    {:ok, [actual]} = lua(lua_code, lua_code)

    ids =
      actual
      |> Enum.flat_map(fn {_, data} ->
        for {"id", id} <- List.flatten(data), do: id
      end)

    assert Enum.sort(ids) == [1, 2, 3]
  end

  test "new_sensor_reading" do
    expect(FarmbotOS.Asset, :new_sensor_reading!, 1, fn params ->
      expected = %{
        "mode" => 1,
        "pin" => 0,
        "value" => 2,
        "x" => 3.0,
        "y" => 4.0,
        "z" => 5.0
      }

      assert expected == params
    end)

    lua_code = """
    return new_sensor_reading({
      mode = 1.1,
      pin = 0.1,
      value = 2.1,
      x = 3.0,
      y = 4.0,
      z = 5.0,
    })
    """

    assert {:ok, [true]} == lua("new_sensor_reading/1", lua_code)
  end

  test "group" do
    expect(FarmbotOS.Asset, :find_points_via_group, fn 1 ->
      %{point_ids: [1, 2, 3]}
    end)

    assert {[[1, 2, 3]], :lua} == DataManipulation.group([1], :lua)
  end

  test "group - no group" do
    expect(FarmbotOS.Asset, :find_points_via_group, fn 1 -> nil end)
    assert {[[]], :lua} == DataManipulation.group([1], :lua)
  end

  test "get_group()" do
    expect(FarmbotOS.Asset.Repo, :get_by, fn FarmbotOS.Asset.PointGroup,
                                             id: 1 ->
      %{point_ids: []}
    end)

    assert {[[]], :lua} == DataManipulation.get_group([1], :lua)
  end

  test "get_group(): no group" do
    expect(FarmbotOS.Asset.Repo, :get_by, fn FarmbotOS.Asset.PointGroup,
                                             id: 1 ->
      nil
    end)

    assert {[[]], :lua} == DataManipulation.get_group([1], :lua)
  end

  test "sort: points" do
    assert {[%{1 => %{id: 1, name: "p", y: 1, x: 1}}], :lua} ==
             DataManipulation.sort(
               [
                 [{1, [{"id", 1}, {"name", "p"}, {"x", 1}, {"y", 1}]}],
                 "random"
               ],
               :lua
             )
  end

  test "sort: point_ids" do
    expect(FarmbotOS.Asset, :get_point, fn _ ->
      %FarmbotOS.Asset.Point{id: 1}
    end)

    assert {[%{1 => 1}], :lua} ==
             DataManipulation.sort([[{1, 1}], "random"], :lua)
  end

  test "take_photo - OK" do
    expect(FarmbotOS.SysCalls.Farmware, :execute_script, fn "take-photo", env ->
      assert env == %{}
      :ok
    end)

    name = "take_photo() OK"
    code = "return take_photo()"
    assert {:ok, []} == lua(name, code)
  end

  test "take_photo - 'normal' errors" do
    expect(FarmbotOS.SysCalls.Farmware, :execute_script, fn "take-photo", env ->
      assert env == %{}
      {:error, "whatever"}
    end)

    name = "take_photo() OK"
    code = "return take_photo()"
    assert {:ok, ["whatever"]} == lua(name, code)
  end

  test "take_photo - malformed errors" do
    expect(FarmbotOS.SysCalls.Farmware, :execute_script, fn "take-photo", env ->
      assert env == %{}
      {:something_else, "whoops"}
    end)

    name = "take_photo() OK"
    code = "return take_photo()"
    assert {:ok, [inspect({:something_else, "whoops"})]} == lua(name, code)
  end

  test "take_photo() - size" do
    expect(
      FarmbotOS.SysCalls.Farmware,
      :execute_script,
      fn "take-photo",
         %{
           "take_photo_width" => "100",
           "take_photo_height" => "100"
         } ->
        :ok
      end
    )

    name = "take_photo() OK"
    code = "return take_photo(100, 100)"
    assert {:ok, []} == lua(name, code)
  end

  test "take_photo() - args" do
    expect(
      FarmbotOS.SysCalls.Farmware,
      :execute_script,
      fn "take-photo",
         %{
           "take_photo_args" => "[\"-s\",\"brightness=100%\"]"
         } ->
        :ok
      end
    )

    name = "take_photo() OK"
    code = "return take_photo({\"-s\", \"brightness=100%\"})"
    assert {:ok, []} == lua(name, code)
  end

  test "take_photo() - size and args" do
    expect(
      FarmbotOS.SysCalls.Farmware,
      :execute_script,
      fn "take-photo",
         %{
           "take_photo_width" => "100",
           "take_photo_height" => "100",
           "take_photo_args" => "[\"-s\",\"brightness=100%\"]"
         } ->
        :ok
      end
    )

    name = "take_photo() OK"
    code = "return take_photo(100, 100, {\"-s\", \"brightness=100%\"})"
    assert {:ok, []} == lua(name, code)
  end

  test "json_decode - OK" do
    actual = DataManipulation.json_decode(["{\"foo\":\"bar\"}"], :lua)
    assert {[[{"foo", "bar"}]], :lua} == actual
  end

  test "json_decode - Error" do
    actual = DataManipulation.json_decode(["no"], :lua)
    assert {[nil, "Error parsing JSON."], :lua} == actual
  end

  test "json_encode - OK" do
    actual = DataManipulation.json_encode([[{"foo", "bar"}]], :lua)
    assert {["{\"foo\":\"bar\"}"], :lua} == actual
  end

  test "env/1" do
    mock = fn -> [%__MODULE__{key: "foo", value: "bar"}] end
    expect(FarmbotOS.Asset, :list_farmware_env, 2, mock)
    results = DataManipulation.env(["foo"], :lua)
    assert {["bar"], :lua} == results
    results = DataManipulation.env(["wrong"], :lua)
    assert {[nil], :lua} == results
  end

  test "env/2 - OK" do
    mock = fn _, _ -> :ok end
    expect(FarmbotOS.SysCalls, :set_user_env, 1, mock)
    results = DataManipulation.env(["foo", "bar"], :lua)
    assert {["bar"], :lua} == results
  end

  test "env/2 - normal error" do
    mock = fn _, _ -> {:error, "reason"} end
    expect(FarmbotOS.SysCalls, :set_user_env, 1, mock)
    results = DataManipulation.env(["foo", "bar"], :lua)
    assert {[nil, "reason"], :lua} == results
  end

  test "env/2 - malformed error" do
    mock = fn _, _ -> :misc end
    expect(FarmbotOS.SysCalls, :set_user_env, 1, mock)
    results = DataManipulation.env(["foo", "bar"], :lua)
    assert {[nil, ":misc"], :lua} == results
  end

  defmodule FakeHackney do
    def request(_method, _url, _headers, _body, _options) do
      resp_headers = [
        {"Access-Control-Allow-Origin", "*"},
        {"Content-Length", "33"},
        {"Content-Type", "application/json; charset=utf-8"}
      ]

      {:ok, 200, resp_headers, make_ref()}
    end

    def body(_ref) do
      {:ok, "{\"whatever\": \"foo_bar_baz\"}"}
    end
  end

  test "http" do
    expect(FarmbotOS.HTTP, :hackney, 1, fn -> FakeHackney end)

    params =
      FarmbotOS.Lua.Util.map_to_table(%{
        "url" => "http://localhost:4567",
        "method" => "POST",
        "headers" => %{"foo" => "bar"},
        "body" => "{\"one\":\"two\"}"
      })

    expected = {
      [
        [
          {"status", 200},
          {"body", "{\"whatever\": \"foo_bar_baz\"}"},
          {"headers",
           [
             {"Access-Control-Allow-Origin", "*"},
             {"Content-Length", "33"},
             {"Content-Type", "application/json; charset=utf-8"}
           ]}
        ]
      ],
      :fake_lua
    }

    results = DataManipulation.http([params], :fake_lua)
    assert results == expected
  end

  @photo_grid_code File.read!("#{:code.priv_dir(:farmbot)}/lua/photo_grid.lua")

  test "api(args, lua)" do
    expect(FarmbotOS.Lua, :raw_eval, 1, fn _, _ -> {:ok, [:result]} end)
    result = DataManipulation.api([], :fake_lua)
    assert result == {[:result], :fake_lua}
  end

  test "rpc(args, lua)" do
    expect(FarmbotOS.Lua, :raw_eval, 1, fn _, _ -> {:ok, [:result]} end)
    result = DataManipulation.rpc([], :fake_lua)
    assert result == {[:result], :fake_lua}
  end

  test "sequence(args, lua)" do
    expect(FarmbotOS.Lua, :raw_eval, 1, fn _, _ -> {:ok, [:result]} end)
    result = DataManipulation.sequence([], :fake_lua)
    assert result == {[:result], :fake_lua}
  end

  test "verify_tool(args, lua)" do
    expect(FarmbotOS.Lua, :raw_eval, 1, fn _, _ -> {:ok, [:result]} end)
    result = DataManipulation.verify_tool([], :fake_lua)
    assert result == {[:result], :fake_lua}
  end

  test "get_curve(args, lua)" do
    expect(FarmbotOS.Lua, :raw_eval, 1, fn _, _ -> {:ok, [:result]} end)
    result = DataManipulation.get_curve([], :fake_lua)
    assert result == {[:result], :fake_lua}
  end

  test "dispense(args, lua)" do
    expect(FarmbotOS.Lua, :raw_eval, 1, fn _, _ -> {:ok, [:result]} end)
    result = DataManipulation.dispense([], :fake_lua)
    assert result == {[:result], :fake_lua}
  end

  test "water(args, lua)" do
    expect(FarmbotOS.Lua, :raw_eval, 1, fn _, _ -> {:ok, [:result]} end)
    result = DataManipulation.water([], :fake_lua)
    assert result == {[:result], :fake_lua}
  end

  test "grid(args, lua)" do
    expect(FarmbotOS.Lua, :raw_eval, 1, fn _, _ -> {:ok, [:result]} end)
    result = DataManipulation.grid([], :fake_lua)
    assert result == {[:result], :fake_lua}
  end

  test "wait(args, lua)" do
    expect(FarmbotOS.Lua, :raw_eval, 1, fn _, _ -> {:ok, [:result]} end)
    result = DataManipulation.wait([], :fake_lua)
    assert result == {[:result], :fake_lua}
  end

  test "get_seed_tray_cell(args, lua)" do
    expect(FarmbotOS.Lua, :raw_eval, 1, fn _, _ -> {:ok, [:result]} end)
    result = DataManipulation.get_seed_tray_cell([], :fake_lua)
    assert result == {[:result], :fake_lua}
  end

  test "photo_grid() - OK" do
    expect(FarmbotOS.Lua, :raw_eval, 1, fn lua_state, lua_code ->
      assert lua_code == @photo_grid_code
      assert lua_state == :fake_lua
      {:ok, [:result]}
    end)

    result = DataManipulation.photo_grid([], :fake_lua)
    assert result == {[:result], :fake_lua}
  end

  test "photo_grid() - KO" do
    expect(FarmbotOS.Lua, :raw_eval, 1, fn lua_state, lua_code ->
      assert lua_code == @photo_grid_code
      assert lua_state == :fake_lua
      {:error, :error_result}
    end)

    result = DataManipulation.photo_grid([], :fake_lua)
    assert result == {[nil, "ERROR: {:error, :error_result}"], :fake_lua}
  end
end
