# Copyright (C) The Arvados Authors. All rights reserved.
#
# SPDX-License-Identifier: AGPL-3.0

require 'has_uuid'
require 'kind_and_etag'

class AddUsernameToUsers < ActiveRecord::Migration[4.2]
  include CurrentApiClient

  SEARCH_INDEX_COLUMNS =
    ["uuid", "owner_uuid", "modified_by_client_uuid",
     "modified_by_user_uuid", "email", "first_name", "last_name",
     "identity_url", "default_owner_uuid"]

  class ArvadosModel < ActiveRecord::Base
    self.abstract_class = true
    extend HasUuid::ClassMethods
    include CurrentApiClient
    include KindAndEtag
    before_create do |record|
      record.uuid ||= record.class.generate_uuid
      record.owner_uuid ||= system_user_uuid
    end
    serialize :properties, Hash

    def self.to_s
      # Clean up the name of the stub model class so we generate correct UUIDs.
      super.rpartition("::").last
    end
  end

  class Log < ArvadosModel
    def self.log_for(thing, age="old")
      { "#{age}_etag" => thing.etag,
        "#{age}_attributes" => thing.attributes,
      }
    end

    def self.log_create(thing)
      new_log("create", thing, log_for(thing, "new"))
    end

    def self.log_update(thing, start_state)
      new_log("update", thing, start_state.merge(log_for(thing, "new")))
    end

    def self.log_destroy(thing)
      new_log("destroy", thing, log_for(thing, "old"))
    end

    private

    def self.new_log(event_type, thing, properties)
      create!(event_type: event_type,
              event_at: Time.now,
              object_uuid: thing.uuid,
              object_owner_uuid: thing.owner_uuid,
              properties: properties)
    end
  end

  class Link < ArvadosModel
  end

  class User < ArvadosModel
  end

  def sanitize_username(username)
    username.
      sub(/^[^A-Za-z]+/, "").
      gsub(/[^A-Za-z0-9]/, "")
  end

  def usernames_wishlist(user)
    usernames = Hash.new(0)
    usernames[user.email.split("@", 2).first] += 1
    Link.
       where(tail_uuid: user.uuid, link_class: "permission", name: "can_login").
       find_each do |login_perm|
      username = login_perm.properties["username"]
      usernames[username] += 2 if (username and not username.empty?)
    end
    usernames.keys.
      sort_by { |n| -usernames[n] }.
      map { |n| sanitize_username(n) }.
      reject(&:empty?)
  end

  def increment_username(username)
    @username_suffixes[username] += 1
    "%s%i" % [username, @username_suffixes[username]]
  end

  def each_wanted_username(user)
    usernames = usernames_wishlist(user)
    usernames.each { |n| yield n }
    base_username = usernames.first || "arvadosuser"
    loop { yield increment_username(base_username) }
  end

  def recreate_search_index(columns)
    remove_index :users, name: "users_search_index"
    add_index :users, columns, name: "users_search_index"
  end

  def up
    @username_suffixes = Hash.new(1)
    add_column :users, :username, :string, null: true
    add_index :users, :username, unique: true
    recreate_search_index(SEARCH_INDEX_COLUMNS + ["username"])

    [Link, Log, User].each { |m| m.reset_column_information }
    User.validates(:username, uniqueness: true, allow_nil: true)
    User.where(is_active: true).order(created_at: :asc).find_each do |user|
      start_log = Log.log_for(user)
      each_wanted_username(user) do |username|
        user.username = username
        break if user.valid?
      end
      user.save!
      Log.log_update(user, start_log)
    end
  end

  def down
    remove_index :users, :username
    recreate_search_index(SEARCH_INDEX_COLUMNS)
    remove_column :users, :username
  end
end
