module ERDL
  class FileUtilities
    def initialize(opts, subcommand, subcommand_opts, alert = nil)
      @opts = opts
      @subcommand = subcommand
      @subcommand_opts = subcommand_opts
      @alert = alert
      @tmpdir = __dir__ + '/../../tmp/'
      # set string max to zero in case streaming is under 10KB, we still want to make temp file
      OpenURI::Buffer.send :remove_const, 'StringMax'
      OpenURI::Buffer.const_set 'StringMax', 0
      @log_obj = ERDL::Loggly.new
    end

    def filename_from_url(url)
      filename = url.match(/(storage.*.csv(?:.gz|.zip)?)/)
      fail("url #{url} does not contain accepted filename pattern") if filename.nil?
      filename[1].split('/')[-1]
    end

    def stream_file(url)
      case @subcommand
      when 'ftp'
        stream_success =
          if @subcommand_opts[:gzip]
            stream_gzip_to_ftp?(url)
          else
            stream_to_ftp?(url)
          end
        return stream_success
      when 's3'
        stream_success =
          if @subcommand_opts[:gzip]
            stream_gzip_to_s3?(url)
          else
            stream_to_s3?(url)
          end
        return stream_success
      when 'sftp'
        stream_success =
          if @subcommand_opts[:gzip]
            stream_gzip_to_sftp?(url)
          else
            stream_to_sftp?(url)
          end
        return stream_success
      else
        fail "Don't know how to execute: #{@subcommand}"
      end
    end

    def stream_to_ftp?(url, ftp = nil)
      ERDL::Base.log_action(__FILE__, "File is being uploaded to ftp server! URL: #{url}")
      ftp ||= Net::FTP.new(ENV['FTP_HOST'])
      ftp.login(ENV['FTP_USER'], ENV['FTP_PASS'])
      ftp.passive = true
      ftp.chdir(@subcommand_opts[:location]) if @subcommand_opts[:location]
      filename = filename_from_url(url)
      @alert.filename = filename if @alert
      return false if remote_file_exists?(filename, ftp) && @opts[:new_only]
      ftp.putbinaryfile(open(url), filename)
      ftp.close
    end

    def stream_gzip_to_ftp?(url, ftp = nil)
      ERDL::Base.log_action(__FILE__, "Zip file is being uploaded to ftp server! URL: #{url}")
      filename = filename_from_url(url)
      @alert.filename = filename if @alert
      temp_file = Tempfile.new(filename + '.gz')
      # copy file in tmp folder from Youtube
      filepath = @tmpdir + filename
      download = open(url)
      IO.copy_stream(download, filepath)
      write_gzip(temp_file, filepath)
      copy_to_ftp(ftp, filename, temp_file, filepath)
    end

    def stream_to_sftp?(url, sftp = nil)
      ERDL::Base.log_action(__FILE__, "File is being uploaded to sftp server! URL: #{url}")
      remote_path = '/'
      sftp ||= Net::SFTP.start(ENV['SFTP_HOST'], ENV['SFTP_USER'], password: ENV['SFTP_PASS'])
      remote_path = remote_path + @subcommand_opts[:location] + '/' if @subcommand_opts[:location]
      filename = filename_from_url(url)
      @alert.filename = filename if @alert
      return false if remote_file_exists?(filename, sftp, remote_path) && @opts[:new_only]
      remote_file_path = remote_path + filename
      sftp.upload!(open(url), remote_file_path)
      sftp.close_channel
      true
    end

    def stream_gzip_to_sftp?(url, sftp = nil)
      url_file_size = Mechanize.new.head(url)['content-length'].to_i
      ERDL::Base.log_action(__FILE__, "Zip file is being uploaded to sftp server! URL: #{url}")
      filename = filename_from_url(url)
      @alert.filename = filename if @alert
      temp_file = File.open(filename + '.gz', 'w')

      # copy file in tmp folder from Youtube
      filepath = @tmpdir + filename
      download = open(url)
      IO.copy_stream(download, filepath)
      write_gzip(temp_file, filepath)
      # to get original file size of local gzip file
      cmd = 'gzip -dc ' + temp_file.path + '|wc'
      response = `#{cmd}`
      file_size = response.split
      local_file_size = Integer(file_size.last)
      local_gzip_size = Integer(File.size(temp_file))
      sftp_gzip_size = copy_to_sftp(filename, temp_file, filepath, sftp)
      isgzip = true # check_gzip(filename)
      log_file_data(url_file_size, local_file_size, local_gzip_size, sftp_gzip_size, isgzip)

      fail 'error in uploading gzip file to sftp' if
        url_file_size != local_file_size ||
        local_gzip_size != sftp_gzip_size ||
        !isgzip
      true
    end

    # to check gzip file integrity
    def check_gzip(filename)
      # to check gzip file integrity
      isgzip = false
      remote_path = ''
      remote_path = @subcommand_opts[:location] + '/' if @subcommand_opts[:location]
      remote_file_path = remote_path + filename + '.gz'
      gunzip_cmd = 'gunzip -tv ' + remote_file_path
      Net::SSH.start(ENV['SFTP_HOST'], ENV['SFTP_USER'], password: ENV['SFTP_PASS']) do |ssh|
        response = ssh.exec!(gunzip_cmd)
        result_status = response.split
        isgzip = true if result_status.last == 'OK'
      end
      isgzip
    end

    # Log file size related data
    def log_file_data(url_file_size, local_file_size, local_gzip_size, sftp_gzip_size, isgzip)
      ERDL::Base.log_action(__FILE__, "Original file size from URL: #{url_file_size}")
      ERDL::Base.log_action(__FILE__, "Original file size of local gzipped temp file: #{local_file_size}")
      ERDL::Base.log_action(__FILE__, "gzipped file size of local gzipped temp file: #{local_gzip_size}")
      ERDL::Base.log_action(__FILE__, "gzipped file size of sftp uploaded file: #{sftp_gzip_size}")
      ERDL::Base.log_action(__FILE__, "Is Gzip properly generated: #{isgzip}")

      # Log data to Loggly
      @log_obj.log_msg("Log data for account : #{@opts[:account]}, " \
        " Original file size from URL: #{url_file_size}, " \
        " Original file size of local gzipped temp file: #{local_file_size}, " \
        " Gzipped file size of local gzipped temp file: #{local_gzip_size}, " \
        " Gzipped file size of sftp uploaded file: #{sftp_gzip_size}, " \
        " Is Gzip file properly generated: #{isgzip}", 'info')
    end

    def copy_to_ftp(ftp, filename, temp_file, filepath)
      ftp ||= Net::FTP.new(ENV['FTP_HOST'])
      ftp.login(ENV['FTP_USER'], ENV['FTP_PASS'])
      ftp.passive = true
      ftp.chdir(@subcommand_opts[:location]) if @subcommand_opts[:location]
      ftp.putbinaryfile(temp_file, filename + '.gz')
      ftp.close
    ensure
      File.delete(filepath)
      temp_file.unlink
    end

    def copy_to_sftp(filename, temp_file, filepath, sftp = nil)
      remote_path = '/'
      file_size = 0
      sftp ||= Net::SFTP.start(ENV['SFTP_HOST'], ENV['SFTP_USER'], password: ENV['SFTP_PASS'])
      remote_path = remote_path + @subcommand_opts[:location] + '/' if @subcommand_opts[:location]
      remote_file_path = remote_path + filename + '.gz'
      sftp.upload!(temp_file.path, remote_file_path) do |event|
        case event
        when :finish then
          sftp.dir.glob(remote_path, filename + '.gz') do |entry|
            file_size = entry.attributes.size
          end
        end
      end
      sftp.close_channel
      return file_size
    ensure
      File.delete(filepath)
      temp_file.close
      File.unlink(temp_file)
    end

    def stream_to_s3?(url, s3 = nil)
      ERDL::Base.log_action(__FILE__, "File is being uploaded to s3! URL: #{url}")
      filename = filename_from_url(url)
      @alert.filename = filename if @alert
      directory = @subcommand_opts[:location]
      upload_location = get_upload_location(directory, filename)
      s3 ||= Aws::S3::Resource.new
      bucket = s3.bucket(@subcommand_opts[:bucket])
      return false if remote_file_exists?(filename, bucket, @opts[:new_only]) && @opts[:new_only]
      obj = bucket.object(upload_location)
      download = open(url)
      filepath = @tmpdir + filename
      IO.copy_stream(download, filepath)
      # use upload_file method which handles multipart upload automatically
      obj.upload_file(filepath)
    end

    def get_upload_location(directory, filename)
      if @opts[:reportversion] == '1.1'
        date = get_date_from_filename(filename, false)
        upload_location = directory + '/' + date + '/' + filename
      elsif @opts[:category] == 'rev_video' && @opts[:frequency] == 'monthly'
        date = get_date_from_filename(filename, true)
        upload_location = directory + '/' + date + '/' + filename
      elsif @opts[:category] == 'rev_video' && @opts[:frequency] == 'weekly'
        date = get_date_from_filename(filename, true)
        next_monday = Date.parse(date.to_s).next_day.strftime('%Y-%m-%d')
        upload_location = directory + '/' + next_monday + '/' + filename
      else
        upload_location = directory + '/' + filename
      end
      upload_location
    end

    def stream_gzip_to_s3?(url, s3 = nil)
      ERDL::Base.log_action(__FILE__, "File is being uploaded to s3! URL: #{url}")
      filename = filename_from_url(url)
      @alert.filename = filename if @alert
      temp_gz_file = Tempfile.new(filename + '.gz')
      # copy file in tmp folder from Youtube
      download = open(url)
      filepath = @tmpdir + filename + 'temp_to_s3'
      IO.copy_stream(download, filepath)
      write_gzip(temp_gz_file, filepath)
      copy_gzip_to_s3(s3, filename, temp_gz_file)
    end

    def copy_gzip_to_s3(s3, filename, temp_file)
      directory = @subcommand_opts[:location]
      if @opts[:category] == 'asset_conflict'
        current_date = Time.now.strftime('%Y-%m-%d')
        upload_location = directory + '/' + current_date + '/' + filename + '.gz'
      else
        upload_location = directory + '/' + filename + '.gz'
      end
      s3 ||= Aws::S3::Resource.new
      bucket = s3.bucket(@subcommand_opts[:bucket])
      return false if @opts[:new_only] && remote_file_exists?(filename + '.gz', bucket, @opts[:new_only])
      obj = bucket.object(upload_location)
      # use upload_file method which handles multipart upload automatically
      obj.upload_file(temp_file)
    end

    def remote_file_exists?(filename, remote, directory = nil)
      case @subcommand
      when 'ftp'
        remote_files = remote.nlst
        remote_files.each do |remote_file|
          return true if remote_file == filename
        end
        return false
      when 'sftp'
        remote.dir.foreach('/' + directory) do |remote_file|
          return true if remote_file.name == filename
        end
        return false
      when 's3'
        remote_files = remote.objects(prefix: directory)
        remote_files.each do |remote_file|
          # checks all directories to see if files within them contain the file
          return true if remote_file.key.include? filename
        end
        return false
      else
        fail 'Could not check the name of the file'
      end
    end

    def write_gzip(write_file, read_file_path)
      Zlib::GzipWriter.open(write_file) do |gz|
        File.open(read_file_path) do |fp|
          gz.write(fp.read(16 * 1024 * 1024)) until fp.eof
        end
        gz.close
      end
    end

    def get_date_from_filename(filename, is_date_range = false)
      filename_list = filename.split('_')
      # if date range the filename is YouTube_theorchardmusic_W_YYYYMMDD_YYYYMMDD...
      if is_date_range
        date_start = filename_list[4].to_i
      # the filename usually looks like YouTube_theorchardmusic_W_YYYYMMDD...
      else
        date_start = filename_list[3].to_i
      end
      Date.parse(date_start.to_s).strftime('%Y-%m-%d')
    end
  end
end
