defmodule ToweropsWeb.Api.V1.GeoipController do
  @moduledoc """
  API controller for managing GeoIP database imports.

  Allows superusers to trigger GeoIP database imports from MaxMind CSV files.

  All endpoints require superuser API token authentication.
  """

  use ToweropsWeb, :controller

  require Logger

  @doc """
  Import GeoIP database batch from processed CSV data.

  Requires superuser API token.

  ## Request

      POST /admin/api/geoip/import
      Content-Type: application/json

      {
        "batch_type": "locations",  // or "blocks"
        "data": [...],  // Array of location or block records
        "truncate": true  // Optional: truncate table before inserting (first batch only)
      }

  ## Response

      200 OK
      {
        "status": "ok",
        "inserted": 5000
      }
  """
  def import_database(conn, %{"batch_type" => batch_type, "data" => data} = params) do
    with :ok <- require_superuser(conn) do
      truncate = Map.get(params, "truncate", false)

      Logger.info(
        "GeoIP import batch requested by #{conn.assigns[:current_user].email}: #{batch_type}, #{length(data)} records, truncate=#{truncate}"
      )

      case import_batch(batch_type, data, truncate) do
        {:ok, inserted_count} ->
          conn
          |> put_status(:ok)
          |> json(%{
            status: "ok",
            inserted: inserted_count
          })

        {:error, reason} ->
          Logger.error("GeoIP batch import failed: #{inspect(reason)}")

          conn
          |> put_status(:internal_server_error)
          |> json(%{error: "Import failed: #{inspect(reason)}"})
      end
    end
  end

  def import_database(conn, _params) do
    conn
    |> put_status(:bad_request)
    |> json(%{
      error: "Missing required parameters: batch_type and data"
    })
  end

  defp require_superuser(conn) do
    user = conn.assigns[:current_user]

    if !user || !user.is_superuser do
      Logger.warning("GeoIP import attempted by non-superuser: #{inspect(user && user.email)}")

      conn
      |> put_status(:forbidden)
      |> json(%{
        error: "Superuser access required. Only API tokens created by superusers can import GeoIP data."
      })
      |> halt()
    else
      :ok
    end
  end

  defp import_batch("locations", data, truncate) do
    alias Towerops.GeoIP.Block
    alias Towerops.GeoIP.Location
    alias Towerops.Repo

    if truncate do
      # Must delete blocks first due to foreign key constraint
      Repo.delete_all(Block)
      Repo.delete_all(Location)
    end

    now = DateTime.truncate(DateTime.utc_now(), :second)

    entries =
      Enum.map(data, fn location ->
        %{
          geoname_id: location["geoname_id"],
          country_code: location["country_code"],
          country_name: location["country_name"],
          city_name: location["city_name"],
          subdivision_1_name: location["subdivision_1_name"],
          subdivision_2_name: location["subdivision_2_name"],
          latitude: location["latitude"],
          longitude: location["longitude"],
          inserted_at: now,
          updated_at: now
        }
      end)

    # Use upsert to avoid duplicates - update specific fields on conflict
    # Don't use replace_all_except because it triggers DELETE+INSERT which violates foreign keys
    {inserted, _} =
      Repo.insert_all(Location, entries,
        on_conflict:
          {:replace,
           [
             :country_code,
             :country_name,
             :city_name,
             :subdivision_1_name,
             :subdivision_2_name,
             :latitude,
             :longitude,
             :updated_at
           ]},
        conflict_target: :geoname_id
      )

    {:ok, inserted}
  rescue
    e ->
      {:error, Exception.message(e)}
  end

  defp import_batch("blocks", data, truncate) do
    alias Towerops.GeoIP.Block
    alias Towerops.Repo

    if truncate do
      Repo.delete_all(Block)
    end

    now = DateTime.truncate(DateTime.utc_now(), :second)

    entries =
      Enum.map(data, fn block ->
        %{
          id: Ecto.UUID.generate(),
          network: block["network"],
          start_ip_int: block["start_ip_int"],
          end_ip_int: block["end_ip_int"],
          geoname_id: block["geoname_id"],
          registered_country_geoname_id: block["registered_country_geoname_id"],
          inserted_at: now,
          updated_at: now
        }
      end)

    # Use upsert to avoid duplicates - update specific fields on conflict
    {inserted, _} =
      Repo.insert_all(Block, entries,
        on_conflict:
          {:replace,
           [
             :start_ip_int,
             :end_ip_int,
             :geoname_id,
             :registered_country_geoname_id,
             :updated_at
           ]},
        conflict_target: :network
      )

    {:ok, inserted}
  rescue
    e ->
      {:error, Exception.message(e)}
  end

  defp import_batch(_type, _data, _truncate) do
    {:error, "Invalid batch_type. Must be 'locations' or 'blocks'"}
  end
end
