class SlackSmartBot
  # help: ----------------------------------------------
  # help: >*<https://github.com/MarioRuiz/slack-smart-bot#repl|REPLs>*
  # help: `repl`
  # help: `live`
  # help: `irb`
  # help: `repl SESSION_NAME`
  # help: `private repl SESSION_NAME`
  # help: `clean repl SESSION_NAME`
  # help: `repl ENV_VAR=VALUE`
  # help: `repl SESSION_NAME ENV_VAR=VALUE ENV_VAR='VALUE'`
  # help: `repl SESSION_NAME: "DESCRIPTION"`
  # help: `repl SESSION_NAME: "DESCRIPTION" ENV_VAR=VALUE ENV_VAR='VALUE'`
  # help:     Will run all we write as a ruby command and will keep the session values.
  # help:     SESSION_NAME only admits from a to Z, numbers, - and _
  # help:     If no SESSION_NAME supplied it will be treated as a temporary REPL
  # help:     If 'private' specified the repl will be accessible only by you and it will be displayed only to you when `see repls`
  # help:     If 'clean' specified the repl won't pre execute the code written on the .smart-bot-repl file
  # help:     To avoid a message to be treated, start the message with '-'.
  # help:     Send _quit_, _bye_ or _exit_ to finish the session.
  # help:     Send puts, print, p or pp if you want to print out something when using _run repl_ later.
  # help:     After 30 minutes of no communication with the Smart Bot the session will be dismissed.
  # help:     If you declare on your rules file a method called 'project_folder' returning the path for the project folder, the code will be executed from that folder.
  # help:     By default it will be automatically loaded the gems: string_pattern, nice_hash and nice_http
  # help:     To pre-execute some ruby when starting the session add the code to .smart-bot-repl file on the project root folder defined on project_folder
  # help:     If you want to see the methods of a class or module you created use _ls TheModuleOrClass_
  # help:     To see the code of a method: _code TheModuleOrClass.my_method_
  # help:     To see the documentation of a method: _doc TheModuleOrClass.my_method_
  # help:     You can ask *ChatGPT* to help you or suggest any code by sending the message: `? PROMPT`
  # help:     If you send just `?` it will suggest some code to be added.
  # help:     You can supply the Environmental Variables you need for the Session
  # help:     You can add collaborators by sending _add collaborator @USER_ to the session.
  # help:     Examples:
  # help:       _repl CreateCustomer LOCATION=spain HOST='https://10.30.40.50:8887'_
  # help:       _repl CreateCustomer: "It creates a random customer for testing" LOCATION=spain HOST='https://10.30.40.50:8887'_
  # help:       _repl delete_logs_
  # help:       _private repl random-ssn_
  # help:     <https://github.com/MarioRuiz/slack-smart-bot#repl|more info>
  # help: command_id: :repl
  # help:
  def repl(dest, user, session_name, env_vars, rules_file, command, description, type)
    #todo: add more tests
    from = user.name
    team_id_user = "#{user.team_id}_#{user.name}"
    if has_access?(__method__, user)
      if !@repl_sessions.key?(team_id_user)
        save_stats(__method__)
        Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
        Dir.mkdir("#{config.path}/repl/#{@channel_id}") unless Dir.exist?("#{config.path}/repl/#{@channel_id}")

        serialt = Time.now.strftime("%Y%m%d%H%M%S%N")
        if session_name.to_s == ""
          session_name = "#{from}_#{serialt}"
          temp_repl = true
        else
          temp_repl = false
          i = 0
          name = session_name
          while File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.input")
            i += 1
            session_name = "#{name}#{i}"
          end
        end
        @repl_sessions[team_id_user] = {
          name: session_name,
          dest: dest,
          started: Time.now,
          finished: Time.now,
          input: [],
          on_thread: Thread.current[:on_thread],
          thread_ts: Thread.current[:thread_ts],
          collaborators: [],
          user_type: :creator,
          user_creator: team_id_user,
        }

        unless temp_repl
          @repls[session_name] = {
            created: @repl_sessions[team_id_user][:started].to_s,
            accessed: @repl_sessions[team_id_user][:started].to_s,
            creator_name: user.name,
            creator_team_id: user.team_id,
            creator_id: user.id,
            description: description,
            type: type,
            runs_by_creator: 0,
            runs_by_others: 0,
            gets: 0,
          }
          update_repls()
        end
        react :running
        @ts_react ||= {}
        if Thread.current[:ts].to_s == ""
          @ts_react[session_name] = Thread.current[:thread_ts]
        else
          @ts_react[session_name] = Thread.current[:ts]
        end
        @ts_repl ||= {}
        @ts_repl[session_name] = ""
        process_to_run = repl_client(team_id_user, session_name, type, serialt, env_vars)

        unless rules_file.empty? # to get the project_folder
          begin
            eval(File.new(config.path + rules_file).read) if File.exist?(config.path + rules_file)
          end
        end

        file_run_path = "./tmp/repl/#{@channel_id}/#{session_name}.rb"
        if defined?(project_folder)
          Dir.mkdir("#{project_folder}/tmp/") unless Dir.exist?("#{project_folder}/tmp/")
          Dir.mkdir("#{project_folder}/tmp/repl") unless Dir.exist?("#{project_folder}/tmp/repl")
          Dir.mkdir("#{project_folder}/tmp/repl/#{@channel_id}/") unless Dir.exist?("#{project_folder}/tmp/repl/#{@channel_id}/")
          file_run = File.open(file_run_path.gsub("./", "#{project_folder}/"), "w")
          file_run.write process_to_run
          file_run.close
        else
          Dir.mkdir("./tmp/") unless Dir.exist?("./tmp/")
          Dir.mkdir("./tmp/repl") unless Dir.exist?("./tmp/repl")
          Dir.mkdir("./tmp/repl/#{@channel_id}/") unless Dir.exist?("./tmp/repl/#{@channel_id}/")
          file_run = File.open(file_run_path, "w")
          file_run.write process_to_run
          file_run.close
        end

        process_to_run = "ruby #{file_run_path}"

        started = Time.now
        process_to_run = ("cd #{project_folder} && " + process_to_run) if defined?(project_folder)
        stdin, stdout, stderr, wait_thr = Open3.popen3(process_to_run)
        timeout = TIMEOUT_LISTENING # 30 minutes

        file_output_repl = File.open("#{config.path}/repl/#{@channel_id}/#{session_name}.output", "r")
        @repl_sessions[team_id_user][:pid] = wait_thr.pid
        @repl_sessions[team_id_user][:output] ||= []
        @repl_sessions[team_id_user][:input_output] ||= []
        @repl_sessions[team_id_user][:input_output] << "Please chatgpt return code in Ruby language."
        if File.exist?("#{project_folder}/.smart-bot-repl") and type != :private_clean and type != :public_clean
            pre_input = File.read("#{project_folder}/.smart-bot-repl")
            @repl_sessions[team_id_user][:input_output] << "```\n#{pre_input}\n```"
            respond "*Preloaded source code:*\n```\n#{pre_input}\n```"
        end

        while (wait_thr.status == "run" or wait_thr.status == "sleep") and @repl_sessions.key?(team_id_user)
          begin
            if (Time.now - @repl_sessions[team_id_user][:finished]) > timeout
              open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[team_id_user][:name]}.input", "a+") { |f|
                f.puts "quit"
              }
              respond "REPL session finished: #{@repl_sessions[team_id_user][:name]}", dest
              unreact :running, @ts_react[@repl_sessions[team_id_user].name]
              pids = `pgrep -P #{@repl_sessions[team_id_user][:pid]}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
              pids.each do |pid|
                begin
                  Process.kill("KILL", pid)
                rescue
                end
              end
              @repl_sessions[team_id_user][:collaborators].each do |collaborator|
                @repl_sessions.delete(collaborator)
              end
              @repl_sessions.delete(team_id_user)
              break
            end
            sleep 0.2
            resp_repl = file_output_repl.read
            if resp_repl.to_s != ""
              if @ts_repl[@repl_sessions[team_id_user].name].to_s != ""
                unreact(:running, @ts_repl[@repl_sessions[team_id_user].name])
                @ts_repl[@repl_sessions[team_id_user].name] = ""
              end
              if (resp_repl.to_s.lines.count < 60 and resp_repl.to_s.size < 3500) or
                 resp_repl.match?(/^\s*[_\*]*`\w+`/im)
                respond resp_repl, dest
              else
                resp_repl.gsub!(/^\s*```/, "")
                resp_repl.gsub!(/```\s*$/, "")
                send_file(dest, "", "response.rb", "", "text/plain", "ruby", content: resp_repl)
              end
              @repl_sessions[team_id_user][:output] << resp_repl
              @repl_sessions[team_id_user][:input_output] << resp_repl
            end
          rescue Exception => excp
            @logger.fatal excp
          end
        end
      elsif @repl_sessions.key?(team_id_user) and @repl_sessions[team_id_user][:command].to_s == ""
        respond "You are already in a repl on this SmartBot. You need to quit that one before starting a new one."
      else
        @repl_sessions[team_id_user][:finished] = Time.now
        code = @repl_sessions[team_id_user][:command]
        @repl_sessions[team_id_user][:command] = ""
        code.gsub!("\\n", "\n")
        code.gsub!("\\r", "\r")
        # Disabled for the moment since it is deleting lines with '}'
        #code.gsub!(/^\W*$/, "") #to remove special chars from slack when copy/pasting.
        if code.match?(/\A\s*-/i)
          # don't treat
        elsif code.match(/\A\s*\?\s*(.*)\s*\z/im)
          save_stats :open_ai_chat
          #call chatgpt when: ? prompt
          #if no prompt then suggest next code line
          prompt = $1
          prompt = "suggest next code line" if prompt.to_s == ""
          tid = "#{user.team_id}_#{user.name}"
          @repl_sessions[tid][:chat_gpt] ||= {}
          react :speech_balloon
          if !@repl_sessions[tid][:chat_gpt].key?(:client) or @repl_sessions[tid][:chat_gpt][:client].nil?
            get_personal_settings()
            tmp_repl_sessions, message_connect = SlackSmartBot::AI::OpenAI.connect(@repl_sessions, config, @personal_settings, reconnect: true, service: :chat_gpt)
            @repl_sessions[tid][:chat_gpt] = tmp_repl_sessions[tid][:chat_gpt]
            respond message_connect if message_connect
          end
          @repl_sessions[tid][:chat_gpt][:prompts] ||= []
          unless @repl_sessions[tid][:chat_gpt][:client].nil?
            model ||= @repl_sessions[tid][:chat_gpt][:smartbot_model]
            #todo: add source code to the prompt
            @repl_sessions[tid][:chat_gpt][:prompts] << prompt
            @repl_sessions[team_id_user][:input_output] << prompt
            prompts = @repl_sessions[tid][:input_output].join("\n")
            success, res = SlackSmartBot::AI::OpenAI.send_gpt_chat(@repl_sessions[tid][:chat_gpt][:client], model, prompts, @repl_sessions[tid].chat_gpt)
            if success
              @repl_sessions[tid][:chat_gpt][:prompts] << res
              respond "*ChatGPT>* _#{model}_\n#{res}"
              @repl_sessions[team_id_user][:input_output] << res
            elsif res.to_s.strip!=''
              respond "*ChatGPT>* _#{model}_\n#{res}"
            else
              respond "ChatGPT: Sorry, I cannot chat right now. Please try again later."
            end
          end
          unreact :speech_balloon
        elsif code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File.") or
              code.include?("`") or code.include?("exec") or code.include?("spawn") or code.include?("IO.") or
              code.match?(/open3/i) or code.match?(/bundle/i) or code.match?(/gemfile/i) or code.include?("%x") or
              code.include?("ENV") or code.match?(/=\s*IO/) or code.include?("Dir.") or
              code.match?(/=\s*File/) or code.match?(/=\s*Dir/) or code.match?(/<\s*File/) or code.match?(/<\s*Dir/) or
              code.match?(/\w+:\s*File/) or code.match?(/\w+:\s*Dir/) or
              code.match?(/=?\s*(require|load)(\(|\s)/i)
          respond "Sorry I cannot run this due security reasons", dest
        elsif code.match(/\A\s*add\s+collaborator\s+<@(\w+)>\s*\z/i)
          collaborator = $1
           = find_user(collaborator)
          collaborator_name = "#{.team_id}_#{.name}"
          if @repl_sessions.key?(collaborator_name)
            respond "Sorry, <@#{collaborator}> is already in a repl. Please ask her/him to quit it first.", dest
          else
            respond "Collaborator added. Now <@#{collaborator}> can interact with this repl.", dest
            creator = @repl_sessions[team_id_user][:user_creator]
            @repl_sessions[creator][:collaborators] << collaborator_name
            @repl_sessions[collaborator_name] = {
              name: @repl_sessions[team_id_user][:name],
              dest: dest,
              on_thread: Thread.current[:on_thread],
              thread_ts: Thread.current[:thread_ts],
              user_type: :collaborator,
              user_creator: creator,
            }
          end
        else
          if @repl_sessions[team_id_user][:user_type] == :collaborator
            creator = @repl_sessions[team_id_user][:user_creator]
            @repl_sessions[@repl_sessions[team_id_user][:user_creator]][:input] << code
          else
            creator = team_id_user
            @repl_sessions[team_id_user][:input] << code
          end
          if code.include?("```")
            @repl_sessions[creator][:input_output] << code
          else
            @repl_sessions[creator][:input_output] << "```\n#{code}\n```"
          end
          case code
          when /^\s*(quit|exit|bye|bye\s+bot)\s*$/i
            if @repl_sessions[team_id_user][:user_type] == :collaborator
              respond "Collaborator <@#{user.id}> removed.", dest
              @repl_sessions[creator][:collaborators].delete(team_id_user)
              @repl_sessions.delete(team_id_user)
            else
              open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[team_id_user][:name]}.input", "a+") { |f|
                f.puts code
              }
              respond "REPL session finished: #{@repl_sessions[team_id_user][:name]}", dest
              unreact :running, @ts_react[@repl_sessions[team_id_user].name]
              pids = `pgrep -P #{@repl_sessions[team_id_user][:pid]}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
              pids.each do |pid|
                begin
                  Process.kill("KILL", pid)
                rescue
                end
              end
              @repl_sessions[team_id_user][:collaborators].each do |collaborator|
                @repl_sessions.delete(collaborator)
              end
              @repl_sessions.delete(team_id_user)
            end
          else
            if @ts_repl[@repl_sessions[creator].name].to_s == ""
              @ts_repl[@repl_sessions[creator].name] = Thread.current[:ts]
              react :running
            end
            open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[creator][:name]}.input", "a+") { |f|
              f.puts code
            }
          end
        end
      end
    end
  end
end