require 'time'
require 'fileutils'
require 'English'

require 'active_samba_ldap/base'

module ActiveSambaLdap
  module AccountEntry
    NAME_RE_SRC = "(?!\\d)[\\w @_\\-\\.]+"

    def self.included(base)
      super
      base.extend(ClassMethods)
    end

    module ClassMethods
      def ldap_mapping(options={})
        options = default_options.merge(options)
        super(extract_ldap_mapping_options(options))
        belongs_to :primary_group, primary_group_options(options)
        belongs_to :groups, groups_options(options)
      end

      def find_by_uid_number(number)
        options = {:objects => true}
        attribute = "uidNumber"
        value = Integer(number)
        find(:first, :filter => "(#{attribute}=#{value})")
      end

      def unix_object_classes
        if samba4?
          samba_object_classes
        else
          ["inetOrgPerson", "posixAccount"]
        end
      end

      private
      def default_options
        {
          :dn_attribute => default_dn_attribute,
          :ldap_scope => :sub,
          :primary_group_class => default_group_class,
          :primary_group_foreign_key => "gidNumber",
          :primary_group_primary_key => "gidNumber",
          :groups_class => default_group_class,
          :groups_many => "memberUid",
          :prefix => default_prefix,
          :classes => default_classes,
          :recommended_classes => default_recommended_classes,
        }
      end

      def default_dn_attribute
        if samba4?
          "cn"
        else
          "uid"
        end
      end

      def default_group_class
        "Group"
      end

      def primary_group_options(options)
        {
          :class => options[:primary_group_class],
          :foreign_key => options[:primary_group_foreign_key],
          :primary_key => options[:primary_group_primary_key],
        }
      end

      def groups_options(options)
        {
          :class => options[:groups_class],
          :many => options[:groups_many],
        }
      end

      def prepare_create_options(account, options)
        prepare_create_options_for_number("uid_number", account, options)
      end
    end

    def fill_default_values(options={})
      self.cn ||= uid
      self.sn ||= uid
      self.given_name ||= uid
      self.display_name ||= cn
      self.gecos ||= substituted_value(:user_gecos) {cn}
      self.home_directory ||= substituted_value(:user_home_directory)
      self.login_shell ||= self.class.configuration[:user_login_shell]

      options = options.stringify_keys
      password = options["password"]
      change_password(password) if password
      self.user_password ||= "{crypt}x"

      uid_number = options["uid_number"]
      self.change_uid_number(uid_number) if uid_number

      primary_group = options["group"] || retrieve_default_primary_group(options)
      self.primary_group = primary_group if primary_group

      self
    end

    def destroy(options={})
      if options[:removed_from_group]
        groups.each do |group|
          remove_from_group(group)
        end
      end
      dir = home_directory
      need_remove_home_directory =
        options[:remove_home_directory] && !new_entry?
      super()
      if need_remove_home_directory and File.directory?(dir)
        if options[:remove_home_directory_interactive]
          system("rm", "-r", "-i", dir)
        else
          FileUtils.rm_r(dir)
        end
      end
      new_entry?
    end

    def change_uid_number(uid, allow_non_unique=false)
      check_unique_uid_number(uid) unless allow_non_unique
      self.uid_number = Integer(uid)
    end

    def change_password(password)
      hash_type = self.class.configuration[:password_hash_type]
      hashed_password = ActiveLdap::UserPassword.__send__(hash_type, password)
      self.user_password = hashed_password
    end

    def setup_home_directory(options={})
      dest = home_directory
      return unless dest

      FileUtils.mkdir_p(dest)
      mode = options[:mode]
      mode ||= self.class.configuration[:user_home_directory_mode]
      FileUtils.chmod(Integer(mode), dest)
      skel = options[:skeleton_directory]
      skel ||= self.class.configuration[:skeleton_directory]
      FileUtils.cp_r(Dir.glob(File.join(skel, ".*")) +
                     Dir.glob(File.join(skel, "*")) -
                     [File.join(skel, "."), File.join(skel, "..")],
                     dest)
      FileUtils.chown_R(uid_number.to_s, gid_number.to_s, dest)
    end

    private
    def check_unique_uid_number(uid_number)
      ActiveSambaLdap::Base.restart_nscd do
        if self.class.find_by_uid_number(uid_number)
          raise UidNumberAlreadyExists.new(uid_number)
        end
      end
    end

    def substitute_template(template)
      template.gsub(/%U/, uid)
    end

    def substituted_value(key)
      config = self.class.configuration
      if block_given?
        value = config[key.to_sym]
        if value
          substitute_template(value)
        else
          yield
        end
      else
        substitute_template(config[key.to_sym])
      end
    end

    def created_group_name
      uid
    end

    def retrieve_default_primary_group(options={})
      group = nil

      gid_number = options["gid_number"]
      group_class = options["group_class"]
      group_class ||= self.class.associated_class(:primary_group)
      unless gid_number
        if options["create_group"]
          group_name = created_group_name
          if group_class.exists?(group_name)
            group = group_class.find(group_name)
          else
            group = group_class.create(:cn => group_name,
                                       :pool => options["pool"],
                                       :pool_class => options["pool_class"])
          end
        else
          gid_number = default_gid_number
        end
      end
      if gid_number
        group = group_class.find_by_gid_number(gid_number)
      end

      group
    end
  end
end
