class RedmineLeSetting < ActiveRecord::Base
  unloadable

  @@skip_callbacks = false
  CONFIG_YAML = RAILS_ROOT + "/config/configuration.yml"

  belongs_to :auth_source

  validates_presence_of :auth_source_id, :admin_account, :admin_password
  
  def self.instance
    @instance ||= (first || new)
  end

  def self.[](name)
    instance[name]
  end

  def self.[]=(name, value)
    instance[name] = value
  end

  def self.method_missing(name, *args)
    instance.send(name, *args)
  end

  def self.transaction(&block)
    @xml_changes = {}
    @text_changes = {}
    @file_changes = {}
    @error_list = {}
    super(&block)
  rescue => err
    @xml_changes.dup.each {|filename, xpath_values|
      replace_xml(filename, *xpath_values)
    }
    @xml_changes = {}
    @text_changes.dup.each {|filename, regexp_values|
      replace_text(filename, *regexp_values)
    }
    @text_changes = {}
    @file_changes.each {|filename, content|
      File.open(filename, "w") {|f| f.write(content)}
    }
    @file_changes = {}
    raise err
  end

  def self.skip_callbacks(&block)
    @@skip_callbacks = true
    result = yield
    @@skip_callbacks = false
    result
  end

  def search_ldap_user(username = nil)
    username ||= admin_account
    source = LdapUser.auth_source = RedmineLeSetting.auth_source
    LdapUser.find(:filter => [source.attr_login, username])
  end

  def search_dn(username = nil)
    user = search_ldap_user(username)
    user && user.dn.to_s
  end

  def base64_admin_password
    admin_password && Base64.encode64(admin_password).chomp
  end

  def config
    @config ||= File.file?(CONFIG_YAML) ? YAML.load_file(CONFIG_YAML) : {}
  end

  def smtp_settings
    conf = config["default"] ||= {}
    conf = conf["email_delivery"] ||= {}
    conf["delivery_method"] ||= :smtp
    conf["smtp_settings"] ||= {}
  end

  def smtp_settings_changed?
    !!@smtp_settings_changed
  end

  def smtp_server
    smtp_settings["address"]
  end

  def smtp_server=(value)
    set_smtp_setting("address", value)
  end

  def smtp_port
    smtp_settings["port"]
  end

  def smtp_port=(value)
    set_smtp_setting("port", value.to_i.nonzero?)
  end

  def smtp_user
    smtp_settings["user_name"]
  end

  def smtp_user=(value)
    set_smtp_setting("user_name", value)
  end

  def smtp_password
    smtp_settings["password"]
  end

  def smtp_password=(value)
    set_smtp_setting("password", value)
  end

  def smtp_sender
    Setting.mail_from
  end

  def smtp_sender=(value)
    @smtp_settings_changed = true if smtp_sender != value
    @smtp_sender = value
  end

  protected
  def validate_on_update
    return unless use_external_ldap
    return unless admin_account_changed? || admin_password_changed?
    user = search_ldap_user
    valid = user && begin
      user.bind(admin_password)
    rescue ActiveLdap::AuthenticationError, ActiveLdap::LdapError::UnwillingToPerform
      false
    end
    errors.add_to_base(l(:notice_account_invalid_creditentials)) unless valid
  end

  def before_save
    return if @@skip_callbacks
    change_smtp_settings if smtp_settings_changed?
    change_admin_account if admin_account_changed? || admin_password_changed?
  end

  private
  def change_smtp_settings
    if @smtp_sender && @smtp_sender != smtp_sender
      Setting.mail_from = @smtp_sender
    end

    self.smtp_port ||= 25

    if smtp_user.blank? || smtp_password.blank?
      %w[authentication user_name password].each {|key|
        smtp_settings.delete(key)
      }
    else
      smtp_settings["authentication"] = :login
      smtp_settings["domain"] ||= "localhost"
    end

    self.class.replace_xml(
      RedmineLe::HOME + "/jenkins/home/hudson.tasks.Mailer.xml",
      ["//smtpHost", smtp_server],
      ["//adminAddress", smtp_sender],
      ["//smtpAuthUsername", smtp_user],
      ["//smtpAuthPassword", smtp_password]
    )
    self.class.write_file(CONFIG_YAML, YAML.dump(config))
    @smtp_settings_changed = nil
  end

  def change_admin_account
    dn = use_external_ldap ? search_dn : auth_source.account

    Repository::Subversion.all(:conditions => [
     "url LIKE ? AND login = ?", 'http%://localhost%/svn/%', admin_account_was
    ]).each {|repos|
      repos.login = admin_account
      repos.password = admin_password
      repos.save!
    }

    auth_source.account = dn
    auth_source.account_password = admin_password
    auth_source.save!

    base = "//credentials/entry/string[contains(., '//localhost:#{RedmineLe::HTTP_PORT}')]/.."
    self.class.replace_xml(
      RedmineLe::HOME + "/jenkins/home/hudson.scm.SubversionSCM.xml",
      [base + "//userName", admin_account],
      [base + "//password", base64_admin_password]
    )

    self.class.replace_xml(
      RedmineLe::HOME + "/jenkins/home/config.xml",
      ["//securityRealm/managerDN", dn],
      ["//securityRealm/managerPassword", base64_admin_password]
    )

    self.class.replace_text(
      RedmineLe::HOME + "/apache/conf/conf.d/subversion.conf",
      [/AuthLDAPBindDN\s+(.*)$/, dn],
      [/AuthLDAPBindPassword\s+(.*)$/, admin_password]
    )
  end

  def self.replace_xml(filename, *xpath_values)
    doc = REXML::Document.new(File.new(filename))

    xpath_values.each {|xpath, value|
      unless elem = doc.elements[xpath]
        (@error_list[filename] ||= []) << xpath if @error_list
        next
      end
      oldvalue = elem.text
      elem.text = value
      (@xml_changes[filename] ||= []) << [xpath, oldvalue] if @xml_changes
    }

    File.open(filename, "w") {|f| doc.write(f)}
  end

  def self.replace_text(filename, *regexp_values)
    content = File.read(filename)

    regexp_values.each {|regexp, value|
      begin
        content[regexp, 1] = value
        (@text_changes[filename] ||= []) << [regexp, $1] if @text_changes
      rescue IndexError
        (@error_list[filename] ||= []) << regexp.inspect if @error_list
      end
    }

    File.open(filename, "w") {|f| f.write(content)}
  end

  def self.write_file(filename, content)
    if @file_changes
      @file_changes[filename] = File.file?(filename) ? File.read(filename) : ""
    end
    File.open(filename, "w") {|f| f.write(content)}
  end

  def set_smtp_setting(key, value)
    v = "@smtp_#{key}_was"
    v = instance_variable_get(v) || instance_variable_set(v, smtp_settings[key])
    @smtp_settings_changed = true if v != value
    value.blank? ? smtp_settings.delete(key) : (smtp_settings[key] = value)
  end
end
