require 'fileutils'
require 'erb'
require 'open3'
require 'shell-spinner'
require_relative 'common'

module Kontena
  module Machine
    module Aws
      class MasterProvisioner
        include RandomName
        include Common
        include Machine::CertHelper
        attr_reader :ec2, :http_client, :region

        # @param [String] access_key_id aws_access_key_id
        # @param [String] secret_key aws_secret_access_key
        # @param [String] region
        def initialize(access_key_id, secret_key, region)
          @ec2 = ::Aws::EC2::Resource.new(
            region: region, credentials: ::Aws::Credentials.new(access_key_id, secret_key)
          )
        end

        # @param [Hash] opts
        def run!(opts)
          ssl_cert = nil
          if opts[:ssl_cert]
            abort('Invalid ssl cert') unless File.exists?(File.expand_path(opts[:ssl_cert]))
            ssl_cert = File.read(File.expand_path(opts[:ssl_cert]))
          else
            ShellSpinner "Generating self-signed SSL certificate" do
              ssl_cert = generate_self_signed_cert
            end
          end

          ami = resolve_ami(region)
          abort('No valid AMI found for region') unless ami
          opts[:vpc] = default_vpc.vpc_id unless opts[:vpc]
          if opts[:subnet].nil?
            subnet = default_subnet(opts[:vpc], region+opts[:zone])
          else
            subnet = ec2.subnet(opts[:subnet])
          end
          userdata_vars = {
              ssl_cert: ssl_cert,
              auth_server: opts[:auth_server],
              version: opts[:version],
              vault_secret: opts[:vault_secret],
              vault_iv: opts[:vault_iv]
          }

          security_group = ensure_security_group(opts[:vpc])
          name = generate_name
          ec2_instance = ec2.create_instances({
            image_id: ami,
            min_count: 1,
            max_count: 1,
            instance_type: opts[:type],
            security_group_ids: [security_group.group_id],
            key_name: opts[:key_pair],
            subnet_id: subnet.subnet_id,
            user_data: Base64.encode64(user_data(userdata_vars)),
            block_device_mappings: [
              {
                device_name: '/dev/xvda',
                virtual_name: 'Root',
                ebs: {
                  volume_size: opts[:storage],
                  volume_type: 'gp2'
                }
              }
            ]
          }).first
          ec2_instance.create_tags({
            tags: [
              {key: 'Name', value: name}
            ]
          })
          ShellSpinner "Creating AWS instance #{name.colorize(:cyan)} " do
            sleep 5 until ec2_instance.reload.state.name == 'running'
          end
          master_url = "https://#{ec2_instance.public_ip_address}"
          Excon.defaults[:ssl_verify_peer] = false
          http_client = Excon.new(master_url, :connect_timeout => 10)
          ShellSpinner "Waiting for #{name.colorize(:cyan)} to start" do
            sleep 5 until master_running?(http_client)
          end

          puts "Kontena Master is now running at #{master_url}"
          puts "Use #{"kontena login #{master_url}".colorize(:light_black)} to complete Kontena Master setup"
        end

        ##
        # @param [String] vpc_id
        # @return [Aws::EC2::SecurityGroup]
        def ensure_security_group(vpc_id)
          group_name = "kontena_master"
          sg = ec2.security_groups({
            filters: [
              {name: 'group-name', values: [group_name]},
              {name: 'vpc-id', values: [vpc_id]}
            ]
          }).first
          unless sg
            ShellSpinner "Creating AWS security group" do
              sg = create_security_group(group_name, vpc_id)
            end
          end
          sg
        end

        ##
        # creates security_group and authorizes default port ranges
        #
        # @param [String] name
        # @param [String, NilClass] vpc_id
        # @return Aws::EC2::SecurityGroup
        def create_security_group(name, vpc_id = nil)
          sg = ec2.create_security_group({
            group_name: name,
            description: "Kontena Master",
            vpc_id: vpc_id
          })

          sg.authorize_ingress({
            ip_protocol: 'tcp',
            from_port: 443,
            to_port: 443,
            cidr_ip: '0.0.0.0/0'
          })

          sg.authorize_ingress({
            ip_protocol: 'tcp',
            from_port: 22,
            to_port: 22,
            cidr_ip: '0.0.0.0/0'
          })

          sg
        end

        # @return [String]
        def region
          ec2.client.config.region
        end

        def user_data(vars)
          cloudinit_template = File.join(__dir__ , '/cloudinit_master.yml')
          erb(File.read(cloudinit_template), vars)
        end

        def generate_name
          "kontena-master-#{super}-#{rand(1..99)}"
        end

        def master_running?(http_client)
          http_client.get(path: '/').status == 200
        rescue
          false
        end

        def erb(template, vars)
          ERB.new(template).result(
            OpenStruct.new(vars).instance_eval { binding }
          )
        end
      end
    end
  end
end
