require 'optparse'
require "ggggsss/version"

require 'aws-sdk-s3'

module Ggggsss
  class Command
    attr_reader :bucket_name, :keyword, :path

    def initialize(args)
      @bucket_name = ''
      opt_parser = OptionParser.new
      opt_parser.on('-b BUCKET_NAME', '--bucket-name BUCKET_NAME') {|name| @bucket_name = name }
      opt_parser.banner += ' KEYWORD PATH_PREFIX'

      opt_parser.parse!(args)

      @keyword, @path = *args
    end

    def execute!
      fetcher = S3Fetcher.new(@bucket_name, @path)
      fetcher.fetch!

      fetcher.objects.each do |s3_object|
        collector = LineCollector.new(s3_object.body, @keyword)
        collector.collect!

        printer = ResultPrinter.new(s3_object.key, collector.results)
        printer.print
      rescue => e
        puts "#{s3_object.key}: #{e.message}"
      end
    end
  end

  class S3Object
    attr_reader :key, :body

    def initialize(key:, body:)
      @key = key
      @body = body
    end
  end

  class S3Fetcher
    attr_reader :objects

    def initialize(bucket_name, path)
      @bucket_name = bucket_name
      @path = path
      @objects = []
    end

    def fetch!
      s3 = Aws::S3::Resource.new
      bucket = s3.bucket(@bucket_name)
      bucket.objects(prefix: @path).each do |object_summary|
        object_output = object_summary.get
        @objects << S3Object.new(key: object_summary.key, body: object_output.body)
      end
    end
  end

  class ResultLine
    attr_reader :line_no, :line

    def initialize(line_no:, line:)
      @line_no = line_no
      @line = line
    end

    def ==(others)
      line_no == others.line_no && line == others.line
    end
  end

  class LineCollector
    attr_reader :results

    def initialize(io, keyword)
      @io = io
      @keyword = Regexp.new(keyword)
      @results = []
    end

    def collect!
      @io.read.each_line.with_index(1) do |line, line_no|
        @results << ResultLine.new(line_no: line_no, line: line.chomp) if @keyword.match?(line)
      end
    end
  end

  class ResultPrinter
    def initialize(filename, results)
      @filename = filename
      @results = results
    end

    def print
      @results.each do |result|
        puts "#{@filename}:#{result.line_no}:#{result.line}"
      end
    end
  end
end