require 'fog/compute/models/server'
require 'net/ssh/proxy/command'

module Fog
  module Compute
    class Google
      class Server < Fog::Compute::Server
        identity :name

        attribute :kind
        attribute :id
        attribute :can_ip_forward, :aliases => 'canIpForward'
        attribute :creation_timestamp, :aliases => 'creationTimestamp'
        attribute :description
        attribute :disks
        attribute :machine_type, :aliases => 'machineType'
        attribute :metadata
        attribute :network_interfaces, :aliases => 'networkInterfaces'
        attribute :scheduling
        attribute :self_link, :aliases => 'selfLink'
        attribute :service_accounts, :aliases => 'serviceAccounts'
        attribute :state, :aliases => 'status'
        attribute :status_message, :aliases => 'statusMessage'
        attribute :tags
        attribute :zone, :aliases => :zone_name

        # These attributes are not available in the representation of an 'instance' returned by the GCE API.
        # They are useful only for the create process
        attribute :network, :aliases => 'network'
        attribute :external_ip, :aliases => 'externalIP'
        attribute :auto_restart
        attribute :on_host_maintenance
        attribute :preemptible

        # Security account scope aliases used by official gcloud utility
        # List derived from 'gcloud compute instances create --help'
        GCE_SCOPE_ALIASES = {
              'compute-ro' => 'compute.readonly',
              'compute-rw' => 'compute',
              'computeaccounts-ro' => 'computeaccounts.readonly',
              'computeaccounts-rw' => 'computeaccounts',
              'logging-write' => 'logging.write',
              'sql' => 'sqlservice',
              'sql-admin' => 'sqlservice.admin',
              'storage-full' => 'devstorage.full_control',
              'storage-ro' => 'devstorage.read_only',
              'storage-rw' => 'devstorage.read_write'
            }

        def image_name=(args)
          Fog::Logger.deprecation("image_name= is no longer used [light_black](#{caller.first})[/]")
        end

        def image_name
          boot_disk = disks.first
          unless boot_disk.is_a?(Disk)
            source = boot_disk['source']
            match = source.match(%r{/zones/(.*)/disks/(.*)$})
            boot_disk = service.disks.get match[2], match[1]
          end
          boot_disk.source_image.nil? ? nil : boot_disk.source_image
        end

        def kernel=(args)
          Fog::Logger.deprecation("kernel= is no longer used [light_black](#{caller.first})[/]")
        end

        def kernel
          Fog::Logger.deprecation("kernel is no longer used [light_black](#{caller.first})[/]")
          nil
        end

        def flavor_id
          machine_type
        end

        def flavor_id=(flavor_id)
          machine_type=flavor_id
        end

        def destroy(async=true)
          requires :name, :zone

          data = service.delete_server(name, zone_name)
          operation = Fog::Compute::Google::Operations.new(:service => service).get(data.body['name'], data.body['zone'])
          unless async
            operation.wait_for { ready? }
          end
          operation
        end

        # not used since v1
        def image
          Fog::Logger.deprecation("Server.image is deprecated, get source_image from boot disk")
          service.get_image(self.image_name.split('/')[-1])
        end

        def public_ip_address
          ip = nil
          if self.network_interfaces.respond_to? :each
            self.network_interfaces.each do |netif|
              if netif["accessConfigs"].respond_to? :each
                netif["accessConfigs"].each do |access_config|
                  if access_config["name"] == "External NAT"
                    ip = access_config['natIP']
                  end
                end
              end
            end
          end

          ip
        end

        def private_ip_address
          ip = nil
          if self.network_interfaces.respond_to? :first
            ip = self.network_interfaces.first['networkIP']
          end
          ip
        end

        def addresses
          [private_ip_address, public_ip_address]
        end

        def attach_disk(disk, options = {})
          requires :identity, :zone

          data = service.attach_disk(identity, zone_name, disk, options)
          Fog::Compute::Google::Operations.new(:service => service).get(data.body['name'], data.body['zone'])
        end

        def detach_disk(device_name)
          requires :identity, :zone

          data = service.detach_disk(identity, zone, device_name)
          Fog::Compute::Google::Operations.new(:service => service).get(data.body['name'], data.body['zone'])
        end

        def reboot
          requires :identity, :zone

          data = service.reset_server(identity, zone_name)
          Fog::Compute::Google::Operations.new(:service => service).get(data.body['name'], data.body['zone'])
        end

        def start
          requires :identity, :zone

          data = service.start_server(identity, zone_name)
          Fog::Compute::Google::Operations.new(:service => service).get(data.body['name'], data.body['zone'])
        end

        def stop
          requires :identity, :zone

          data = service.stop_server(identity, zone_name)
          Fog::Compute::Google::Operations.new(:service => service).get(data.body['name'], data.body['zone'])
        end

        def serial_port_output
          requires :identity, :zone

          data = service.get_server_serial_port_output(identity, zone_name)
          data.body['contents']
        end

        def set_disk_auto_delete(auto_delete, device_name)
          requires :identity, :zone

          data = service.set_server_disk_auto_delete(identity, zone_name, auto_delete, device_name)
          Fog::Compute::Google::Operations.new(:service => service).get(data.body['name'], data.body['zone'])
        end

        def set_scheduling(on_host_maintenance, automatic_restart, preemptible)
          requires :identity, :zone

          data = service.set_server_scheduling(identity, zone_name, on_host_maintenance, automatic_restart, preemptible)
          Fog::Compute::Google::Operations.new(:service => service).get(data.body['name'], data.body['zone'])
        end

        def set_metadata(metadata = {})
          requires :identity, :zone

          data = service.set_metadata(identity, zone_name, self.metadata['fingerprint'], metadata)
          Fog::Compute::Google::Operations.new(:service => service).get(data.body['name'], data.body['zone'])
        end

        def set_tags(tags = [])
          requires :identity, :zone

          data = service.set_tags(identity, zone_name, self.tags['fingerprint'], tags)
          Fog::Compute::Google::Operations.new(:service => service).get(data.body['name'], data.body['zone'])
        end

        def ready?
          self.state == RUNNING
        end

        def zone_name
          zone.nil? ? nil : zone.split('/')[-1]
        end

        def add_ssh_key username, key
          if self.metadata.nil?
            self.metadata = Hash.new("")
          end

          # You can have multiple SSH keys, seperated by newlines.
          # https://developers.google.com/compute/docs/console?hl=en#sshkeys
          if !self.metadata["sshKeys"]
            self.metadata["sshKeys"] = ""
          end

          if !self.metadata["sshKeys"].empty?
            self.metadata["sshKeys"] += "\n"
          end

          self.metadata["sshKeys"] += "#{username}:#{key.strip}"

          return self.metadata
        end

        def map_service_accounts(scope_array)

          scope_array_expanded = scope_array.map do |e|
            if GCE_SCOPE_ALIASES[e]
              GCE_SCOPE_ALIASES[e]
            else
              e
            end
          end

          scope_array_finalized = scope_array_expanded.map do |e|
            if e.start_with?("https://")
              e
            else
              "https://www.googleapis.com/auth/#{e}"
            end
          end

          return scope_array_finalized
        end

        def reload
          data = service.get_server(self.name, zone_name).body
          self.merge_attributes(data)
        end

        def save
          requires :name
          requires :machine_type
          requires :zone_name
          requires :disks

          if not service.zones.find{ |zone| zone.name == self.zone_name }
            raise ArgumentError.new "#{self.zone_name.inspect} is either down or you don't have permission to use it."
          end

          self.add_ssh_key(self.username, self.public_key) if self.public_key

          options = {
              'machineType' => machine_type,
              'networkInterfaces' => network_interfaces,
              'network' => network,
              'externalIp' => external_ip,
              'disks' => disks,
              'metadata' => metadata,
              'serviceAccounts' => service_accounts,
              'tags' => tags,
              'auto_restart' => auto_restart,
              'on_host_maintenance' => on_host_maintenance,
              'preemptible' => preemptible,
              'can_ip_forward' => can_ip_forward
          }.delete_if {|key, value| value.nil?}

          if service_accounts
            options['serviceAccounts'] = [{
              "kind" => "compute#serviceAccount",
              "email" => "default",
              "scopes" => self.map_service_accounts(service_accounts)
            }]
          end

          data = service.insert_server(name, zone_name, options)
          operation = Fog::Compute::Google::Operations.new(:service => service).get(data.body['name'], data.body['zone'])
          operation.wait_for { !pending? }
          reload
        end
      end
    end
  end
end
