defmodule Towerops.Preseem.FleetIntelligence do
  @moduledoc """
  Computes fleet-wide model performance profiles by grouping matched devices
  by manufacturer and model.
  """
  import Ecto.Query

  alias Towerops.Preseem.AccessPoint
  alias Towerops.Preseem.FleetProfile
  alias Towerops.Repo

  @doc "Compute fleet profiles for all matched APs in an organization."
  def compute_fleet_profiles(organization_id) do
    now = DateTime.truncate(DateTime.utc_now(), :second)

    matched_aps =
      AccessPoint
      |> where(organization_id: ^organization_id)
      |> where([ap], not is_nil(ap.device_id))
      |> where([ap], not is_nil(ap.model))
      |> Repo.all()

    groups =
      Enum.group_by(matched_aps, fn ap ->
        {extract_manufacturer(ap.model), ap.model}
      end)

    count =
      Enum.reduce(groups, 0, fn {{manufacturer, model}, aps}, acc ->
        profile_attrs = compute_model_profile(manufacturer, model, aps, now)
        upsert_fleet_profile(organization_id, profile_attrs)
        acc + 1
      end)

    {:ok, count}
  end

  defp extract_manufacturer(model) when is_binary(model) do
    case String.split(model, " ", parts: 2) do
      [manufacturer, _rest] -> manufacturer
      [single] -> single
    end
  end

  defp compute_model_profile(manufacturer, model, aps, now) do
    subscriber_counts = aps |> Enum.map(& &1.subscriber_count) |> Enum.reject(&is_nil/1)
    qoe_scores = aps |> Enum.map(& &1.qoe_score) |> Enum.reject(&is_nil/1)
    capacity_scores = aps |> Enum.map(& &1.capacity_score) |> Enum.reject(&is_nil/1)
    busy_hours = aps |> Enum.map(& &1.busy_hours) |> Enum.reject(&is_nil/1)

    %{
      manufacturer: manufacturer,
      model: model,
      firmware_version: nil,
      device_count: length(aps),
      avg_subscribers: safe_avg(subscriber_counts),
      avg_qoe_score: safe_avg(qoe_scores),
      avg_capacity_score: safe_avg(capacity_scores),
      capacity_ceiling: compute_capacity_ceiling(aps),
      avg_busy_hours: safe_avg(busy_hours),
      performance_data: %{
        "subscriber_range" => %{
          "min" => Enum.min(subscriber_counts, fn -> nil end),
          "max" => Enum.max(subscriber_counts, fn -> nil end)
        },
        "firmware_distribution" => aps |> Enum.map(& &1.firmware) |> Enum.reject(&is_nil/1) |> Enum.frequencies()
      },
      computed_at: now
    }
  end

  defp safe_avg([]), do: nil
  defp safe_avg(values), do: Float.round(Enum.sum(values) / length(values), 2)

  defp compute_capacity_ceiling(aps) do
    aps
    |> Enum.filter(fn ap ->
      ap.qoe_score != nil and ap.subscriber_count != nil and ap.qoe_score >= 70
    end)
    |> Enum.map(& &1.subscriber_count)
    |> Enum.max(fn -> nil end)
  end

  # Use a manual query + update/insert since the unique index includes nullable firmware_version.
  # PostgreSQL treats NULLs as distinct in unique indexes, so ON CONFLICT won't match
  # rows with NULL firmware_version. Ecto's get_by also rejects nil values.
  defp upsert_fleet_profile(organization_id, attrs) do
    full_attrs = Map.put(attrs, :organization_id, organization_id)

    existing =
      FleetProfile
      |> where(organization_id: ^organization_id)
      |> where(manufacturer: ^attrs.manufacturer)
      |> where(model: ^attrs.model)
      |> where([fp], is_nil(fp.firmware_version))
      |> Repo.one()

    case existing do
      nil ->
        %FleetProfile{}
        |> FleetProfile.changeset(full_attrs)
        |> Repo.insert!()

      profile ->
        profile
        |> FleetProfile.changeset(full_attrs)
        |> Repo.update!()
    end
  end
end
