defmodule Towerops.Snmp.Device do
  @moduledoc """
  SNMP device schema containing discovered device information.

  Stores system details (sysDescr, sysObjectID), vendor information,
  and relationships to interfaces, sensors, and neighbors.
  """
  use Ecto.Schema

  import Ecto.Changeset

  alias Ecto.Association.NotLoaded
  alias Towerops.Devices.Device, as: DeviceSchema
  alias Towerops.Snmp.Interface
  alias Towerops.Snmp.Processor
  alias Towerops.Snmp.Sensor
  alias Towerops.Snmp.StateSensor
  alias Towerops.Snmp.Storage

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  schema "snmp_devices" do
    field :sys_descr, :string
    field :sys_object_id, :string
    field :sys_name, :string
    field :sys_uptime, :integer
    field :sys_contact, :string
    field :sys_location, :string
    field :manufacturer, :string
    field :model, :string
    field :firmware_version, :string
    field :serial_number, :string
    field :raw_discovery_data, :map
    field :last_discovery_at, :utc_datetime

    belongs_to :device, DeviceSchema

    has_many :interfaces, Interface, foreign_key: :snmp_device_id
    has_many :processors, Processor, foreign_key: :snmp_device_id
    has_many :sensors, Sensor, foreign_key: :snmp_device_id
    has_many :state_sensors, StateSensor, foreign_key: :snmp_device_id
    has_many :storage, Storage, foreign_key: :snmp_device_id

    timestamps(type: :utc_datetime)
  end

  @type t :: %__MODULE__{
          id: Ecto.UUID.t(),
          sys_descr: String.t() | nil,
          sys_object_id: String.t() | nil,
          sys_name: String.t() | nil,
          sys_uptime: integer() | nil,
          sys_contact: String.t() | nil,
          sys_location: String.t() | nil,
          manufacturer: String.t() | nil,
          model: String.t() | nil,
          firmware_version: String.t() | nil,
          serial_number: String.t() | nil,
          raw_discovery_data: map() | nil,
          last_discovery_at: DateTime.t() | nil,
          device_id: Ecto.UUID.t(),
          device: NotLoaded.t() | DeviceSchema.t(),
          interfaces: NotLoaded.t() | [Interface.t()],
          processors: NotLoaded.t() | [Processor.t()],
          sensors: NotLoaded.t() | [Sensor.t()],
          state_sensors: NotLoaded.t() | [StateSensor.t()],
          storage: NotLoaded.t() | [Storage.t()],
          inserted_at: DateTime.t(),
          updated_at: DateTime.t()
        }

  @doc false
  def changeset(device, attrs) do
    device
    |> cast(attrs, [
      :device_id,
      :sys_descr,
      :sys_object_id,
      :sys_name,
      :sys_uptime,
      :sys_contact,
      :sys_location,
      :manufacturer,
      :model,
      :firmware_version,
      :serial_number,
      :raw_discovery_data,
      :last_discovery_at
    ])
    |> validate_required([:device_id])
    |> unique_constraint(:device_id)
    |> foreign_key_constraint(:device_id)
  end
end
