require 'base64'
WHITE_SPACE = ' '.freeze

module Fog
  module Compute
    class AzureRM
      # This class provides the actual implementation for service calls.
      class Real
        def create_virtual_machine(vm_config, async = false)
          msg = "Creating Virtual Machine #{vm_config[:name]} in Resource Group #{vm_config[:resource_group]}."
          Fog::Logger.debug msg
          virtual_machine = Azure::ARM::Compute::Models::VirtualMachine.new

          unless vm_config[:availability_set_id].nil?
            sub_resource = MsRestAzure::SubResource.new
            sub_resource.id = vm_config[:availability_set_id]
            virtual_machine.availability_set = sub_resource
          end

          string_data = vm_config[:custom_data]
          string_data = WHITE_SPACE if string_data.nil?
          encoded_data = Base64.strict_encode64(string_data)
          virtual_machine.hardware_profile = define_hardware_profile(vm_config[:vm_size])
          virtual_machine.storage_profile = define_storage_profile(vm_config[:name],
                                                                   vm_config[:storage_account_name],
                                                                   vm_config[:publisher],
                                                                   vm_config[:offer],
                                                                   vm_config[:sku],
                                                                   vm_config[:version],
                                                                   vm_config[:vhd_path],
                                                                   vm_config[:os_disk_caching],
                                                                   vm_config[:platform],
                                                                   vm_config[:resource_group],
                                                                   vm_config[:managed_disk_storage_type],
                                                                   vm_config[:os_disk_size],
                                                                   vm_config[:location])

          virtual_machine.os_profile = if vm_config[:platform].casecmp(WINDOWS).zero?
                                         define_windows_os_profile(vm_config[:name],
                                                                   vm_config[:username],
                                                                   vm_config[:password],
                                                                   vm_config[:provision_vm_agent],
                                                                   vm_config[:enable_automatic_updates],
                                                                   encoded_data)
                                       else
                                         define_linux_os_profile(vm_config[:name],
                                                                 vm_config[:username],
                                                                 vm_config[:password],
                                                                 vm_config[:disable_password_authentication],
                                                                 vm_config[:ssh_key_path],
                                                                 vm_config[:ssh_key_data],
                                                                 encoded_data)
                                       end
          virtual_machine.network_profile = define_network_profile(vm_config[:network_interface_card_ids])
          virtual_machine.location = vm_config[:location]
          virtual_machine.tags = vm_config[:tags]
          begin
            response = if async
                         @compute_mgmt_client.virtual_machines.create_or_update_async(vm_config[:resource_group], vm_config[:name], virtual_machine)
                       else
                         @compute_mgmt_client.virtual_machines.create_or_update(vm_config[:resource_group], vm_config[:name], virtual_machine)
                       end
          rescue MsRestAzure::AzureOperationError => e
            raise_azure_exception(e, msg)
          end
          unless async
            unless vm_config[:vhd_path].nil? || vm_config[:managed_disk_storage_type].nil?
              delete_generalized_image(vm_config[:resource_group], vm_config[:name])
              delete_storage_account_or_container(vm_config[:resource_group], vm_config[:storage_account_name], vm_config[:name])
            end
          end
          Fog::Logger.debug "Virtual Machine #{vm_config[:name]} Created Successfully." unless async
          response
        end

        private

        def define_hardware_profile(vm_size)
          hw_profile = Azure::ARM::Compute::Models::HardwareProfile.new
          hw_profile.vm_size = vm_size
          hw_profile
        end

        def image_reference(publisher, offer, sku, version)
          image_reference = Azure::ARM::Compute::Models::ImageReference.new
          image_reference.publisher = publisher
          image_reference.offer = offer
          image_reference.sku = sku
          image_reference.version = version
          image_reference
        end

        def define_storage_profile(vm_name, storage_account_name, publisher, offer, sku, version, vhd_path, os_disk_caching, platform, resource_group, managed_disk_storage_type, os_disk_size, location)
          storage_profile = Azure::ARM::Compute::Models::StorageProfile.new
          storage_profile.image_reference = image_reference(publisher, offer, sku, version) if vhd_path.nil?
          os_disk = Azure::ARM::Compute::Models::OSDisk.new

          new_vhd_path = copy_vhd_to_storage_account(resource_group, storage_account_name, vhd_path, location, vm_name) unless vhd_path.nil?

          if managed_disk_storage_type.nil?
            vhd = Azure::ARM::Compute::Models::VirtualHardDisk.new
            vhd.uri = get_blob_endpoint(storage_account_name) + "/vhds/#{vm_name}_os_disk.vhd"

            unless vhd_path.nil?
              img_vhd = Azure::ARM::Compute::Models::VirtualHardDisk.new
              img_vhd.uri = new_vhd_path
              os_disk.image = img_vhd
            end

            os_disk.vhd = vhd
          else
            unless vhd_path.nil?
              image_obj = create_generalized_image(image_config_params(location, new_vhd_path, platform, resource_group, vm_name))
              storage_profile.image_reference = Azure::ARM::Compute::Models::ImageReference.new
              storage_profile.image_reference.id = image_obj.id
            end

            managed_disk = Azure::ARM::Compute::Models::ManagedDiskParameters.new
            managed_disk.storage_account_type = managed_disk_storage_type
            os_disk.managed_disk = managed_disk
          end
          storage_profile.os_disk = configure_os_disk_object(os_disk, os_disk_caching, os_disk_size, platform, vm_name)
          storage_profile
        end

        def image_config_params(location, new_vhd_path, platform, resource_group, vm_name)
          {
            location: location,
            new_vhd_path: new_vhd_path,
            platform: platform,
            resource_group: resource_group,
            vm_name: vm_name
          }
        end

        def configure_os_disk_object(os_disk, os_disk_caching, os_disk_size, platform, vm_name)
          os_disk.name = "#{vm_name}_os_disk"
          os_disk.os_type = platform
          os_disk.disk_size_gb = os_disk_size unless os_disk_size.nil?
          os_disk.create_option = Azure::ARM::Compute::Models::DiskCreateOptionTypes::FromImage
          os_disk.caching = unless os_disk_caching.nil?
                              case os_disk_caching
                              when 'None'
                                Azure::ARM::Compute::Models::CachingTypes::None
                              when 'ReadOnly'
                                Azure::ARM::Compute::Models::CachingTypes::ReadOnly
                              when 'ReadWrite'
                                Azure::ARM::Compute::Models::CachingTypes::ReadWrite
                              end
                            end
          os_disk
        end

        def define_windows_os_profile(vm_name, username, password, provision_vm_agent, enable_automatic_updates, encoded_data)
          os_profile = Azure::ARM::Compute::Models::OSProfile.new
          windows_config = Azure::ARM::Compute::Models::WindowsConfiguration.new
          windows_config.provision_vmagent = provision_vm_agent
          windows_config.enable_automatic_updates = enable_automatic_updates
          os_profile.windows_configuration = windows_config
          os_profile.computer_name = vm_name
          os_profile.admin_username = username
          os_profile.admin_password = password
          os_profile.custom_data = encoded_data
          os_profile
        end

        def define_linux_os_profile(vm_name, username, password, disable_password_authentication, ssh_key_path, ssh_key_data, encoded_data)
          os_profile = Azure::ARM::Compute::Models::OSProfile.new
          linux_config = Azure::ARM::Compute::Models::LinuxConfiguration.new

          unless ssh_key_path.nil? || ssh_key_data.nil?
            ssh_config = Azure::ARM::Compute::Models::SshConfiguration.new
            ssh_public_key = Azure::ARM::Compute::Models::SshPublicKey.new
            ssh_public_key.path = ssh_key_path
            ssh_public_key.key_data = ssh_key_data
            ssh_config.public_keys = [ssh_public_key]
            linux_config.ssh = ssh_config
          end

          linux_config.disable_password_authentication = disable_password_authentication
          os_profile.linux_configuration = linux_config
          os_profile.computer_name = vm_name
          os_profile.admin_username = username
          os_profile.admin_password = password
          os_profile.custom_data = encoded_data
          os_profile
        end

        def copy_vhd_to_storage_account(resource_group, storage_account_name, vhd_path, location, vm_name)
          # Copy if VHD does not exist belongs to same storage account.
          vhd_storage_account = (vhd_path.split('/')[2]).split('.')[0]
          if storage_account_name != vhd_storage_account
            if storage_account_name.nil?
              new_time = current_time
              storage_account_name = "sa#{new_time}"
              storage_account = @storage_service.storage_accounts.create(
                storage_account_config_params(location, resource_group, storage_account_name)
              )
            else
              storage_account = @storage_service.storage_accounts.get(resource_group, storage_account_name)
            end
            access_key = storage_account.get_access_keys.first.value
            storage_data = Fog::Storage::AzureRM.new(azure_storage_account_name: storage_account_name, azure_storage_access_key: access_key)
            new_time = current_time
            container_name = "customvhd-#{vm_name.downcase}-os-image"
            blob_name = "vhd_image#{new_time}.vhd"
            storage_data.directories.create(key: container_name)
            storage_data.copy_blob_from_uri(container_name, blob_name, vhd_path)
            until storage_data.get_blob_properties(container_name, blob_name).properties[:copy_status] == 'success'
              Fog::Logger.debug 'Waiting disk to ready'
              sleep(10)
            end
            new_vhd_path = get_blob_endpoint(storage_account_name) + "/#{container_name}/#{blob_name}"
            Fog::Logger.debug "Path:#{new_vhd_path}. | Copy done"
          else
            new_vhd_path = vhd_path
          end
          new_vhd_path
        end

        def storage_account_config_params(location, resource_group, storage_account_name)
          {
            name: storage_account_name,
            location: location,
            resource_group: resource_group,
            account_type: 'Standard',
            replication: 'LRS',
            tags:
            {
              TEMPORARY_STORAGE_ACCOUNT_TAG_KEY => TEMPORARY_STORAGE_ACCOUNT_TAG_VALUE
            }
          }
        end

        def define_network_profile(network_interface_card_ids)
          network_interface_cards = []
          network_interface_card_ids.each_with_index do |id, index|
            nic = Azure::ARM::Compute::Models::NetworkInterfaceReference .new
            nic.id = id
            nic.primary = index == PRIMARY_NIC_INDEX
            network_interface_cards << nic
          end

          network_profile = Azure::ARM::Compute::Models::NetworkProfile.new
          network_profile.network_interfaces = network_interface_cards
          network_profile
        end

        def delete_storage_account_or_container(resource_group, storage_account_name, vm_name)
          delete_storage_account(resource_group) if storage_account_name.nil?
          delete_storage_container(resource_group, storage_account_name, vm_name) unless storage_account_name.nil?
        end

        def delete_storage_container(resource_group, storage_account_name, vm_name)
          access_key = @storage_service.get_storage_access_keys(resource_group, storage_account_name).first.value
          container_name = "customvhd-#{vm_name.downcase}-os-image"
          @storage_service.directories.delete_temporary_container(storage_account_name, access_key, container_name)
        end

        def delete_storage_account(resource_group)
          @storage_service.storage_accounts.delete_storage_account_from_tag(resource_group, TEMPORARY_STORAGE_ACCOUNT_TAG_KEY, TEMPORARY_STORAGE_ACCOUNT_TAG_VALUE)
        end
      end
      # This class provides the mock implementation for unit tests.
      class Mock
        def create_virtual_machine(*)
          vm = {
            'location' => 'westus',
            'id' => '/subscriptions/########-####-####-####-############/resourceGroups/fog-test-rg/providers/Microsoft.Compute/virtualMachines/fog-test-server',
            'name' => 'fog-test-server',
            'type' => 'Microsoft.Compute/virtualMachines',
            'properties' =>
            {
              'hardwareProfile' =>
                {
                  'vmSize' => 'Standard_A0'
                },
              'storageProfile' =>
                {
                  'imageReference' =>
                    {
                      'publisher' => 'MicrosoftWindowsServerEssentials',
                      'offer' => 'WindowsServerEssentials',
                      'sku' => 'WindowsServerEssentials',
                      'version' => 'latest'
                    },
                  'osDisk' =>
                    {
                      'name' => 'fog-test-server_os_disk',
                      'vhd' =>
                        {
                          'uri' => 'http://mystorage1.blob.core.windows.net/vhds/fog-test-server_os_disk.vhd'
                        },
                      'createOption' => 'FromImage',
                      'osType' => 'Linux',
                      'caching' => 'ReadWrite'
                    },
                  'dataDisks' => []
                },
              'osProfile' =>
                {
                  'computerName' => 'fog-test-server',
                  'adminUsername' => 'fog',
                  'linuxConfiguration' =>
                    {
                      'disablePasswordAuthentication' => true
                    },
                  'secrets' => [],
                  'customData' => 'ZWNobyBjdXN0b21EYXRh'
                },
              'networkProfile' =>
                {
                  'networkInterfaces' =>
                    [
                      {
                        'id' => '/subscriptions/########-####-####-####-############/resourceGroups/fog-test-rg/providers/Microsoft.Network/networkInterfaces/fog-test-vnet'
                      }
                    ]
                },
              'provisioningState' => 'Succeeded'
            }
          }
          vm_mapper = Azure::ARM::Compute::Models::VirtualMachine.mapper
          @compute_mgmt_client.deserialize(vm_mapper, vm, 'result.body')
        end
      end
    end
  end
end