# frozen_string_literal: true

# Yammer importer
#  https://docs.microsoft.com/en-us/yammer/manage-security-and-compliance/export-yammer-enterprise-data#export-yammer-network-data-by-date-range-and-network
#
# You will need a bunch of CSV files:
#
# - Users.csv Groups.csv Topics.csv Groups.csv Files.csv Messages.csv
# (Others included in Yammer export are ignored)

require "csv"
require_relative "base"
require_relative "base/generic_database"

# Call it like this:
#   RAILS_ENV=production bundle exec ruby script/import_scripts/yammer.rb DIRNAME

class ImportScripts::Yammer < ImportScripts::Base
  BATCH_SIZE = 1000
  NUM_WORDS_IN_TITLE = ENV["NUM_WORDS_IN_TITLE"].to_i || 20
  SKIP_EMPTY_EMAIL = true
  SKIP_INACTIVE_USERS = false
  PARENT_CATEGORY_NAME = ENV["PARENT_CATEGORY_NAME"] || "Yammer Import"
  IMPORT_GROUPS_AS_TAGS = true
  MERGE_USERS = true
  # import groups as tags rather than as categories
  SiteSetting.tagging_enabled = true if IMPORT_GROUPS_AS_TAGS
  PM_TAG = ENV["PM_TAG"] || "eht"

  def initialize(path)
    super()

    @path = path
    @db = ImportScripts::GenericDatabase.new(@path, batch_size: BATCH_SIZE, recreate: true)
  end

  def execute
    create_developer_users
    read_csv_files

    import_categories
    import_users
    import_topics
    import_pm_topics
    import_posts
    import_pm_posts
  end

  def create_developer_users
    GlobalSetting
      .developer_emails
      .split(",")
      .each { |e| User.create(email: e, active: true, username: e.split("@")[0]) }
  end

  def read_csv_files
    puts "", "reading CSV files"

    # consider csv_parse Tags.csv
    # consider Admins.csv that has admins

    u_count = 0
    csv_parse("Users") do |row|
      next if SKIP_INACTIVE_USERS && row[:state] != "active"
      u_count += 1
      @db.insert_user(
        id: row[:id],
        email: row[:email],
        name: row[:name],
        username: row[:name],
        bio: "",
        # job_title: row[:job_title],
        # location: row[:location],
        # department: row[:department],
        created_at: parse_datetime(row[:joined_at]),
        # deleted_at: parse_datetime(row[:deleted_at]),
        # suspended_at: parse_datetime(row[:suspended_at]),
        # guid: row[:guid],
        # state: row[:state],
        avatar_path: row[:user_cover_image_url],
        # last_seen_at: ,
        active: row[:state] == "active" ? 1 : 0,
      )
    end

    category_position = 0
    csv_parse("Groups") do |row|
      @db.insert_category(
        id: row[:id],
        name: row[:name],
        description: row[:description],
        position: category_position += 1,
      )
    end

    csv_parse("Files") do |row|
      @db.insert_upload(
        id: row[:file_id],
        user_id: row[:uploader_id],
        original_filename: row[:name],
        filename: row[:path],
        description: row[:description],
      )
    end

    # get topics from messages
    csv_parse("Messages") do |row|
      next unless row[:thread_id] == row[:id]
      next if row[:in_private_conversation] == "true"
      next unless row[:deleted_at].blank?
      # next if row[:message_type] == 'system'
      title = ""
      url = ""
      description = ""
      raw = row[:body]
      reg = /opengraphobject:\[(\d*?) : (.*?) : title="(.*?)" : description="(.*?)"\]/
      if row[:attachments]
        row[:attachments].match(reg) do
          url = Regexp.last_match(2)
          title = Regexp.last_match(3) if Regexp.last_match(3)
          description = Regexp.last_match(4)
          raw += "\n***\n#{description}\n#{url}\n" unless raw.include?(url)
        end
        row[:attachments].match(/uploadedfile:(\d*)$/) do
          file_id = Regexp.last_match(1).to_i
          up = @db.fetch_upload(file_id).first
          path = File.join(@path, up["filename"])
          filename = up["original_filename"]
          user_id = user_id_from_imported_user_id(row["user_id"]) || Discourse.system_user.id
          if File.exist?(path)
            upload = create_upload(user_id, path, filename)
            raw += html_for_upload(upload, filename) if upload&.persisted?
          end
        end
      end
      @db.insert_topic(
        id: row[:id],
        title: title,
        raw: raw,
        category_id: row[:group_id],
        closed: row[:closed] == "TRUE" ? 1 : 0,
        user_id: row[:sender_id],
        created_at: parse_datetime(row[:created_at]),
      )
    end

    # get pm topics
    csv_parse("Messages") do |row|
      next unless row[:thread_id] == row[:id]
      next unless row[:in_private_conversation] == "true"
      next unless row[:deleted_at].blank?
      # next if row[:message_type] == 'system'
      title = ""
      url = ""
      description = ""
      raw = row[:body]
      reg = /opengraphobject:\[(\d*?) : (.*?) : title="(.*?)" : description="(.*?)"\]/
      if row[:attachments]
        row[:attachments].match(reg) do
          url = Regexp.last_match(2)
          title = Regexp.last_match(3) if Regexp.last_match(3)
          description = Regexp.last_match(4)
          raw += "\n***\n#{description}\n#{url}\n" unless raw.include?(url)
        end
        row[:attachments].match(/uploadedfile:(\d*)$/) do
          file_id = Regexp.last_match(1).to_i
          up = @db.fetch_upload(file_id).first
          path = File.join(@path, up["filename"])
          filename = up["original_filename"]
          user_id = user_id_from_imported_user_id(row["user_id"]) || Discourse.system_user.id
          if File.exist?(path)
            upload = create_upload(user_id, path, filename)
            raw += html_for_upload(upload, filename) if upload&.persisted?
          end
        end
      end
      @db.insert_pm_topic(
        id: row[:id],
        title: title,
        raw: raw,
        category_id: row[:group_id],
        closed: row[:closed] == "TRUE" ? 1 : 0,
        target_users: row[:participants].gsub("user:", ""),
        user_id: row[:sender_id],
        created_at: parse_datetime(row[:created_at]),
      )
    end

    # get posts from messages
    csv_parse("Messages") do |row|
      next if row[:thread_id] == row[:id]
      next unless row[:deleted_at].blank?
      next if row[:in_private_conversation] == "true"
      @db.insert_post(
        id: row[:id],
        raw: row[:body] + "\n" + row[:attachments],
        topic_id: row[:thread_id],
        reply_to_post_id: row[:replied_to_id],
        user_id: row[:sender_id],
        created_at: parse_datetime(row[:created_at]),
      )
    end

    # get pm posts from messages
    csv_parse("Messages") do |row|
      next if row[:thread_id] == row[:id]
      next unless row[:deleted_at].blank?
      next unless row[:in_private_conversation] == "false"
      @db.insert_pm_post(
        id: row[:id],
        raw: row[:body] + "\n" + row[:attachments],
        topic_id: row[:thread_id],
        reply_to_post_id: row[:replied_to_id],
        user_id: row[:sender_id],
        created_at: parse_datetime(row[:created_at]),
      )
    end

    #@db.delete_unused_users
    @db.sort_posts_by_created_at
  end

  def parse_datetime(text)
    return nil if text.blank? || text == "null"
    DateTime.parse(text)
  end

  def import_categories
    puts "", "creating categories"
    parent_category = nil
    if !PARENT_CATEGORY_NAME.blank?
      parent_category = Category.find_by(name: PARENT_CATEGORY_NAME)
      parent_category =
        Category.create(
          name: PARENT_CATEGORY_NAME,
          user_id: Discourse.system_user.id,
        ) unless parent_category
    end

    if IMPORT_GROUPS_AS_TAGS
      @tag_map = {}
      rows = @db.fetch_categories
      rows.each { |row| @tag_map[row["id"]] = row["name"] }
    else
      rows = @db.fetch_categories
      create_categories(rows) do |row|
        {
          id: row["id"],
          name: row["name"],
          description: row["description"],
          position: row["position"],
          parent_category_id: parent_category,
        }
      end
    end
  end

  def batches
    super(BATCH_SIZE)
  end

  def import_users
    puts "", "creating users"
    total_count = @db.count_users
    puts "", "Got #{total_count} users!"
    last_id = ""

    batches do |offset|
      rows, last_id = @db.fetch_users(last_id)
      break if rows.empty?

      next if all_records_exist?(:users, rows.map { |row| row["id"] })

      create_users(rows, total: total_count, offset: offset) do |row|
        user = User.find_by_email(row["email"].downcase)
        if user
          user.custom_fields["import_id"] = row["id"]
          user.custom_fields["matched_existing"] = "yes"
          user.save
          add_user(row["id"].to_s, user)
          next
        end
        {
          id: row["id"],
          email: row["email"],
          name: row["name"],
          created_at: row["created_at"],
          last_seen_at: row["last_seen_at"],
          active: row["active"] == 1,
        }
      end
    end
  end

  def import_topics
    puts "", "creating topics"
    staff_guardian = Guardian.new(Discourse.system_user)

    total_count = @db.count_topics
    last_id = ""

    batches do |offset|
      rows, last_id = @db.fetch_topics(last_id)
      base_category = Category.find_by(name: PARENT_CATEGORY_NAME)
      break if rows.empty?

      next if all_records_exist?(:posts, rows.map { |row| import_topic_id(row["id"]) })

      create_posts(rows, total: total_count, offset: offset) do |row|
        {
          id: import_topic_id(row["id"]),
          title:
            (
              if row["title"].present?
                row["title"]
              else
                row["raw"].split(/\W/)[0..(NUM_WORDS_IN_TITLE - 1)].join(" ")
              end
            ),
          raw: normalize_raw(row["raw"]),
          category:
            (
              if IMPORT_GROUPS_AS_TAGS
                base_category.id
              else
                category_id_from_imported_category_id(row["category_id"])
              end
            ),
          user_id: user_id_from_imported_user_id(row["user_id"]) || Discourse.system_user.id,
          created_at: row["created_at"],
          closed: row["closed"] == 1,
          post_create_action:
            proc do |post|
              if IMPORT_GROUPS_AS_TAGS
                topic = Topic.find(post.topic_id)
                tag_names = [@tag_map[row["category_id"]]]
                DiscourseTagging.tag_topic_by_names(topic, staff_guardian, tag_names)
              end
            end,
        }
      end
    end
  end

  def import_pm_topics
    puts "", "creating pm topics"
    staff_guardian = Guardian.new(Discourse.system_user)

    total_count = @db.count_pm_topics
    last_id = ""

    batches do |offset|
      rows, last_id = @db.fetch_pm_topics(last_id)
      base_category = Category.find_by(name: PARENT_CATEGORY_NAME)
      break if rows.empty?

      next if all_records_exist?(:posts, rows.map { |row| import_topic_id(row["id"]) })
      create_posts(rows, total: total_count, offset: offset) do |row|
        target_users = []
        row["target_users"]
          .split(",")
          .each do |u|
            user_id = user_id_from_imported_user_id(u)
            next unless user_id
            user = User.find(user_id)
            target_users.append(user.username)
          end
        target_usernames = target_users.join(",")
        {
          id: import_topic_id(row["id"]),
          title:
            (
              if row["title"].present?
                row["title"]
              else
                row["raw"].split(/\W/)[0..(NUM_WORDS_IN_TITLE - 1)].join(" ")
              end
            ),
          raw: normalize_raw(row["raw"]),
          category:
            (
              if IMPORT_GROUPS_AS_TAGS
                base_category.id
              else
                category_id_from_imported_category_id(row["category_id"])
              end
            ),
          user_id: user_id_from_imported_user_id(row["user_id"]) || Discourse.system_user.id,
          created_at: row["created_at"],
          closed: row["closed"] == 1,
          archetype: Archetype.private_message,
          target_usernames: target_usernames,
          post_create_action:
            proc do |post|
              if PM_TAG
                topic = Topic.find(post.topic_id)
                tag_names = [PM_TAG]
                DiscourseTagging.tag_topic_by_names(topic, staff_guardian, tag_names)
              end
            end,
        }
      end
    end
  end

  def import_topic_id(topic_id)
    "T#{topic_id}"
  end

  def import_posts
    puts "", "creating posts"
    total_count = @db.count_posts
    last_row_id = 0

    batches do |offset|
      rows, last_row_id = @db.fetch_sorted_posts(last_row_id)
      break if rows.empty?

      next if all_records_exist?(:posts, rows.map { |row| row["id"] })

      create_posts(rows, total: total_count, offset: offset) do |row|
        topic = topic_lookup_from_imported_post_id(import_topic_id(row["topic_id"]))
        if topic.nil?
          p "MISSING TOPIC #{row["topic_id"]}"
          p row
          next
        end
        {
          id: row["id"],
          raw: normalize_raw(row["raw"]),
          user_id: user_id_from_imported_user_id(row["user_id"]) || Discourse.system_user.id,
          topic_id: topic[:topic_id],
          created_at: row["created_at"],
        }
      end
    end
  end

  def import_pm_posts
    puts "", "creating pm posts"
    total_count = @db.count_pm_posts
    last_row_id = 0

    batches do |offset|
      rows, last_row_id = @db.fetch_pm_posts(last_row_id)
      break if rows.empty?

      next if all_records_exist?(:posts, rows.map { |row| row["id"] })

      create_posts(rows, total: total_count, offset: offset) do |row|
        topic = topic_lookup_from_imported_post_id(import_topic_id(row["topic_id"]))

        if topic.nil?
          p "MISSING TOPIC #{row["topic_id"]}"
          p row
          next
        end

        {
          id: row["id"],
          raw: normalize_raw(row["raw"]),
          user_id: user_id_from_imported_user_id(row["user_id"]) || Discourse.system_user.id,
          topic_id: topic[:topic_id],
          created_at: row["created_at"],
        }
      end
    end
  end

  def normalize_raw(raw)
    return "<missing>" if raw.blank?

    raw = raw.gsub('\n', "")
    raw.gsub!(/\[\[user:(\d+)\]\]/) do
      u = Regexp.last_match(1)
      user_id = user_id_from_imported_user_id(u) || Discourse.system_user.id
      if user_id
        user = User.find(user_id)
        "@#{user.username}"
      else
        u
      end
    end
    raw
  end

  def permalink_exists?(url)
    Permalink.find_by(url: url)
  end

  def csv_parse(table_name)
    CSV.foreach(
      File.join(@path, "#{table_name}.csv"),
      headers: true,
      header_converters: :symbol,
      skip_blanks: true,
      encoding: "bom|utf-8",
    ) { |row| yield row }
  end
end

unless ARGV[0] && Dir.exist?(ARGV[0])
  puts "", "Usage:", "", "bundle exec ruby script/import_scripts/yammer.rb DIRNAME", ""
  exit 1
end

ImportScripts::Yammer.new(ARGV[0]).perform