module TrackerApi
  module Resources
    class Story
      include Shared::Base

      attribute :client

      attribute :accepted_at, DateTime
      attribute :after_id, Integer
      attribute :before_id, Integer
      attribute :blockers, [Blocker]
      attribute :comment_ids, [Integer]
      attribute :comments, [Comment]
      attribute :created_at, DateTime
      attribute :current_state, String # (accepted, delivered, finished, started, rejected, planned, unstarted, unscheduled)
      attribute :deadline, DateTime
      attribute :description, String
      attribute :estimate, Float
      attribute :external_id, String
      attribute :follower_ids, [Integer]
      attribute :followers, [Person]
      attribute :integration_id, Integer
      attribute :kind, String
      attribute :label_ids, [Integer]
      attribute :labels, [Label]
      attribute :name, String
      attribute :owned_by_id, Integer # deprecated!
      attribute :owned_by, Person
      attribute :owner_ids, [Integer]
      attribute :owners, [Person]
      attribute :planned_iteration_number, Integer
      attribute :project_id, Integer
      attribute :pull_requests, [PullRequest]
      attribute :requested_by, Person
      attribute :requested_by_id, Integer
      attribute :reviews, [Review]
      attribute :story_type, String # (feature, bug, chore, release)
      attribute :task_ids, [Integer]
      attribute :tasks, [Task]
      attribute :transitions, [StoryTransition]
      attribute :updated_at, DateTime
      attribute :url, String


      class UpdateRepresenter < Representable::Decorator
        include Representable::JSON

        property :follower_ids, if: ->(_) { !follower_ids.blank? }
        property :name
        property :after_id
        property :before_id
        property :description
        property :story_type
        property :current_state
        property :estimate
        property :accepted_at
        property :deadline
        property :requested_by_id
        property :owner_ids, if: ->(_) { !owner_ids.blank? }
        property :project_id

        # Use render_empty: false to address: https://github.com/dashofcode/tracker_api/issues/110
        # - The default value of the labels attribute in Resources::Story is an empty array.
        # - If the value of labels is not change (i.e. not dirty) then when a new Story
        #   is created from the dirty attributes in the save method the labels attributes becomes
        #   an empty array again. render_empty: false keeps this from rendering in the json passed
        #   in the API PUT request. It is is empty then the labels will be cleared.
        # - The next issue is that there is no way to delete all the labels from a Story with
        #   the current implementation.
        #
        # NOTE: There are two solutions: 1) remove dirty tracking 2) rewrite without virtus
        # SEE: https://github.com/dashofcode/tracker_api/pull/98
        collection :labels, class: Label, decorator: Label::UpdateRepresenter, render_empty: false

        property :integration_id
        property :external_id
      end

      # @return [String] Comma separated list of labels.
      def label_list
        @label_list ||= begin
          return if labels.nil?
          labels.collect(&:name).join(',')
        end
      end

      # Adds a new label to the story.
      #
      # @param [Label|Hash|String] label
      def add_label(label)
        new_label = if label.kind_of?(String)
          Label.new(name: label)
        else
          label
        end

        # Use attribute writer to get coercion and dirty tracking.
        self.labels = (labels ? labels.dup : []).push(new_label).uniq
      end

      # Adds a new owner to the story.
      #
      # @param [Person|Fixnum] owner
      def add_owner(owner)
        owner_id = if owner.kind_of?(Integer)
          owner_id = owner
        else
          raise ArgumentError, 'Valid Person expected.' unless owner.instance_of?(Resources::Person)
          owner_id = owner.id
        end

        # Use attribute writer to get coercion and dirty tracking.
        self.owner_ids = (owner_ids ? owner_ids.dup : []).push(owner_id).uniq
      end

      # Provides a list of all the activity performed on the story.
      #
      # @param [Hash] params
      # @return [Array[Activity]]
      def activity(params = {})
        Endpoints::Activity.new(client).get_story(project_id, id, params)
      end

      def blockers(params = {})
        Endpoints::Blockers.new(client).get(project_id, id, params)
      end

      # Provides a list of all the comments on the story.
      #
      # @param [Hash] params
      # @return [Array[Comment]]
      def comments(reload: false)
        if !reload && @comments.present?
          @comments
        else
          @comments = Endpoints::Comments.new(client).get(project_id, story_id: id)
        end
      end

      # Provides a list of all the tasks on the story.
      #
      # @param [Hash] params
      # @return [Array[Task]]
      def tasks(params = {})
        if params.blank? && @tasks.present?
          @tasks
        else
          @tasks = Endpoints::Tasks.new(client).get(project_id, id, params)
        end
      end

      # Provides a list of all the owners of the story.
      #
      # @param [Hash] params
      # @return [Array[Person]]
      def owners(params = {})
        if params.blank? && @owners.present?
          @owners
        else
          @owners = Endpoints::StoryOwners.new(client).get(project_id, id, params)
        end
      end

      # Provides a list of all the transitions of the story.
      #
      # @param [Hash] params
      # @return [Array[StoryTransition]]
      def transitions(params = {})
        if params.blank? && @transitions.present?
          @transitions
        else
          @transitions = Endpoints::StoryTransitions.new(client).get(project_id, id, params)
        end
      end

      # Returns the story's original ("undirtied") project_id
      #
      # @return Integer
      def project_id
        if dirty_attributes.key?(:project_id)
          original_attributes[:project_id]
        else
          @project_id
        end
      end

      # @param [Hash] params attributes to create the task with
      # @return [Task] newly created Task
      def create_task(params)
        Endpoints::Task.new(client).create(project_id, id, params)
      end

      # @param [Hash] params attributes to create the comment with
      # @return [Comment] newly created Comment
      def create_comment(params)
        files = params.delete(:files)
        comment = Endpoints::Comment.new(client).create(project_id, story_id: id, params: params)
        comment.create_attachments(files: files) if files.present?
        comment
      end

      # Save changes to an existing Story.
      def save
        raise ArgumentError, 'Can not update a story with an unknown project_id.' if project_id.nil?
        return self unless dirty?

        Endpoints::Story.new(client).update(self, UpdateRepresenter.new(Story.new(self.dirty_attributes)))
      end

      def reviews(params = {})
          if params.blank? && @reviews.present?
            @reviews
          else
            @reviews = Endpoints::Reviews.new(client).get(project_id, id, params)
          end
      end
    end
  end
end
