defmodule Statestores.Adapters.MariaDBSnapshotAdapter do
  @moduledoc """
  Implements the behavior defined in `Statestores.Adapters.SnapshotBehaviour` for MariaDB databases.
  """
  use Statestores.Adapters.SnapshotBehaviour

  use Ecto.Repo,
    otp_app: :spawn_statestores,
    adapter: Ecto.Adapters.MyXQL

  alias Statestores.Schemas.{Snapshot, ValueObjectSchema}

  def get_by_key(id), do: get_by(Snapshot, id: id)

  def get_by_key_and_revision(id, revision) do
    query = """
    SELECT *
      FROM snapshots
       FOR SYSTEM_TIME ALL
     WHERE id = #{id}
       AND revision = #{revision}
     ORDER BY inserted_at, updated_at DESC
    """

    %MyXQL.Result{rows: rows} =
      Ecto.Adapters.SQL.query!(Statestores.Adapters.MariaDBSnapshotAdapter, query)

    List.first(rows)
    |> to_snapshot()
    |> case do
      nil ->
        get_by_key(id)

      response ->
        response
    end
  end

  def get_all_snapshots_by_key(id) do
    query = "SELECT * FROM snapshots FOR SYSTEM_TIME ALL WHERE id = #{id}"

    %MyXQL.Result{rows: rows} =
      Ecto.Adapters.SQL.query!(Statestores.Adapters.MariaDBSnapshotAdapter, query)

    rows
    |> Enum.map(&to_snapshot/1)
  end

  def get_snapshots_by_interval(id, time_start, time_end) do
    query = """
    SELECT *
      FROM snapshots
       FOR SYSTEM_TIME BETWEEN '#{time_start}' AND '#{time_end}'
     WHERE id = #{id}
     ORDER BY inserted_at, updated_at ASC
    """

    %MyXQL.Result{rows: rows} =
      Ecto.Adapters.SQL.query!(Statestores.Adapters.MariaDBSnapshotAdapter, query)

    rows
    |> Enum.map(&to_snapshot/1)
  end

  def save(
        %Snapshot{
          system: system,
          actor: actor,
          status: status,
          node: node,
          revision: revision,
          tags: tags,
          data_type: type,
          data: data
        } = event
      ) do
    %Snapshot{}
    |> Snapshot.changeset(ValueObjectSchema.to_map(event))
    |> insert_or_update(
      on_conflict: [
        set: [
          system: system,
          actor: actor,
          status: status,
          node: node,
          revision: revision,
          tags: tags,
          data_type: type,
          data: data,
          updated_at: DateTime.utc_now()
        ]
      ]
    )
    |> case do
      {:ok, event} ->
        {:ok, event}

      {:error, changeset} ->
        {:error, changeset}

      other ->
        {:error, other}
    end
  end

  def default_port, do: "3306"

  defp to_map(json) when is_nil(json) or json == "", do: %{}

  defp to_map(tags) do
    case Jason.decode(tags) do
      {:ok, json} -> json
      _ -> %{}
    end
  end

  defp to_snapshot(nil), do: nil

  defp to_snapshot(row) do
    tags = to_map(Enum.at(row, 6))
    data = Statestores.Vault.decrypt!(Enum.at(row, 8))

    %Snapshot{
      id: Enum.at(row, 0),
      actor: Enum.at(row, 1),
      system: Enum.at(row, 2),
      status: Enum.at(row, 3),
      node: Enum.at(row, 4),
      revision: Enum.at(row, 5),
      tags: tags,
      data_type: Enum.at(row, 7),
      data: data,
      inserted_at: Enum.at(row, 9),
      updated_at: Enum.at(row, 10)
    }
  end
end
