defmodule Towerops.Sites.Site do
  @moduledoc """
  Site schema representing physical locations within an organization.

  Sites contain devices and can have site-level SNMP configuration defaults.
  """
  use Ecto.Schema

  import Ecto.Changeset

  alias Ecto.Association.NotLoaded
  alias Towerops.Agents.AgentToken
  alias Towerops.Devices.Device
  alias Towerops.Organizations.Organization
  alias Towerops.Sites.Site

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  schema "sites" do
    field :name, :string
    field :description, :string
    field :location, :string

    # SNMP configuration (overrides organization default)
    field :snmp_version, :string
    field :snmp_community, :string

    belongs_to :organization, Organization
    belongs_to :agent_token, AgentToken
    belongs_to :parent_site, Site
    has_many :child_sites, Site, foreign_key: :parent_site_id
    has_many :device, Device

    timestamps(type: :utc_datetime)
  end

  @type t :: %__MODULE__{
          id: Ecto.UUID.t(),
          name: String.t(),
          description: String.t() | nil,
          location: String.t() | nil,
          snmp_version: String.t() | nil,
          snmp_community: String.t() | nil,
          organization_id: Ecto.UUID.t(),
          organization: NotLoaded.t() | Organization.t(),
          agent_token_id: Ecto.UUID.t() | nil,
          agent_token: NotLoaded.t() | AgentToken.t() | nil,
          parent_site_id: Ecto.UUID.t() | nil,
          parent_site: NotLoaded.t() | t() | nil,
          child_sites: NotLoaded.t() | [t()],
          device: NotLoaded.t() | [Device.t()],
          inserted_at: DateTime.t(),
          updated_at: DateTime.t()
        }

  @doc false
  def changeset(site, attrs) do
    site
    |> cast(attrs, [
      :name,
      :description,
      :location,
      :organization_id,
      :agent_token_id,
      :parent_site_id,
      :snmp_version,
      :snmp_community
    ])
    |> validate_required([:name, :organization_id])
    |> validate_length(:name, min: 2, max: 200)
    |> validate_length(:description, max: 1000)
    |> validate_length(:location, max: 200)
    |> validate_inclusion(:snmp_version, ["1", "2c", "3"], message: "must be 1, 2c, or 3")
    |> foreign_key_constraint(:organization_id)
    |> foreign_key_constraint(:agent_token_id)
    |> foreign_key_constraint(:parent_site_id)
    |> validate_not_circular_parent()
  end

  defp validate_not_circular_parent(changeset) do
    parent_site_id = get_change(changeset, :parent_site_id)
    site_id = get_field(changeset, :id)

    if parent_site_id && site_id && parent_site_id == site_id do
      add_error(changeset, :parent_site_id, "cannot be the same as the site itself")
    else
      changeset
    end
  end
end
