require "securerandom"
require "bcrypt"

class SettingsController < ApplicationController
  before_action :authenticate_user!
  before_action :set_main_nav_section
  before_action :set_settings_section, only: [:show, :update, :update_email, :reset_email_password]
  before_action :set_user, only: [:show, :update, :update_email, :reset_email_password]

  def index
    redirect_to setting_path(:profile)
  end

  def show
    if @settings_section == "experiments"
      session[:shared_secret] ||= SecureRandom.base64(12)
    end
  end

  def update
    @user.preferences.merge!(user_params[:preferences] || {})
    @user.display_name = user_params[:display_name]
    @user.avatar_new = user_params[:avatar]

    if @user.save
      if @user.display_name && (@user.display_name != @user.ldap_entry[:display_name])
        LdapManager::UpdateDisplayName.call(dn: @user.dn, display_name: @user.display_name)
      end

      if @user.avatar_new.present?
        LdapManager::UpdateAvatar.call(dn: @user.dn, file: @user.avatar_new)
      end

      redirect_to setting_path(@settings_section), flash: {
        success: 'Settings saved.'
      }
    else
      @validation_errors = @user.errors
      render :show, status: :unprocessable_entity
    end
  end

  def update_email
    if @user.valid_ldap_authentication?(security_params[:current_password])
      if @user.update email: email_params[:email]
        redirect_to setting_path(:account), flash: {
          notice: 'Please confirm your new address using the confirmation link we just sent you.'
        }
      else
        @validation_errors = @user.errors
        render :show, status: :unprocessable_entity
      end
    else
      redirect_to setting_path(:account), flash: {
        error: 'Password did not match your current password. Try again.'
      }
    end
  end

  def reset_email_password
    @user.current_password = security_params[:current_password]

    if @user.valid_ldap_authentication?(@user.current_password)
      @user.current_password = nil
      session[:new_email_password] = generate_email_password
      hashed_password = hash_email_password(session[:new_email_password])
      LdapManager::UpdateEmailPassword.call(dn: @user.dn, password_hash: hashed_password)

      if @user.ldap_entry[:email_maildrop] != @user.address
        LdapManager::UpdateEmailMaildrop.call(dn: @user.dn, address: @user.address)
      end

      redirect_to new_password_services_email_path
    else
      @validation_errors = {
        current_password: [ "Wrong password. Try again!" ]
      }
      render :show, status: :forbidden
    end
  end

  def reset_password
    current_user.send_reset_password_instructions
    sign_out current_user
    msg = "We have sent you an email with a link to reset your password."
    redirect_to check_your_email_path, notice: msg
  end

  def set_nostr_pubkey
    signed_event = nostr_event_params[:signed_event].to_h.symbolize_keys
    is_valid_id  = NostrManager::ValidateId.call(event: signed_event)
    is_valid_sig = NostrManager::VerifySignature.call(event: signed_event)
    is_correct_content = signed_event[:content] == "Connect my public key to #{current_user.address} (confirmation #{session[:shared_secret]})"

    unless is_valid_id && is_valid_sig && is_correct_content
      flash[:alert] = "Public key could not be verified"
      http_status :unprocessable_entity and return
    end

    pubkey_taken = User.all_except(current_user).where(
      ou: current_user.ou, nostr_pubkey: signed_event[:pubkey]
    ).any?

    if pubkey_taken
      flash[:alert] = "Public key already in use for a different account"
      http_status :unprocessable_entity and return
    end

    current_user.update! nostr_pubkey: signed_event[:pubkey]
    session[:shared_secret] = nil

    flash[:success] = "Public key verification successful"
    http_status :ok
  rescue
    flash[:alert] = "Public key could not be verified"
    http_status :unprocessable_entity and return
  end

  # DELETE /settings/nostr_pubkey
  def remove_nostr_pubkey
    current_user.update! nostr_pubkey: nil

    redirect_to setting_path(:experiments), flash: {
      success: 'Public key removed from account'
    }
  end

  private

    def set_main_nav_section
      @current_section = :settings
    end

    def set_settings_section
      @settings_section = params[:section]
      allowed_sections = [
        :profile, :account, :xmpp, :email, :lightning, :remotestorage,
        :experiments
      ]

      unless allowed_sections.include?(@settings_section.to_sym)
        redirect_to setting_path(:profile)
      end
    end

    def set_user
      @user = current_user
    end

    def user_params
      params.require(:user).permit(:display_name, :avatar, preferences: [
        :lightning_notify_sats_received,
        :remotestorage_notify_auth_created,
        :xmpp_exchange_contacts_with_invitees
      ])
    end

    def email_params
      params.require(:user).permit(:email)
    end

    def security_params
      params.require(:user).permit(:current_password)
    end

    def nostr_event_params
      params.permit(signed_event: [
        :id, :pubkey, :created_at, :kind, :tags, :content, :sig
      ])
    end

    def generate_email_password
      characters = [('a'..'z'), ('A'..'Z'), (0..9)].map(&:to_a).flatten
      SecureRandom.random_bytes(16).each_byte.map { |b| characters[b % characters.length] }.join
    end

    def hash_email_password(password)
      salt = BCrypt::Engine.generate_salt
      BCrypt::Engine.hash_secret(password, salt)
    end
end
