require 'yaml'
require 'gitlab'
require 'set'
module SchleuderGitlabTicketing
  class List
    include SchleuderGitlabTicketing::GitlabConfig

    def initialize(name, config)
      @name = name
      @config = config
    end

    def configured?
      unless @config['project'] && @config['namespace'] && @config['gitlab'] && gitlab && project
        return false
      end
      true
    end

    def process(mail)
      return false if ignore_mail?(mail)
      ticket_id = get_ticket_id(mail.subject)
      ticket = ticket_id ? get_ticket(ticket_id) : nil

      if (title = clean_subject(mail.subject)).nil? || title.empty?
        title = "Request from #{mail.from.first}"
      end
      desc = desc(mail)
      updates = {}
      labels = Set.new

      if !ticket
        ticket = create_ticket(title, desc)
        ticket_id = ticket.iid
        mail.subject = update_subject(mail.subject, ticket_id)
      else
        labels = Set.new(ticket.labels)
        comment_ticket(ticket_id,desc)
        assignee_id = team_member_id(mail.from.first)
        if assignee_id && ((as = ticket.assignee).nil? || as.id != assignee_id)
          updates[:assignee_id] = assignee_id
        end
      end

      bc = be_closed?(mail.subject)
      tc = ticket_closed?(ticket)

      if !tc && bc
        labels.delete('inprocess')
        updates[:state_event] = 'close'
      elsif !tc && !bc
        labels << 'inprocess' if updates[:assignee_id]
      elsif tc && !bc
        labels << 'inprocess'
        updates[:state_event] = 'reopen'
      end
      if labels.empty? && (updates[:state_event] == 'close')
        # make sure we remove the inprocess label
        updates[:labels] = ''
      elsif !labels.empty? && (labels.to_a.sort != ticket.labels.sort)
        updates[:labels] = labels.to_a.join(',')
      end
      update_ticket(ticket_id, updates)
      true
    end

    def gitlab
      @gitlab ||= if @config['gitlab'].is_a?(Gitlab::Client)
        @config['gitlab']
      else
        super
      end
    end

    def sender_filters
      Array(@config['sender_filters'])
    end
    def subject_filters
      Array(@config['subject_filters'])
    end

    private
    def desc(mail)
      res = []
      res << "From: #{mail.from.first}"
      res << "Message-Id: #{mail.message_id}"
      res << "Subject: #{mail.subject || '[not set]'}"
      res.join("\n\n")
    end

    def be_closed?(subject)
      !(subject =~ /\[ok\]/).nil?
    end

    def ticket_closed?(ticket)
      ticket.state == 'closed'
    end

    def update_ticket(ticket_id,updates)
      return if updates.empty?
      gitlab.edit_issue(project.id, ticket_id, updates)
    rescue Gitlab::Error::NotFound => e
      nil
    end

    def create_ticket(title, desc)
      opts = { description: desc }
      gitlab.create_issue(project.id, title, opts)
    end

    def comment_ticket(ticket_id, desc)
      gitlab.create_issue_note(project.id, ticket_id, desc)
    end

    def get_ticket(id)
      gitlab.issue(project.id, id)
    rescue Gitlab::Error::NotFound => e
      nil
    end

    def get_ticket_id(str)
      if str && (m = str.match(/.*\[#{Regexp.escape(ticket_prefix)}\#(\d+)\].*/)) && m[1]
        m[1].to_i
      else
        nil
      end
    end

    def project
      @project ||= gitlab.search_projects(@config['project']).find{|p| p.namespace.name == @config['namespace'] && p.name == @config['project'] }
    rescue Gitlab::Error::NotFound => e
      nil
    end

    def issues
      gitlab.issues(p.id)
    end

    def team_member_id(email)
      if user_id = find_user_id(email)
        user_id if project_member?(project, user_id)
      else
        nil
      end
    rescue Gitlab::Error::NotFound => e
      nil
    end

    def project_member?(project, user_id)
      begin
        gitlab.team_member(project.id, user_id)
      rescue Gitlab::Error::NotFound
        gitlab.group_member(project.namespace.id, user_id)
      end
    rescue Gitlab::Error::NotFound => e
      nil
    end

    def find_user_id(username_or_email)
      users = gitlab.user_search(username_or_email)
      # did we look for an email => exact match
      # or by username?
      user = if username_or_email =~ /@/
        users.first
      else
        users.find do |u|
          u.name == username_or_email || u.username == username_or_email
        end
      end
      user.nil? ? nil : user.id
    rescue Gitlab::Error::NotFound => e
      nil
    end

    def ignore_mail?(mail)
      return true if mail.respond_to?(:[]) && mail['X-Spam-Flag'].to_s.downcase == 'yes'
      return true if sender_filters.any?{|s| mail.from.first =~ /#{s}/ }
      return true if subject_filters.any?{|s| mail.subject =~ /#{s}/ }
      false
    end

    def ticket_prefix
      @ticket_prefix ||= (@config['ticket_prefix'] || "gl/#{@config['project']}")
    end

    def clean_subject(subj)
      subj.nil? ? nil : subj.gsub(/^(Re|Fw):\s*/,'').gsub(/\[[<>]?#{list_subj}\]\s*/,'').gsub(/\s*\[#{Regexp.escape(ticket_prefix)}#\d+\](\s*$)?/,'').gsub(/\s*\[ok\](\s*$)?/,'').strip
    end

    def list_subj
      @list_subj ||= (@config['list_subj'] || @name.split('@',2).first)
    end

    def update_subject(subject,ticket_id)
      [subject.nil? ? nil : subject.gsub(/(\s)?\[#{Regexp.escape(ticket_prefix)}#\d+\]/,''), "[#{ticket_prefix}##{ticket_id}]"].compact.join(' ')
    end
  end
end
