require 'roo'
require 'roo-xls'

module EasyEntityImports
  class XlsxSimpleImporter

    attr_reader :logger

    def initialize
      @logger = EasyEntityImports::ImportLogger.new
    end

    def import(xlsx_file=nil)
      begin
        @xlsx = Roo::Spreadsheet.open(xlsx_file)
      rescue
        @logger.log_fatal_error(I18n.t('easy_imports.file_could_not_be_processed'))
        return false
      end

      header_row = @xlsx.row(1)
      header_row.each { |key| key.try(:strip!); key.try(:gsub!,' ', '_'); key.try(:downcase!) }

      return false unless validate_required_columns(header_row)
      set_header_keys header_row

      rows = @xlsx.parse(@header_keys)

      @map = Hash.new{|k, v| k[v] = {}}
      current_project = nil
      rows.each do |row|
        next if row.blank?
        if row[:project].present?
          current_project = Project.new(name: row[:project])
          current_project.enable_module!('easy_wbs')
          begin
            warnings = (current_project.errors.full_messages) unless current_project.valid?
            raise unless EasyLicenseManager.has_license_limit?(:active_project_limit)
            current_project.save!(validate: false)
            current_project.init_overview_page
            @logger.log_entity_creation current_project
            @logger.log_entity_warning(current_project, nil, warnings) if warnings.present?
          rescue
            @logger.log_entity_error(current_project, current_project.name, "#{I18n.t('easy_imports.project_could_not_be_created', project_name: current_project.name)}<ul>#{current_project.errors.full_messages.map { |m| "<li>#{m}</li>" }.join}</ul>".html_safe)
            current_project = nil
          end
        else
          next if row[:task].blank? || current_project.nil?
          warnings = []
          issue = Issue.new
          issue.subject = row[:task]
          issue.project = current_project

          if @header_keys[:assignee] && row[:assignee].present?
            issue.assigned_to = find_in_map(row, :assignee) || @map.has_key?(row[:assignee]) ? nil : User.like(row[:assignee]).first
            warnings << "#{I18n.t('easy_imports.could_not_find_user', user_name: row[:assignee])} #{I18n.t('easy_imports.assignee_set_to_nobody')}" if issue.assigned_to.nil?
          end

          issue.priority = get_attribute_value(row, :priority, IssuePriority)
          warnings << "#{I18n.t('easy_imports.could_not_find_issue_priority', priority_name: row[:priority])} #{I18n.t('easy_imports.issue_priority_set_to_value', priority_name: issue.priority)}" if @logger.get_errors_for(:issue_priorities, row[:priority]).any?

          issue.status = get_attribute_value(row, :status, IssueStatus)
          warnings << "#{I18n.t('easy_imports.could_not_find_issue_status', status_name: row[:status])} #{I18n.t('easy_imports.issue_status_set_to_value', status_name: issue.status)}" if @logger.get_errors_for(:issue_statuses, row[:status]).any?

          issue.tracker = get_attribute_value(row, :tracker, Tracker, entity_creation_attr: issue.status)
          warnings << "#{I18n.t('easy_imports.could_not_find_tracker', tracker_name: row[:tracker])} #{I18n.t('easy_imports.tracker_set_to_value', tracker_name: issue.tracker)}" if @logger.get_errors_for(:trackers, row[:tracker]).any?

          if row[:due_date].present?
            issue.due_date = parse_date(row[:due_date])
            warnings << "#{row[:due_date]} #{I18n.t('activerecord.errors.messages.not_a_date')}." if issue.due_date.nil?
          end

          issue.description = row[:description] if row[:description]
          issue.author = User.current

          issue.project.trackers << issue.tracker unless current_project.trackers.include? issue.tracker

          begin
            warnings.concat(issue.errors.full_messages) unless issue.valid?
            issue.save(validate: false)
            @logger.log_entity_creation issue
            @logger.log_entity_warning(issue, nil, warnings) if warnings.any?
          rescue
            @logger.log_entity_error(issue, current_project.id, "#{I18n.t('easy_imports.issue_could_not_be_created', row[:task])}<ul>#{issue.errors.full_messages.map { |m| "<li>#{m}</li>" }.join}</ul>".html_safe)
          end
        end
      end
    end

    def log
      @logger && @logger.log
    end

    private

    def get_attribute_value(row, key, klass=nil, options={})
      if !(@header_keys[key]) || !(value = row[key])
        return default_value_for(key)
      end
      if @map[key].has_key?(value)
        @map[key][value]
      else
        find_existing_entity(row, key, klass) ||
        (respond_to?("create_#{klass.name.underscore}", true) && send(:"create_#{klass.name.underscore}", *[value, options[:entity_creation_attr]].compact)) ||
        default_value_for(key)
      end
    end

    def find_in_map(row, key)
      @map[key][row[key]]
    end

    def find_existing_entity(row, key, klass)
      return nil if row[key].blank?
      entity = klass.find_by(name: row[key])
      if entity
        @map[key][row[key]] = entity
        @logger.log_entity_mapping(entity)
      end
      entity
    end

    def default_value_for(key)
      init_default_values unless @default_values
      @default_values[key]
    end

    def init_default_values
      @default_values = {}
      @default_values[:tracker] = Tracker.first
      @default_values[:priority] = IssuePriority.default || IssuePriority.first
      @default_values[:status] = IssueStatus.find_by(is_closed: false) || IssueStatus.first
    end

    # not used atm
    # def create_user(name)
    #   return nil if name.blank?
    #   first_name, last_name = name.split(' ', 2)
    #   login = name.parameterize.gsub('-', '_')
    #   user = User.new(firstname: first_name, lastname: last_name, mail: "#{login}@example.com")
    #   user.login = login
    #
    #   if user.save
    #     @map[:assignee][name] = user
    #   else
    #     @logger.log_entity_error(user, name, "User with name #{name} could not be created. Issue assignee set to nobody")
    #     user = nil
    #   end
    #   user
    # end

    def create_issue_priority(name)
      return if name.blank?
      issue_priority = IssuePriority.new(name: name)

      if issue_priority.save
        @map[:priority][name] = issue_priority
        @logger.log_entity_creation issue_priority
      else
        @logger.log_entity_error(issue_priority, name, "#{I18n.t('easy_imports.could_not_find_issue_priority', priority_name: name)} #{I18n.t('easy_imports.issue_priority_set_to_default_value')}")
        issue_priority = nil
      end
      issue_priority
    end

    def create_tracker(name, status)
      return if name.blank?
      tracker = Tracker.new(name: name)
      tracker.default_status = status

      if tracker.save
        @map[:tracker][name] = tracker
        @logger.log_entity_creation tracker
      else
        @logger.log_entity_error(tracker, name, "#{I18n.t('easy_imports.could_not_find_tracker', tracker_name: name)} #{I18n.t('easy_imports.tracker_set_to_default_value')}")
        tracker = nil
      end
      tracker
    end

    def create_issue_status(name)
      return if name.blank?
      issue_status = IssueStatus.new(name: name)

      if issue_status.save
        @map[:status][name] = issue_status
        @logger.log_entity_creation issue_status
      else
        @logger.log_entity_error(issue_status, name, "#{I18n.t('easy_imports.could_not_find_issue_status', issue_status: name)} #{I18n.t('easy_imports.issue_status_set_to_default_value')}")
        issue_status = nil
      end
      issue_status
    end

    def valid_columns
      %w(project task due_date assignee priority status tracker description)
    end

    def required_columns
      %w(project task)
    end

    def set_header_keys(columns)
      return @header_keys if @header_keys
      @header_keys = (valid_columns & columns).map { |c| [c.to_sym, c] }.to_h
    end

    # not used atm
    # def collect_data(rows)
    #   data = Hash.new{|k, v| k[v] = []}
    #
    #   rows.each do |row|
    #     data[:users] << row[:assignee] if row[:assignee]
    #     data[:priorities] << row[:priority] if row[:priority]
    #     data[:statuses] << row[:status] if row[:status]
    #     data[:trackers] << row[:tracker] if row[:tracker]
    #   end
    #
    #   data.each {|_, v| v.uniq!; v.compact! }
    # end

    def validate_required_columns(columns)
      if (missing_columns = required_columns - columns).any?
        headers = missing_columns.map { |e| e.capitalize }.join(', ')
        @logger.log_fatal_error "#{I18n.t('easy_imports.file_could_not_be_processed')} #{I18n.t('easy_imports.missing_required_headers', headers: headers)}"
        return false
      else
        return true
      end
    end

    def parse_date(date)
      if date.is_a?(Date)
        date
      else
        Date.parse(date) rescue nil
      end
    end
  end
end
