# frozen_string_literal: true
require 'aws-sdk'
require 'erb'
require 'hako'
require 'hako/script'

module Hako
  module Scripts
    class NginxFront < Script
      S3Config = Struct.new(:region, :bucket, :prefix) do
        # @param [Hash] options
        def initialize(options)
          self.region = options.fetch('region')
          self.bucket = options.fetch('bucket')
          self.prefix = options.fetch('prefix', nil)
        end

        def key(app_id)
          if prefix
            "#{prefix}/#{app_id}.conf"
          else
            "#{app_id}.conf"
          end
        end
      end

      # @param [Hash<String, Container>] containers
      # @return [nil]
      def deploy_starting(containers)
        front = containers.fetch('front')
        front.definition['env'].merge!(
          'AWS_DEFAULT_REGION' => @s3.region,
          'S3_CONFIG_BUCKET' => @s3.bucket,
          'S3_CONFIG_KEY' => @s3.key(@app.id),
        )
        front.links << link_app
      end

      # @param [Hash<String, Container>] containers
      # @param [Fixnum] front_port
      # @return [nil]
      def deploy_started(containers, front_port)
        app = containers.fetch('app')
        front = containers.fetch('front')
        front.definition['port_mappings'] << port_mapping(front_port)
        upload_config(generate_config(app.port))
        Hako.logger.debug "Uploaded front configuration to s3://#{@s3.bucket}/#{@s3.key(@app.id)}"
      end

      private

      # @param [Hash] options
      # @return [nil]
      def configure(options)
        super
        @options = options
        @options['locations'] ||= { '/' => {} }
        @s3 = S3Config.new(@options.fetch('s3'))
      end

      # @return [String]
      def link_app
        'app:app'
      end

      # @param [Fixnum] front_port
      # @return [Hash]
      def port_mapping(front_port)
        { container_port: 80, host_port: front_port, protocol: 'tcp' }
      end

      # @param [Fixnum] app_port
      # @return [String]
      def generate_config(app_port)
        Generator.new(@options, app_port).render
      end

      # @return [Hash]
      def upload_config(front_conf)
        if @dry_run
          Hako.logger.info "Generated configuration:\n#{front_conf}"
        else
          s3_client.put_object(
            body: front_conf,
            bucket: @s3.bucket,
            key: @s3.key(@app.id),
          )
        end
      end

      # @return [Aws::S3::Client]
      def s3_client
        @s3_client ||= Aws::S3::Client.new(region: @s3.region)
      end

      class Generator
        # @param [Hash] options
        # @param [Fixnum] app_port
        def initialize(options, app_port)
          @options = options
          @app_port = app_port
        end

        # @return [String]
        def render
          ERB.new(File.read(nginx_conf_erb), nil, '-').result(binding)
        end

        private

        # @return [String]
        def listen_spec
          "app:#{@app_port}"
        end

        # @return [String]
        def templates_directory
          File.expand_path('../../templates', __FILE__)
        end

        # @return [String]
        def nginx_conf_erb
          File.join(templates_directory, 'nginx.conf.erb')
        end

        # @return [String]
        def nginx_location_conf_erb
          File.join(templates_directory, 'nginx.location.conf.erb')
        end

        # @return [Hash<String, Location>]
        def locations
          locs = @options.fetch('locations').dup
          locs.keys.each do |k|
            locs[k] = Location.new(locs[k])
          end
          locs
        end

        # @return [String, nil]
        def client_max_body_size
          @options.fetch('client_max_body_size', nil)
        end

        # @param [String] listen_spec
        # @param [Location] location
        # @return [String]
        def render_location(listen_spec, location)
          ERB.new(File.read(nginx_location_conf_erb), nil, '-').result(binding).each_line.map do |line|
            "    #{line}"
          end.join('')
        end

        class Location
          # @param [Hash] config
          def initialize(config)
            @config = config
          end

          # @return [Array<String>, nil]
          def allow_only_from
            allow = @config.fetch('allow_only_from', nil)
            if allow
              allow.flatten
            end
          end
        end
      end
    end
  end
end