defmodule TeslaMate.SettingsTest do
  use TeslaMate.DataCase, async: false

  alias TeslaMate.Settings.{GlobalSettings, CarSettings}
  alias TeslaMate.{Settings, Log}

  import TestHelper, only: [decimal: 1]

  defp car_fixture(attrs \\ %{}) do
    {:ok, car} =
      attrs
      |> Enum.into(%{
        efficiency: 0.153,
        eid: 42,
        model: "S",
        vid: 42,
        name: "foo",
        trim_badging: "P100D",
        vin: "12345F"
      })
      |> Log.create_car()

    car
  end

  describe "global_settings" do
    @update_attrs %{
      unit_of_length: :mi,
      unit_of_temperature: :F,
      preferred_range: :rated,
      base_url: "https://testlamate.exmpale.com",
      grafana_url: "https://grafana.exmpale.com",
      language: "de"
    }
    @invalid_attrs %{
      unit_of_length: nil,
      unit_of_temperature: nil,
      preferred_range: nil,
      base_url: nil,
      grafana_url: nil,
      language: "foo"
    }

    test "get_global_settings!/0 returns the settings" do
      assert settings = Settings.get_global_settings!()
      assert settings.unit_of_length == :km
      assert settings.unit_of_temperature == :C
      assert settings.preferred_range == :rated
      assert settings.base_url == nil
      assert settings.grafana_url == nil
      assert settings.language == "en"
    end

    test "update_global_settings/2 with valid data updates the settings" do
      settings = Settings.get_global_settings!()

      assert {:ok, %GlobalSettings{} = settings} =
               Settings.update_global_settings(settings, @update_attrs)

      assert settings.unit_of_length == :mi
      assert settings.unit_of_temperature == :F
      assert settings.preferred_range == :rated
      assert settings.base_url == "https://testlamate.exmpale.com"
      assert settings.grafana_url == "https://grafana.exmpale.com"
      assert settings.language == "de"
    end

    test "update_global_settings/2 with invalid data returns error changeset" do
      settings = Settings.get_global_settings!()

      assert {:error, %Ecto.Changeset{} = changeset} =
               Settings.update_global_settings(settings, @invalid_attrs)

      assert errors_on(changeset) == %{
               unit_of_length: ["can't be blank"],
               unit_of_temperature: ["can't be blank"],
               preferred_range: ["can't be blank"],
               language: ["is not supported"]
             }

      assert ^settings = Settings.get_global_settings!()
    end

    test "validate the base url" do
      settings = Settings.get_global_settings!()

      assert {:ok, %GlobalSettings{base_url: "http://example.com"}} =
               Settings.update_global_settings(settings, %{base_url: "http://example.com/"})

      assert {:ok, %GlobalSettings{base_url: "http://example.com/foo"}} =
               Settings.update_global_settings(settings, %{base_url: "  http://example.com/foo/ "})

      cases = [
        {"ftp://example", "invalid scheme"},
        {"example.com", "is missing a scheme (e.g. https)"},
        {"https://", "is missing a host"}
      ]

      for {base_url, message} <- cases do
        assert {:error, changeset} =
                 Settings.update_global_settings(settings, %{base_url: base_url})

        assert errors_on(changeset) == %{base_url: [message]}
      end
    end

    test "empty strings become nil" do
      assert {:ok,
              %GlobalSettings{
                base_url: "https://testlamate.exmpale.com",
                grafana_url: "https://grafana.exmpale.com"
              } = settings} =
               Settings.get_global_settings!()
               |> Settings.update_global_settings(@update_attrs)

      assert {:ok, %GlobalSettings{grafana_url: nil, base_url: nil}} =
               Settings.update_global_settings(settings, %{grafana_url: "     ", base_url: ""})
    end
  end

  describe "car settings" do
    @update_attrs %{
      suspend_min: 60,
      suspend_after_idle_min: 60,
      req_not_unlocked: true,
      free_supercharging: true,
      use_streaming_api: false,
      enabled: true,
      lfp_battery: false
    }
    @invalid_attrs %{
      suspend_min: nil,
      suspend_after_idle_min: nil,
      req_not_unlocked: nil,
      free_supercharging: nil,
      use_streaming_api: nil,
      enabled: nil,
      lfp_battery: nil
    }

    test "get_car_settings/0 returns the settings" do
      car = car_fixture()

      assert [settings] = Settings.get_car_settings()
      assert settings.id == car.settings_id
      assert settings.suspend_min == 21
      assert settings.suspend_after_idle_min == 15
      assert settings.req_not_unlocked == false
      assert settings.free_supercharging == false
      assert settings.use_streaming_api == true
      assert settings.enabled == true
      assert settings.lfp_battery == false
    end

    test "update_car_settings/2 with valid data updates the settings" do
      car = car_fixture()
      [settings] = Settings.get_car_settings()

      assert {:ok, %CarSettings{} = settings} =
               Settings.update_car_settings(settings, @update_attrs)

      assert settings.id == car.settings_id
      assert settings.suspend_min == 60
      assert settings.suspend_after_idle_min == 60
      assert settings.req_not_unlocked == true
      assert settings.free_supercharging == true
      assert settings.use_streaming_api == false
      assert settings.enabled == true
      assert settings.lfp_battery == false
    end

    test "update_car_settings/2 publishes the settings" do
      car = car_fixture()
      :ok = Settings.subscribe_to_changes(car)

      assert [settings] = Settings.get_car_settings()

      assert {:ok, %CarSettings{} = settings} =
               Settings.update_car_settings(settings, @update_attrs)

      assert_receive ^settings
    end

    test "update_car_settings/2 with invalid data returns error changeset" do
      _car = car_fixture()
      [settings] = Settings.get_car_settings()

      assert {:error, %Ecto.Changeset{} = changeset} =
               Settings.update_car_settings(settings, @invalid_attrs)

      assert errors_on(changeset) == %{
               req_not_unlocked: ["can't be blank"],
               suspend_after_idle_min: ["can't be blank"],
               suspend_min: ["can't be blank"],
               free_supercharging: ["can't be blank"],
               use_streaming_api: ["can't be blank"],
               enabled: ["can't be blank"],
               lfp_battery: ["can't be blank"]
             }

      assert [^settings] = Settings.get_car_settings()
    end
  end

  describe "language" do
    alias TeslaMate.Locations.Address
    alias TeslaMate.Locations

    test "refreshes all addresses when changfing the language" do
      settings = Settings.get_global_settings!()

      {:ok, %Address{}} =
        Locations.create_address(%{
          display_name: "foo",
          name: "0",
          latitude: 0,
          longitude: 0,
          state: "Berlin",
          country: "Germany",
          osm_id: 0,
          osm_type: "way",
          raw: %{}
        })

      {:ok, %Address{}} =
        Locations.create_address(%{
          display_name: "bar",
          name: "1",
          latitude: 0,
          longitude: 0,
          state: "Berlin",
          country: "Germany",
          osm_id: 1,
          osm_type: "way",
          raw: %{}
        })

      assert {:ok, _} = Settings.update_global_settings(settings, %{language: "nl"})

      assert [
               %Address{
                 name: "0",
                 state: "Berlin_nl",
                 country: "nl",
                 latitude: decimal("0.000000"),
                 longitude: decimal("0.000000")
               },
               %Address{
                 name: "1",
                 state: "Berlin_nl",
                 country: "nl",
                 latitude: decimal("0.000000"),
                 longitude: decimal("0.000000")
               }
             ] = Repo.all(from a in Address, order_by: 1)
    end

    @tag :capture_log
    test "returns error tuple" do
      settings = Settings.get_global_settings!()

      {:ok, %Address{} = a0} =
        Locations.create_address(%{
          display_name: "foo",
          name: "0",
          latitude: 0,
          longitude: 0,
          state: "Berlin",
          country: "Germany",
          osm_id: 0,
          osm_type: "way",
          raw: %{}
        })

      {:ok, %Address{} = a1} =
        Locations.create_address(%{
          display_name: "error",
          name: "1",
          latitude: 0,
          longitude: 0,
          state: "Berlin",
          country: "Germany",
          osm_id: 1,
          osm_type: "way",
          raw: %{}
        })

      assert {:error, :boom} = Settings.update_global_settings(settings, %{language: "nl"})

      zero = Decimal.new("0.000000")
      assert [r0, r1] = Repo.all(from a in Address, order_by: 1)
      assert r0 == %Address{a0 | latitude: zero, longitude: zero}
      assert r1 == %Address{a1 | latitude: zero, longitude: zero}
    end
  end

  describe "efficiencies" do
    alias TeslaMate.Log.{Car, ChargingProcess, Position}
    alias TeslaMate.Log

    test "triggers a recalculaten of efficiencies if the preferred range changes" do
      %Car{efficiency: nil} = car = car_fixture(%{efficiency: nil})

      data = [
        {293.9, 293.9, nil, nil, 0.0, 59, 59, 0},
        {293.2, 303.4, nil, nil, 1.65, 59, 61, 33},
        {302.5, 302.5, nil, nil, 0.0, 61, 61, 0},
        {302.5, 302.5, nil, nil, 0.0, 61, 61, 0},
        {302.1, 309.5, nil, nil, 1.14, 61, 62, 23},
        {71.9, 350.5, nil, nil, 42.21, 14, 70, 27},
        {181.0, 484.0, nil, nil, 46.13, 36, 97, 46},
        {312.3, 324.9, nil, nil, 1.75, 63, 65, 6},
        {325.6, 482.7, nil, nil, 23.71, 65, 97, 34},
        {80.5, 412.4, nil, nil, 50.63, 16, 83, 70},
        {259.7, 426.2, nil, nil, 25.56, 52, 85, 36},
        {105.5, 361.4, nil, nil, 38.96, 21, 72, 22},
        {143.1, 282.5, nil, nil, 21.11, 29, 57, 15},
        {111.6, 406.9, nil, nil, 44.93, 22, 82, 36},
        {115.0, 453.2, nil, nil, 51.49, 23, 91, 38},
        {112.5, 112.5, 111.5, 112.5, 0.0, 23, 23, 1},
        {109.7, 139.7, 108.7, 139.7, 4.57, 22, 28, 26},
        {63.9, 142.3, 64.9, 142.3, 11.82, 13, 29, 221},
        {107.9, 450.1, 108.9, 450.1, 52.1, 22, 90, 40}
      ]

      :ok = insert_charging_processes(car, data)
      settings = Settings.get_global_settings!()

      # no change
      assert {:ok, settings} =
               Settings.update_global_settings(settings, %{preferred_range: :rated})

      assert %Car{efficiency: nil} = Log.get_car!(car.id)

      # changed
      assert {:ok, settings} =
               Settings.update_global_settings(settings, %{preferred_range: :ideal})

      assert %Car{efficiency: 0.152} = Log.get_car!(car.id)

      # changed back
      assert {:ok, _settings} =
               Settings.update_global_settings(settings, %{preferred_range: :rated})

      assert %Car{efficiency: 0.15} = Log.get_car!(car.id)
    end

    @valid_pos_attrs %{date: DateTime.utc_now(), latitude: 0.0, longitude: 0.0}

    defp insert_charging_processes(car, data) do
      {:ok, %Position{id: position_id}} = Log.insert_position(car, @valid_pos_attrs)

      data =
        for {sir, eir, srr, err, ca, sl, el, d} <- data do
          %{
            car_id: car.id,
            start_date: DateTime.utc_now(),
            position_id: position_id,
            start_ideal_range_km: sir,
            end_ideal_range_km: eir,
            start_rated_range_km: srr,
            end_rated_range_km: err,
            charge_energy_added: ca,
            start_battery_level: sl,
            end_battery_level: el,
            duration_min: d
          }
        end

      {_, nil} = Repo.insert_all(ChargingProcess, data)

      :ok
    end
  end
end
