defmodule Towerops.Snmp.PhysicalEntity do
  @moduledoc """
  SNMP physical entity schema for tracking hardware inventory.

  Physical entities are discovered via ENTITY-MIB entPhysicalTable and include:
  - Chassis (main enclosure)
  - Modules (line cards, supervisor modules)
  - Power supplies
  - Fans
  - Sensors
  - Ports

  Entities form a parent/child hierarchy via contained_in relationships.
  """
  use Ecto.Schema

  import Ecto.Changeset

  alias Ecto.Association.NotLoaded
  alias Towerops.Snmp.Device

  @valid_entity_classes ~w(other unknown chassis backplane container power fan sensor module port stack cpu memory)

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  schema "snmp_physical_entities" do
    field :entity_index, :integer
    field :entity_class, :string
    field :description, :string
    field :model_name, :string
    field :serial_number, :string
    field :firmware_revision, :string
    field :hardware_revision, :string
    field :manufacturer_name, :string
    field :position, :integer
    field :last_checked_at, :utc_datetime
    field :metadata, :map, default: %{}

    belongs_to :snmp_device, Device
    belongs_to :parent_entity, __MODULE__
    has_many :child_entities, __MODULE__, foreign_key: :parent_entity_id

    timestamps(type: :utc_datetime)
  end

  @type t :: %__MODULE__{
          id: Ecto.UUID.t(),
          entity_index: integer(),
          entity_class: String.t(),
          description: String.t() | nil,
          model_name: String.t() | nil,
          serial_number: String.t() | nil,
          firmware_revision: String.t() | nil,
          hardware_revision: String.t() | nil,
          manufacturer_name: String.t() | nil,
          position: integer() | nil,
          last_checked_at: DateTime.t() | nil,
          metadata: map(),
          snmp_device_id: Ecto.UUID.t(),
          snmp_device: NotLoaded.t() | Device.t(),
          parent_entity_id: Ecto.UUID.t() | nil,
          parent_entity: NotLoaded.t() | t() | nil,
          child_entities: NotLoaded.t() | [t()],
          inserted_at: DateTime.t(),
          updated_at: DateTime.t()
        }

  @doc false
  def changeset(physical_entity, attrs) do
    physical_entity
    |> cast(attrs, [
      :snmp_device_id,
      :entity_index,
      :entity_class,
      :description,
      :model_name,
      :serial_number,
      :firmware_revision,
      :hardware_revision,
      :manufacturer_name,
      :position,
      :parent_entity_id,
      :last_checked_at,
      :metadata
    ])
    |> validate_required([:snmp_device_id, :entity_index, :entity_class])
    |> validate_inclusion(:entity_class, @valid_entity_classes)
    |> unique_constraint([:snmp_device_id, :entity_index])
    |> foreign_key_constraint(:snmp_device_id)
    |> foreign_key_constraint(:parent_entity_id)
  end
end
