defmodule Towerops.Organizations.Invitation do
  @moduledoc """
  Invitation schema for inviting users to join an organization.

  Tracks invitation tokens, email, role, expiration, and acceptance status.
  """
  use Ecto.Schema

  import Ecto.Changeset

  alias Ecto.Association.NotLoaded
  alias Towerops.Accounts.User
  alias Towerops.Organizations.Organization

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  schema "organization_invitations" do
    field :email, :string
    field :role, Ecto.Enum, values: [:admin, :member, :viewer]
    field :token, :string
    field :accepted_at, :utc_datetime
    field :expires_at, :utc_datetime

    belongs_to :organization, Organization
    belongs_to :invited_by, User
    belongs_to :accepted_by, User

    timestamps(type: :utc_datetime)
  end

  @type t :: %__MODULE__{
          id: Ecto.UUID.t(),
          email: String.t(),
          role: :admin | :member | :viewer,
          token: String.t(),
          accepted_at: DateTime.t() | nil,
          expires_at: DateTime.t(),
          organization_id: Ecto.UUID.t(),
          organization: NotLoaded.t() | Organization.t(),
          invited_by_id: Ecto.UUID.t(),
          invited_by: NotLoaded.t() | User.t(),
          accepted_by_id: Ecto.UUID.t() | nil,
          accepted_by: NotLoaded.t() | User.t() | nil,
          inserted_at: DateTime.t(),
          updated_at: DateTime.t()
        }

  @doc false
  def changeset(invitation, attrs) do
    invitation
    |> cast(attrs, [
      :email,
      :role,
      :organization_id,
      :invited_by_id,
      :token,
      :expires_at,
      :accepted_at
    ])
    |> set_default_role()
    |> validate_required([:email, :role, :organization_id, :invited_by_id])
    |> validate_format(:email, ~r/^[^\s]+@[^\s]+\.[^\s]+$/)
    |> generate_token()
    |> set_expires_at()
    |> validate_required([:token, :expires_at])
    |> unique_constraint(:token)
    |> foreign_key_constraint(:organization_id)
    |> foreign_key_constraint(:invited_by_id)
  end

  defp set_default_role(changeset) do
    # Default to :member role if not specified
    # Only organization creators can be owners, not invited users
    if get_field(changeset, :role) do
      changeset
    else
      put_change(changeset, :role, :member)
    end
  end

  defp generate_token(changeset) do
    # Only generate token if not already provided
    if get_field(changeset, :token) do
      changeset
    else
      token = 32 |> :crypto.strong_rand_bytes() |> Base.url_encode64(padding: false)
      put_change(changeset, :token, token)
    end
  end

  defp set_expires_at(changeset) do
    # Only set expires_at if not already provided
    if get_field(changeset, :expires_at) do
      changeset
    else
      expires_at = DateTime.utc_now() |> DateTime.add(7, :day) |> DateTime.truncate(:second)
      put_change(changeset, :expires_at, expires_at)
    end
  end
end
