module MessageTrain
  class Box
    include ActiveModel::Model
    attr_accessor :parent, :division, :errors, :results
    alias_method :id, :division

    def initialize(parent, division)
      @parent = parent
      @division = division
      @results = Results.new(self)
      @errors = Errors.new(self)
    end

    def marks
      { conversations: nil }
    end

    def to_param
      division.to_s
    end

    def unread_count
      conversations(unread: true).count
    end

    def conversations(options = {})
      found = parent.conversations(division).with_undeleted_for(parent)
      if options[:read] == false || options[:unread]
        found = found.with_unread_for(parent)
      end
      if division == :trash
        found = found.with_trashed_for(parent)
      else
        found = found.with_untrashed_for(parent)
        if division == :drafts
          found = found.with_drafts_by(parent)
        else
          found = found.with_ready_for(parent)
        end
        if division == :ignored
          found = found.ignored(parent)
        else
          found = found.unignored(parent)
        end
      end
      found
    end

    def find_conversation(id)
      parent.all_conversations.find(id)
    end

    def find_message(id)
      parent.all_messages.find(id)
    end

    def new_message(args = {})
      if args[:conversation_id].nil?
        message = MessageTrain::Message.new(args)
      else
        conversation = find_conversation(args[:conversation_id])
        message = conversation.messages.build(args)
        message.subject = "Re: #{conversation.subject}"
        recipient_arrays = {}
        conversation.default_recipients_for(parent).each do |recipient|
          table_name = recipient.class.table_name
          recipient_arrays[table_name] ||= []
          recipient_arrays[table_name] << recipient.send(MessageTrain.configuration.slug_columns[table_name.to_sym])
        end
        recipient_arrays.each do |key, array|
          message.recipients_to_save[key] = array.join(', ')
        end
      end
      message
    end

    def send_message(attributes)
      message_to_send = MessageTrain::Message.new attributes
      message_to_send.sender = parent
      unless message_to_send.save
        errors.add(message_to_send, message_to_send.errors.full_messages.to_sentence)
      end
      message_to_send
    end

    def update_message(message_to_update, attributes)
      attributes.delete(:sender)
      if message_to_update.sender == parent
        message_to_update.update(attributes)
        unless message_to_update.errors.empty?
          errors.add(message_to_update, message_to_update.errors.full_messages.to_sentence)
        end
      else
        errors.add(message_to_update, :access_to_message_id_denied.l(id: message_to_update.id))
      end
      message_to_update.reload
    end

    def ignore(object)
      case object.class.name
        when 'Hash'
          ignore object.values
        when 'Array'
          object.collect { |item| ignore(item) }.uniq == [true]
        when 'String', 'Fixnum'
          id = object.to_i
          object = parent.all_conversations.find_by_id(id)
          if object.nil?
            errors.add(self, :class_id_not_found_in_box.l(class: 'Conversation', id: id.to_s))
          else
            ignore(object)
          end
        when 'MessageTrain::Conversation'
          if authorize(object)
            if object.set_ignored(parent)
              results.add(object, :update_successful.l)
            else
              errors.add(object, object.errors.full_messages.to_sentence)
            end
          else
            false
          end
        else
        errors.add(self, :cannot_ignore_type.l(type: object.class.name))
      end
    end

    def unignore(object)
      case object.class.name
        when 'Hash'
          unignore object.values
        when 'Array'
          object.collect { |item| unignore(item) }.uniq == [true]
        when 'String', 'Fixnum'
          id = object.to_i
          object = parent.all_conversations.find_by_id(id)
          if object.nil?
            errors.add(self, :class_id_not_found_in_box.l(class: 'Conversation', id: id.to_s))
          else
            unignore(object)
          end
        when 'MessageTrain::Conversation'
          if authorize(object)
            if object.set_unignored(parent)
              results.add(object, :update_successful.l)
            else
              errors.add(object, object.errors.full_messages.to_sentence)
            end
          else
            false
          end
        else
        errors.add(self, :cannot_ignore_type.l(type: object.class.name))
      end
    end

    def title
      "box_title_#{division.to_s}".to_sym.l
    end

    def message
      if !errors.all.empty?
        errors.all.collect { |x| x[:message] }.uniq.to_sentence
      elsif results.all.empty?
        :nothing_to_do.l
      else
        results.all.collect { |x| x[:message] }.uniq.to_sentence
      end
    end

    def mark(mark_to_set, objects)
      objects.each do |key, object|
        if object.present?
          case object.class.name
            when 'Hash'
              mark(mark_to_set, { key => object.values } )
            when 'Array'
              object.collect { |item| mark(mark_to_set, key => item) }.uniq == [true]
            when 'String', 'Fixnum'
              id = object.to_i
              object = parent.send("all_#{key}".to_sym).find_by_id(id)
              if object.nil?
                errors.add(self, :class_id_not_found_in_box.l(class: key.to_s.classify, id: id.to_s))
              else
                mark(mark_to_set, key => object)
              end
            when 'MessageTrain::Conversation', 'MessageTrain::Message'
              if authorize(object)
                if object.mark(mark_to_set, parent)
                  results.add(object, :update_successful.l)
                else
                  errors.add(object, object.errors.full_messages.to_sentence)
                end
              else
                false
              end
            else
            errors.add(self, :cannot_mark_type.l(type: object.class.name))
          end
        end
      end
    end

    def authorize(object)
      case object.class.name
        when 'MessageTrain::Conversation'
          if object.includes_receipts_for? parent
            true
          else
            errors.add(object, :access_to_conversation_id_denied.l(id: object.id))
          end
        when 'MessageTrain::Message'
          if object.receipts.for(parent).any?
            true
          else
            errors.add(object, :access_to_message_id_denied.l(id: object.id))
          end
        else
          errors.add(object, :dont_know_how_to_mark_object.l(object: object.class.name))
      end
    end

    class Results
      attr_accessor :items, :box

      def initialize(box)
        @items = []
        @box = box
      end

      def add(object, message)
        item = {}
        if object.is_a? MessageTrain::Box
          item[:css_id] = 'box'
          item[:path] = MessageTrain::Engine.routes.path_for({
                                                             controller: 'message_train/boxes',
                                                             action: :show,
                                                             division: object.division
                                                         })
        elsif object.new_record?
          item[:css_id] = "#{object.class.table_name.singularize}"
          item[:path] = nil
        else
          item[:css_id] = "#{object.class.table_name.singularize}_#{object.id.to_s}"
          item[:path] = MessageTrain::Engine.routes.path_for({
                                                             controller: object.class.table_name.gsub('message_train_', 'message_train/'),
                                                             action: :show,
                                                             box_division: box.division,
                                                             id: object.id
                                                         })
        end
        item[:message] = message
        items << item
        true
      end

      def all
        items
      end
    end
    class Errors < Results
      def add(object, message)
        super object, message
        false
      end
    end
  end
end