module Fog
  module Compute
    class Google
      class Mock
        include Shared

        def handle_disks(options, zone_name)
          disks = []
          i = 0
          options.delete("disks").each do |disk|
            disk = Disk.new(disk) unless disk.is_a? Disk
            disks << {
              "kind" => "compute#attachedDisk",
              "index" => i,
              "type" => "PERSISTENT",
              "mode" => "READ_WRITE",
              "source" => "https://www.googleapis.com/compute/#{api_version}/projects/#{@project}/zones/#{zone_name}/disks/#{disk.name}",
              "deviceName" => "persistent-disk-#{i}",
              "boot" => true
            }
            i += 1
          end
          disks
        end

        # (see Fog::Compute::Google::Real#insert_server)
        def insert_server(server_name, zone_name, options = {}, *_deprecated_args)
          # check that zone exists
          get_zone(zone_name)

          if options["disks"].nil? || options["disks"].empty?
            raise ArgumentError.new "Empty value for field 'disks'. Boot disk must be specified"
          end
          id = Fog::Mock.random_numbers(19).to_s
          data[:servers][server_name] = {
            "kind" => "compute#instance",
            "id" => id,
            "creationTimestamp" => Time.now.iso8601,
            "zone" => "https://www.googleapis.com/compute/#{api_version}/projects/#{@project}/zones/#{zone_name}",
            "status" => "PROVISIONING",
            "name" => server_name,
            "tags" => { "fingerprint" => "42WmSpB8rSM=" },
            "machineType" => "https://www.googleapis.com/compute/#{api_version}/projects/#{@project}/zones/#{zone_name}/machineTypes/#{options['machineType']}",
            "canIpForward" => false,
            "networkInterfaces" => [
              {
                "network" => "https://www.googleapis.com/compute/#{api_version}/projects/#{@project}/global/networks/default",
                "networkIP" => Fog::Mock.random_ip,
                "name" => "nic0",
                "accessConfigs" => [
                  {
                    "kind" => "compute#accessConfig",
                    "type" => "ONE_TO_ONE_NAT",
                    "name" => "External NAT",
                    "natIP" => Fog::Mock.random_ip
                  }
                ]
              }
            ],
            "disks" => handle_disks(options, zone_name),
            "metadata" => {
              "kind" => "compute#metadata",
              "fingerprint" => "5_hasd_gC3E=",
              "items" => [
                {
                  "key" => "ssh-keys",
                  "value" => "sysadmin:ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAgEA1zc7mx+0H8Roywet/L0aVX6MUdkDfzd/17kZhprAbpUXYOILv9AG4lIzQk6xGxDIltghytjfVGme/4A42Sb0Z9LN0pxB4KnWTNoOSHPJtp6jbXpq6PdN9r3Z5NKQg0A/Tfw7gt2N0GDsj6vpK8VbHHdW78JAVUxql18ootJxjaksdocsiHNK8iA6/v9qiLRhX3fOgtK7KpxxdZxLRzFg9vkp8jcGISgpZt27kOgXWhR5YLhi8pRJookzphO5O4yhflgoHoAE65XkfrsRCe0HU5QTbY2jH88rBVkq0KVlZh/lEsuwfmG4d77kEqaCGGro+j1Wrvo2K3DSQ+rEcvPp2CYRUySjhaeLF18UzQLtxNeoN14QOYqlm9ITdkCnmq5w4Wn007MjSOFp8LEq2RekrnddGXjg1/vgmXtaVSGzJAlXwtVfZor3dTRmF0JCpr7DsiupBaDFtLUlGFFlSKmPDVMPOOB5wajexmcvSp2Vu4U3yP8Lai/9/ZxMdsGPhpdCsWVL83B5tF4oYj1HVIycbYIxIIfFqOxZcCru3CMfe9jmzKgKLv2UtkfOS8jpS/Os2gAiB3wPweH3agvtwYAYBVMDwt5cnrhgHYWoOz7ABD8KgmCrD7Y9HikiCqIUNkgUFd9YmjcYi5FkU5rFXIawN7efs341lsdf923lsdf923fs= johndoe@acme"
                }
              ]
            },
            "selfLink" => "https://www.googleapis.com/compute/#{api_version}/projects/#{@project}/zones/#{zone_name}/instances/#{server_name}"
          }

          operation = random_operation
          data[:operations][operation] = {
            "kind" => "compute#operation",
            "id" => Fog::Mock.random_numbers(19).to_s,
            "name" => operation,
            "zone" => "https://www.googleapis.com/compute/#{api_version}/projects/#{@project}/zones/#{zone_name}",
            "operationType" => "insert",
            "targetLink" => "https://www.googleapis.com/compute/#{api_version}/projects/#{@project}/zones/#{zone_name}/instances/#{server_name}",
            "targetId" => id,
            "status" => Fog::Compute::Google::Operation::PENDING_STATE,
            "user" => "123456789012-qwertyuiopasdfghjkl1234567890qwe@developer.gserviceaccount.com",
            "progress" => 0,
            "insertTime" => Time.now.iso8601,
            "startTime" => Time.now.iso8601,
            "selfLink" => "https://www.googleapis.com/compute/#{api_version}/projects/#{@project}/zones/#{zone_name}/operations/#{operation}"
          }

          build_excon_response(data[:operations][operation])
        end
      end

      class Real
        include Shared

        def handle_disks(options)
          disks = []
          # An array of persistent disks. You must supply a boot disk as the first disk in
          # this array and mark it as a boot disk using the disks[].boot property.
          options.delete("disks").each do |disk|
            if disk.is_a? Disk
              disks << disk.get_object
            else
              disks << disk
            end
          end
          disks.first["boot"] = true
          disks
        end

        def format_metadata(metadata)
          { "items" => metadata.map { |k, v| { "key" => k, "value" => v } } }
        end

        ##
        # Create a new instance (virtual machine). You can provision a server
        # using low-level request options via this method (if you don't need to
        # modify low-level request options, consider using the 'servers'
        # collection object instead - see Fog::Compute::Google::Servers).
        # This method should directly take any of the API parameters/options for
        # the GCP endpoint (passed through the +options+ parameter).
        #
        # @see https://cloud.google.com/compute/docs/reference/latest/instances/insert
        # @example minimal server creation
        #   require 'fog/google'
        #   client = Fog::Compute::Google.new
        #   my_result = client.insert_server('my-server',
        #                                    'us-central1-b',
        #                                    'machineType' => 'f1-micro',
        #                                     'disks' => [
        #                                       'initializeParams' => {
        #                                         'sourceImage' => 'projects/debian-cloud/global/images/family/debian-8'
        #                                       }
        #                                     ])
        #
        # @param server_name [String] Name to assign to the created server. Must
        #   be unique within the specified zone.
        # @param zone_name [String] Name of zone containing the created server.
        #   See Fog::Compute::Google#zones for a list of available zones.
        # @param options [Hash] options to use when creating a server. You can
        #  use any of the options documented at
        #  https://cloud.google.com/compute/docs/reference/latest/instances/insert.
        #  Additionally accepts some nonstandard parameters documented below.
        # @option options [String] 'machineType' Required: the machine type to use - see
        #   https://cloud.google.com/compute/docs/machine-types for a list.
        # @option options [String] 'externalIp' An IP address to assign to the
        #   server. See
        #   https://cloud.google.com/compute/docs/configure-instance-ip-addresses
        #   for more information about assigning instance ip addresses.
        # @option options [Boolean] 'auto_restart' (false) if true, then the
        #   server will be automatically restarted if terminated by Google Compute
        #   Engine. Note that this value cannot be set to true if the VM is
        #   preemptible.
        # @option options [Boolean] 'preemptible' (false) if true, then the
        #   created server will be preemptible and therefore only run for at
        #   most 24 hours. See
        #   https://cloud.google.com/compute/docs/instances/preemptible for more
        #   information about preemptible instances.
        # @option options ["MIGRATE", "TERMINATE"] 'on_host_maintenance' if
        #   "MIGRATE", then the VM will be migrated to different hardware during
        #   a maintenance event. If "TERMINATE", then the VM will be terminated.
        #   Note that if the VM is preemptible, then this option MUST be
        #   "TERMINATE".
        # @option options [Boolean] 'can_ip_forward' (false) if true, then the
        #   created instance will allow forwarding packets that don't originate
        #   with the assigned host ip address. See
        #   https://cloud.google.com/compute/docs/networking#canipforward for
        #   more information.
        # @return [Excon::Response] response object that represents the result.
        def insert_server(server_name, zone_name, options = {}, *deprecated_args)
          if deprecated_args.length > 0 || !options.is_a?(Hash)
            raise ArgumentError.new "Too many parameters specified. This may be the cause of code written for an outdated"\
                " version of fog. Usage: server_name, zone_name, [options]"
          end
          api_method = @compute.instances.insert
          parameters = {
            "project" => @project,
            "zone" => zone_name
          }
          body_object = { :name => server_name }

          body_object["machineType"] = @api_url + @project + "/zones/#{zone_name}/machineTypes/#{options.delete 'machineType'}"
          network = nil
          if options.key? "network"
            network = options.delete "network"
          else
            network = GOOGLE_COMPUTE_DEFAULT_NETWORK
          end

          # ExternalIP is default value for server creation
          access_config = { "type" => "ONE_TO_ONE_NAT", "name" => "External NAT" }
          # leave natIP undefined to use an IP from a shared ephemeral IP address pool
          if options.key? "externalIp"
            access_config["natIP"] = options.delete "externalIp"
            # If set to 'false', that would mean user does no want to allocate an external IP
            access_config = nil if access_config["natIP"] == false
          end

          networkInterfaces = []
          unless network.nil?
            networkInterface = { "network" => @api_url + @project + "/global/networks/#{network}" }
            networkInterface["accessConfigs"] = [access_config] if access_config
            networkInterfaces << networkInterface
          end

          scheduling = {
            "automaticRestart" => false,
            "onHostMaintenance" => "MIGRATE",
            "preemptible" => false
          }
          if options.key? "auto_restart"
            scheduling["automaticRestart"] = options.delete "auto_restart"
            scheduling["automaticRestart"] = scheduling["automaticRestart"].class == TrueClass
          end
          if options.key? "preemptible"
            scheduling["preemptible"] = options.delete "preemptible"
            scheduling["preemptible"] = scheduling["preemptible"].class == TrueClass
            # If we don't have an explicit value for "on_host_maintenance, then
            # force the value to be 'TERMINATE' as it's the default and
            # only-accepted value for preemptible vms
            scheduling["onHostMaintenance"] = "TERMINATE" unless options.key? "on_host_maintenance"
          end
          if options.key? "on_host_maintenance"
            ohm = options.delete "on_host_maintenance"
            scheduling["onHostMaintenance"] = (ohm.respond_to?("upcase") &&
                    ohm.upcase == "MIGRATE" && "MIGRATE") || "TERMINATE"
          end
          body_object["scheduling"] = scheduling

          # @see https://developers.google.com/compute/docs/networking#canipforward
          if options.key? "can_ip_forward"
            body_object["canIpForward"] = options.delete "can_ip_forward"
          end

          # TODO: add other networks
          body_object["networkInterfaces"] = networkInterfaces

          if options["disks"].nil? || options["disks"].empty?
            raise ArgumentError.new "Empty value for field 'disks'. Boot disk must be specified"
          end
          body_object["disks"] = handle_disks(options)

          options["metadata"] = format_metadata options["metadata"] if options["metadata"]

          body_object["tags"] = { "items" => options.delete("tags") } if options["tags"]

          body_object.merge!(options) # Adds in all remaining options that weren't explicitly handled.

          request(api_method, parameters, body_object = body_object)
        end
      end
    end
  end
end
