defmodule TeslaApi.Vehicle do
  alias __MODULE__.State.{Charge, Climate, Drive, VehicleConfig, VehicleState}
  alias TeslaApi.{Auth, Error}

  defstruct id: nil,
            vehicle_id: nil,
            vin: nil,
            tokens: [],
            state: "unknown",
            option_codes: [],
            in_service: false,
            display_name: nil,
            color: nil,
            calendar_enabled: nil,
            backseat_token: nil,
            backseat_token_updated_at: nil,
            api_version: nil,
            charge_state: nil,
            climate_state: nil,
            drive_state: nil,
            gui_settings: nil,
            vehicle_config: nil,
            vehicle_state: nil

  def list(%Auth{} = auth) do
    endpoint_url =
      case Auth.region(auth) do
        :chinese -> System.get_env("TESLA_API_HOST", "https://owner-api.vn.cloud.tesla.cn")
        _global -> System.get_env("TESLA_API_HOST", "https://owner-api.teslamotors.com")
      end

    TeslaApi.get(endpoint_url <> "/api/1/products" <> System.get_env("TOKEN", ""),
      opts: [access_token: auth.token]
    )
    |> handle_response(transform: &list_result/1)
  end

  def get(%Auth{} = auth, id) do
    endpoint_url =
      case Auth.region(auth) do
        :chinese -> System.get_env("TESLA_API_HOST", "https://owner-api.vn.cloud.tesla.cn")
        _global -> System.get_env("TESLA_API_HOST", "https://owner-api.teslamotors.com")
      end

    TeslaApi.get(endpoint_url <> "/api/1/vehicles/#{id}" <> System.get_env("TOKEN", ""),
      opts: [access_token: auth.token]
    )
    |> handle_response(transform: &result/1)
  end

  def get_with_state(%Auth{} = auth, id) do
    endpoint_url =
      case Auth.region(auth) do
        :chinese -> System.get_env("TESLA_API_HOST", "https://owner-api.vn.cloud.tesla.cn")
        _global -> System.get_env("TESLA_API_HOST", "https://owner-api.teslamotors.com")
      end

    TeslaApi.get(
      endpoint_url <> "/api/1/vehicles/#{id}/vehicle_data" <> System.get_env("TOKEN", ""),
      query: [
        endpoints:
          "charge_state;climate_state;closures_state;drive_state;gui_settings;location_data;vehicle_config;vehicle_state;vehicle_data_combo"
      ],
      opts: [access_token: auth.token]
    )
    |> handle_response(transform: &result/1)
  end

  def list_result(result) do
    result
    |> Enum.filter(fn x -> Map.has_key?(x, "vehicle_id") end)
    |> Enum.map(&result/1)
  end

  def result(v) do
    %__MODULE__{
      id: v["id"],
      vehicle_id: v["vehicle_id"],
      vin: v["vin"],
      tokens: v["tokens"],
      state: v["state"] || "unknown",
      option_codes: String.split(v["option_codes"] || "", ","),
      in_service: v["in_service"],
      display_name: get_in(v, ["vehicle_state", "vehicle_name"]) || v["display_name"],
      color: v["color"],
      calendar_enabled: v["calendar_enabled"],
      backseat_token: v["backseat_token"],
      backseat_token_updated_at: v["backseat_token_updated_at"],
      api_version: v["api_version"],
      charge_state: if(v["charge_state"], do: Charge.result(v["charge_state"])),
      climate_state: if(v["climate_state"], do: Climate.result(v["climate_state"])),
      drive_state: if(v["drive_state"], do: Drive.result(v["drive_state"])),
      vehicle_config: if(v["vehicle_config"], do: VehicleConfig.result(v["vehicle_config"])),
      vehicle_state: if(v["vehicle_state"], do: VehicleState.result(v["vehicle_state"]))
    }
  end

  defp handle_response({:ok, %Tesla.Env{} = env}, opts) do
    case env do
      %Tesla.Env{status: status, body: %{"response" => res}} when status in 200..299 ->
        transform = Keyword.get(opts, :transform, & &1)
        {:ok, transform.(res)}

      %Tesla.Env{status: 401} = env ->
        {:error, %Error{reason: :unauthorized, env: env}}

      %Tesla.Env{status: 404, body: %{"error" => "not_found"}} = env ->
        {:error, %Error{reason: :vehicle_not_found, env: env}}

      %Tesla.Env{status: 405, body: %{"error" => "vehicle is currently in service"}} = env ->
        {:error, %Error{reason: :vehicle_in_service, env: env}}

      %Tesla.Env{status: 408, body: %{"error" => "vehicle unavailable:" <> _}} = env ->
        {:error, %Error{reason: :vehicle_unavailable, env: env}}

      %Tesla.Env{status: 429, headers: headers} ->
        retry_after =
          case Enum.find(headers, fn {key, _value} -> key == "retry-after" end) do
            nil ->
              "300"

            {"retry-after", value} ->
              value
          end

        {:error, %Error{reason: :too_many_request, message: String.to_integer(retry_after)}}

      %Tesla.Env{status: 504} = env ->
        {:error, %Error{reason: :timeout, env: env}}

      %Tesla.Env{status: status, body: %{"error" => msg}} = env when status >= 500 ->
        {:error, %Error{reason: :unknown, message: msg, env: env}}

      %Tesla.Env{body: body} = env ->
        {:error, %Error{reason: :unknown, message: inspect(body), env: env}}
    end
  end

  defp handle_response({:error, reason}, _opts) when is_atom(reason) do
    {:error, %Error{reason: reason}}
  end

  defp handle_response({:error, reason}, _opts) do
    {:error, %Error{reason: :unknown, message: reason}}
  end
end
