defmodule Mv.MembershipFees.MembershipFeeCycle do
  @moduledoc """
  Ash resource representing an individual membership fee cycle for a member.

  ## Overview
  MembershipFeeCycle represents a single billing cycle for a member. Each cycle
  tracks the payment status and amount for a specific time period.

  ## Attributes
  - `cycle_start` - Start date of the billing cycle (aligned to calendar boundaries)
  - `amount` - The fee amount for this cycle (stored for audit trail)
  - `status` - Payment status: unpaid, paid, or suspended
  - `notes` - Optional notes for this cycle

  ## Design Decisions
  - **No cycle_end field**: Calculated from cycle_start + interval (from fee type)
  - **Amount stored per cycle**: Preserves historical amounts when fee type changes
  - **Calendar-aligned cycles**: All cycles start on calendar boundaries

  ## Relationships
  - `belongs_to :member` - The member this cycle belongs to
  - `belongs_to :membership_fee_type` - The fee type for this cycle

  ## Constraints
  - Unique constraint on (member_id, cycle_start) - one cycle per period per member
  - CASCADE delete when member is deleted
  - RESTRICT delete on membership_fee_type if cycles exist
  """
  use Ash.Resource,
    domain: Mv.MembershipFees,
    data_layer: AshPostgres.DataLayer,
    authorizers: [Ash.Policy.Authorizer]

  postgres do
    table "membership_fee_cycles"
    repo Mv.Repo
  end

  resource do
    description "Individual membership fee cycle for a member"
  end

  actions do
    defaults [:read, :destroy]

    create :create do
      primary? true
      accept [:cycle_start, :amount, :status, :notes, :member_id, :membership_fee_type_id]
    end

    update :update do
      primary? true
      accept [:status, :notes, :amount]
    end

    update :mark_as_paid do
      description "Mark cycle as paid"
      require_atomic? false
      accept [:notes]

      change fn changeset, _context ->
        Ash.Changeset.force_change_attribute(changeset, :status, :paid)
      end
    end

    update :mark_as_suspended do
      description "Mark cycle as suspended"
      require_atomic? false
      accept [:notes]

      change fn changeset, _context ->
        Ash.Changeset.force_change_attribute(changeset, :status, :suspended)
      end
    end

    update :mark_as_unpaid do
      description "Mark cycle as unpaid (for error correction)"
      require_atomic? false
      accept [:notes]

      change fn changeset, _context ->
        Ash.Changeset.force_change_attribute(changeset, :status, :unpaid)
      end
    end
  end

  # READ: bypass for own_data (:linked) then HasPermission for :all; create/update/destroy: HasPermission only.
  policies do
    bypass action_type(:read) do
      description "own_data: read only cycles where member_id == actor.member_id"
      authorize_if Mv.Authorization.Checks.MembershipFeeCycleReadLinkedForOwnData
    end

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

  attributes do
    uuid_v7_primary_key :id

    attribute :cycle_start, :date do
      allow_nil? false
      public? true
      description "Start date of the billing cycle"
    end

    attribute :amount, :decimal do
      allow_nil? false
      public? true

      description "Fee amount for this cycle (stored for audit trail, non-negative, max 2 decimal places)"

      constraints min: 0, scale: 2
    end

    attribute :status, :atom do
      allow_nil? false
      public? true
      default :unpaid
      description "Payment status of this cycle"
      constraints one_of: [:unpaid, :paid, :suspended]
    end

    attribute :notes, :string do
      allow_nil? true
      public? true
      description "Optional notes for this cycle"
    end
  end

  relationships do
    belongs_to :member, Mv.Membership.Member do
      allow_nil? false
    end

    belongs_to :membership_fee_type, Mv.MembershipFees.MembershipFeeType do
      allow_nil? false
    end
  end

  identities do
    identity :unique_cycle_per_member, [:member_id, :cycle_start]
  end
end
