defmodule Mv.Membership.CustomFieldValue do
  @moduledoc """
  Ash resource representing a custom field value for a member.

  ## Overview
  CustomFieldValues implement the Entity-Attribute-Value (EAV) pattern, allowing
  dynamic custom fields to be attached to members. Each custom field value links a
  member to a custom field and stores the actual value.

  ## Value Storage
  Values are stored using Ash's union type with JSONB storage format:
  ```json
  {
    "type": "string",
    "value": "example"
  }
  ```

  ## Supported Types
  - `:string` - Text data
  - `:integer` - Numeric data
  - `:boolean` - True/false flags
  - `:date` - Date values
  - `:email` - Validated email addresses (custom type)

  ## Relationships
  - `belongs_to :member` - The member this custom field value belongs to (CASCADE delete)
  - `belongs_to :custom_field` - The custom field definition (CASCADE delete)

  ## Constraints
  - Each member can have only one custom field value per custom field (unique composite index)
  - Custom field values are deleted when the associated member is deleted (CASCADE)
  - Custom field values are deleted when the associated custom field is deleted (CASCADE)
  - String values maximum length: 10,000 characters
  - Email values maximum length: 254 characters (RFC 5321)

  ## Future Features
  - Type-matching validation (value type must match custom field's value_type) - to be implemented
  """
  use Ash.Resource,
    domain: Mv.Membership,
    data_layer: AshPostgres.DataLayer,
    authorizers: [Ash.Policy.Authorizer]

  import Ash.Expr

  postgres do
    table "custom_field_values"
    repo Mv.Repo

    references do
      reference :member, on_delete: :delete
      reference :custom_field, on_delete: :delete
    end
  end

  actions do
    defaults [:create, :read, :update, :destroy]
    default_accept [:value, :member_id, :custom_field_id]

    read :by_custom_field_id do
      argument :custom_field_id, :uuid, allow_nil?: false

      filter expr(custom_field_id == ^arg(:custom_field_id))
    end
  end

  # Authorization Policies
  # Order matters: Most specific policies first, then general permission check
  # Pattern aligns with User and Member resources (bypass for READ, HasPermission for update/destroy)
  # Create uses CustomFieldValueCreateScope because Ash cannot apply filters to create actions.
  policies do
    # SPECIAL CASE: Users can READ custom field values of their linked member
    # Bypass needed for list queries (expr triggers auto_filter in Ash)
    bypass action_type(:read) do
      description "Users can read custom field values of their linked member"
      authorize_if expr(member_id == ^actor(:member_id))
    end

    # CREATE: CustomFieldValueCreateScope (no filter; Ash rejects filters on create)
    # - :own_data -> create allowed when member_id == actor.member_id (scope :linked)
    # - :read_only -> no create permission
    # - :normal_user / :admin -> create allowed (scope :all)
    policy action_type(:create) do
      description "CustomFieldValue create allowed by permission set scope"
      authorize_if Mv.Authorization.Checks.CustomFieldValueCreateScope
    end

    # READ/UPDATE/DESTROY: HasPermission (scope :linked / :all)
    policy action_type([:read, :update, :destroy]) do
      description "Check permissions from user's role and permission set"
      authorize_if Mv.Authorization.Checks.HasPermission
    end

    # DEFAULT: Ash implicitly forbids if no policy authorized (fail-closed)
  end

  attributes do
    uuid_primary_key :id

    attribute :value, :union,
      constraints: [
        storage: :type_and_value,
        types: [
          boolean: [
            type: :boolean
          ],
          date: [
            type: :date
          ],
          integer: [
            type: :integer
          ],
          string: [
            type: :string,
            constraints: [
              max_length: 10_000,
              trim?: true
            ]
          ],
          email: [
            type: Mv.Membership.Email
          ]
        ]
      ]
  end

  relationships do
    belongs_to :member, Mv.Membership.Member

    belongs_to :custom_field, Mv.Membership.CustomField
  end

  calculations do
    calculate :value_to_string, :string, expr(value[:value] <> "")
  end

  # Ensure a member can only have one custom field value per custom field
  # For example: A member can have only one "phone" custom field value, one "email" custom field value, etc.
  identities do
    identity :unique_custom_field_per_member, [:member_id, :custom_field_id]
  end
end
