defmodule Towerops.Snmp.Neighbor do
  @moduledoc """
  SNMP neighbor schema for network topology discovered via LLDP/CDP.

  Stores neighbor information (chassis ID, port ID, system name)
  and links to matched devices in the organization.
  """
  use Ecto.Schema

  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  schema "snmp_neighbors" do
    field :protocol, :string
    field :remote_chassis_id, :string
    field :remote_system_name, :string
    field :remote_system_description, :string
    field :remote_platform, :string
    field :remote_port_id, :string
    field :remote_port_description, :string
    field :remote_address, :string
    field :remote_capabilities, {:array, :string}, default: []
    field :last_discovered_at, :utc_datetime

    belongs_to :device, Towerops.Devices.Device
    belongs_to :interface, Towerops.Snmp.Interface

    timestamps(type: :utc_datetime)
  end

  @doc false
  def changeset(neighbor, attrs) do
    # Ensure UUIDs are properly formatted strings before casting
    attrs =
      attrs
      |> Map.update(:device_id, nil, &normalize_uuid/1)
      |> Map.update(:interface_id, nil, &normalize_uuid/1)

    neighbor
    |> cast(attrs, [
      :device_id,
      :interface_id,
      :protocol,
      :remote_chassis_id,
      :remote_system_name,
      :remote_system_description,
      :remote_platform,
      :remote_port_id,
      :remote_port_description,
      :remote_address,
      :remote_capabilities,
      :last_discovered_at
    ])
    |> validate_required([
      :device_id,
      :interface_id,
      :protocol,
      :last_discovered_at
    ])
    |> validate_inclusion(:protocol, ["lldp", "cdp"])
    |> foreign_key_constraint(:device_id)
    |> foreign_key_constraint(:interface_id)
    |> unique_constraint([:interface_id, :remote_chassis_id, :protocol])
  end

  # Normalize UUID to string format for Ecto casting
  defp normalize_uuid(nil), do: nil

  defp normalize_uuid(uuid) when is_binary(uuid) and byte_size(uuid) == 16 do
    # Convert 16-byte binary UUID to string format
    Ecto.UUID.cast!(uuid)
  end

  defp normalize_uuid(uuid) when is_binary(uuid), do: uuid
  defp normalize_uuid(uuid), do: uuid
end
