defmodule Towerops.Organizations.Organization do
  @moduledoc """
  Organization schema for multi-tenant workspace management.

  Organizations contain:
  - Members (users with roles)
  - Sites (physical locations)
  - Devices (network equipment)
  - Default SNMP configuration
  - Default agent token for polling
  """
  use Ecto.Schema

  import Ecto.Changeset

  alias Ecto.Association.NotLoaded
  alias Towerops.Agents.AgentToken
  alias Towerops.Organizations.Invitation
  alias Towerops.Organizations.Membership

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  schema "organizations" do
    field :name, :string
    field :slug, :string

    # device)
    field :snmp_version, :string, default: "2c"
    field :snmp_community, :string

    belongs_to :default_agent_token, AgentToken

    has_many :memberships, Membership
    has_many :users, through: [:memberships, :user]
    has_many :invitations, Invitation

    timestamps(type: :utc_datetime)
  end

  @type t :: %__MODULE__{
          id: Ecto.UUID.t(),
          name: String.t(),
          slug: String.t(),
          snmp_version: String.t() | nil,
          snmp_community: String.t() | nil,
          default_agent_token_id: Ecto.UUID.t() | nil,
          default_agent_token: NotLoaded.t() | AgentToken.t() | nil,
          memberships: NotLoaded.t() | [Membership.t()],
          users: NotLoaded.t() | [Towerops.Accounts.User.t()],
          invitations: NotLoaded.t() | [Invitation.t()],
          inserted_at: DateTime.t(),
          updated_at: DateTime.t()
        }

  @doc false
  def changeset(organization, attrs) do
    organization
    |> cast(attrs, [:name, :default_agent_token_id, :snmp_version, :snmp_community])
    |> validate_required([:name])
    |> validate_length(:name, min: 2, max: 100)
    |> validate_inclusion(:snmp_version, ["1", "2c", "3"], message: "must be 1, 2c, or 3")
    |> generate_slug()
    |> validate_required([:slug])
    |> unique_constraint(:slug)
    |> foreign_key_constraint(:default_agent_token_id)
  end

  defp generate_slug(changeset) do
    # Only generate slug if not already set
    if get_field(changeset, :slug) do
      changeset
    else
      # Generate a random, URL-safe slug using base62 encoding
      # 8 characters gives us 62^8 = ~218 trillion possible combinations
      slug =
        6
        |> :crypto.strong_rand_bytes()
        |> Base.encode64(padding: false)
        |> String.replace(~r/[^a-zA-Z0-9]/, "")
        |> String.slice(0..7)
        |> String.downcase()

      put_change(changeset, :slug, slug)
    end
  end
end
