defmodule Mv.MembershipFees.MembershipFeeType do
  @moduledoc """
  Ash resource representing a membership fee type definition.

  ## Overview
  MembershipFeeType defines the different types of membership fees that can be
  assigned to members. Each type has a fixed interval (billing cycle) and a
  default amount.

  ## Attributes
  - `name` - Unique name for the fee type (e.g., "Standard", "Reduced", "Family")
  - `amount` - The fee amount in the default currency (decimal)
  - `interval` - Billing interval: monthly, quarterly, half_yearly, or yearly
  - `description` - Optional description for the fee type

  ## Immutability
  The `interval` field is immutable after creation. This prevents complex
  migration scenarios when changing billing cycles. To change intervals,
  create a new fee type and migrate members.

  ## Relationships
  - `has_many :members` - Members assigned to this fee type
  - `has_many :membership_fee_cycles` - All cycles using this fee type
  """
  use Ash.Resource,
    domain: Mv.MembershipFees,
    data_layer: AshPostgres.DataLayer,
    authorizers: [Ash.Policy.Authorizer]

  postgres do
    table "membership_fee_types"
    repo Mv.Repo
  end

  resource do
    description "Membership fee type definition with interval and amount"
  end

  actions do
    defaults [:read]

    create :create do
      primary? true
      accept [:name, :amount, :interval, :description]
    end

    update :update do
      primary? true
      # require_atomic? false because validation queries (member/cycle counts) are not atomic
      # DB constraints serve as the final safeguard if data changes between validation and update
      require_atomic? false
      # Note: interval is NOT in accept list - it's immutable after creation
      accept [:name, :amount, :description]
    end

    destroy :destroy do
      primary? true

      # require_atomic? false because validation queries (member/cycle/settings counts) are not atomic
      # DB constraints serve as the final safeguard if data changes between validation and delete
      require_atomic? false
    end
  end

  policies do
    policy action_type([:read, :create, :update, :destroy]) do
      description "Check permissions from role (all can read, only admin can create/update/destroy)"
      authorize_if Mv.Authorization.Checks.HasPermission
    end
  end

  validations do
    # Prevent interval changes after creation
    validate fn changeset, _context ->
               if Ash.Changeset.changing_attribute?(changeset, :interval) do
                 case changeset.data do
                   # Creating new resource, interval can be set
                   nil ->
                     :ok

                   _existing ->
                     {:error,
                      field: :interval, message: "Interval cannot be changed after creation"}
                 end
               else
                 :ok
               end
             end,
             on: [:update]

    # Prevent deletion if assigned to members
    validate fn changeset, _context ->
               if changeset.action_type == :destroy do
                 require Ash.Query

                 # Integrity check: count members without authorization (systemic operation)
                 member_count =
                   Mv.Membership.Member
                   |> Ash.Query.filter(membership_fee_type_id == ^changeset.data.id)
                   |> Ash.count!(authorize?: false)

                 if member_count > 0 do
                   {:error,
                    message:
                      "Cannot delete membership fee type: #{member_count} member(s) are assigned to it"}
                 else
                   :ok
                 end
               else
                 :ok
               end
             end,
             on: [:destroy]

    # Prevent deletion if cycles exist
    validate fn changeset, _context ->
               if changeset.action_type == :destroy do
                 require Ash.Query

                 # Integrity check: count cycles without authorization (systemic operation)
                 cycle_count =
                   Mv.MembershipFees.MembershipFeeCycle
                   |> Ash.Query.filter(membership_fee_type_id == ^changeset.data.id)
                   |> Ash.count!(authorize?: false)

                 if cycle_count > 0 do
                   {:error,
                    message:
                      "Cannot delete membership fee type: #{cycle_count} cycle(s) reference it"}
                 else
                   :ok
                 end
               else
                 :ok
               end
             end,
             on: [:destroy]

    # Prevent deletion if used as default in settings
    validate fn changeset, _context ->
               if changeset.action_type == :destroy do
                 require Ash.Query

                 # Integrity check: count settings without authorization (systemic operation)
                 setting_count =
                   Mv.Membership.Setting
                   |> Ash.Query.filter(default_membership_fee_type_id == ^changeset.data.id)
                   |> Ash.count!(authorize?: false)

                 if setting_count > 0 do
                   {:error,
                    message: "Cannot delete membership fee type: it's used as default in settings"}
                 else
                   :ok
                 end
               else
                 :ok
               end
             end,
             on: [:destroy]
  end

  attributes do
    uuid_v7_primary_key :id

    attribute :name, :string do
      allow_nil? false
      public? true
      description "Unique name for the membership fee type"
    end

    attribute :amount, :decimal do
      allow_nil? false
      public? true
      description "Fee amount in default currency (non-negative, max 2 decimal places)"
      constraints min: 0, scale: 2
    end

    attribute :interval, :atom do
      allow_nil? false
      public? true
      description "Billing interval (immutable after creation)"
      constraints one_of: [:monthly, :quarterly, :half_yearly, :yearly]
    end

    attribute :description, :string do
      allow_nil? true
      public? true
      description "Optional description for the fee type"
    end
  end

  relationships do
    has_many :membership_fee_cycles, Mv.MembershipFees.MembershipFeeCycle
    has_many :members, Mv.Membership.Member
  end

  identities do
    identity :unique_name, [:name]
  end
end
