module OpenStax::Aws
  class Template

    class TemplateInvalid < StandardError
      attr_reader :template_path, :template_body

      def initialize(msg, template_path, template_body)
        @template_path = template_path
        @template_body = template_body

        super(msg)
      end
    end

    attr_reader :absolute_file_path

    def self.from_absolute_file_path(absolute_file_path)
      new(absolute_file_path: absolute_file_path)
    end

    def self.from_body(body)
      new(body: body)
    end

    def basename
      File.basename(absolute_file_path)
    end

    def extname
      File.extname(absolute_file_path)
    end

    def erb?
      extname == '.erb'
    end

    def body
      return @body unless @body.nil?

      @body = File.read(absolute_file_path)
      @body = ERB.new(@body).tap { |erb| erb.filename = absolute_file_path }.result if erb?
      @body
    end

    def hash
      json_hash || yml_hash || raise("Cannot read template #{absolute_file_path}")
    end

    def valid?
      begin
        validate
      rescue TemplateInvalid
        return false
      end

      true
    end

    def parameter_names
      hash["Parameters"].try(:keys) || []
    end

    def required_capabilities
      has_an_iam_resource = hash["Resources"].any? do |resource_id, resource_body|
        resource_body["Type"].starts_with?("AWS::IAM::")
      end

      # A bit of a cop out to claim named_iam since we haven't checked
      # to see if any of the IAM resources have custom names, but it will
      # work
      has_an_iam_resource ? [:named_iam] : []
    end

    def s3_key
      [
        OpenStax::Aws.configuration.cfn_template_bucket_folder,
        s3_folder,
        basename
      ].compact.join("/")
    end

    def s3_folder
      @unique_s3_folder ||=
        OpenStax::Aws.configuration.fixed_s3_template_folder ||
        Time.now.utc.strftime("%Y%m%d_%H%M%S_#{SecureRandom.hex(4)}")
    end

    def s3_url
      upload_once
      "https://s3.amazonaws.com/#{OpenStax::Aws.configuration.cfn_template_bucket_name}/#{s3_key}"
    end

    def upload_once
      return if @uploaded

      validate
      upload

      @uploaded = true
    end

    def is_sam?
      body.match(/Transform: .?AWS::Serverless/).present?
    end

  protected

    def initialize(absolute_file_path: nil, body: nil)
      raise "One of `absolute_file_path` or `body` must be set" if absolute_file_path.blank? && body.nil?
      @absolute_file_path = absolute_file_path
      @body = body.try(:dup)
    end

    def validate
      begin
        # any region works here
        Aws::CloudFormation::Client.new(region: "us-west-2").validate_template(template_body: body)
      rescue Aws::CloudFormation::Errors::ValidationError => ee
        raise TemplateInvalid.new(basename + ": " + ee.message, absolute_file_path, body)
      end
    end

    def upload
      client = Aws::S3::Client.new(region: OpenStax::Aws.configuration.cfn_template_bucket_region)
      client.put_object({
        body: body,
        bucket: OpenStax::Aws.configuration.cfn_template_bucket_name,
        key: s3_key
      })
    end

    def json_hash
      JSON.parse(body) rescue nil
    end

    def yml_hash
      YAML.load(body) rescue nil
    end

  end
end