require 'fog/cloudstack'
require 'fog/compute'
require 'digest/md5'

module Fog
  module Compute
    class Cloudstack < Fog::Service

      class BadRequest < Fog::Compute::Cloudstack::Error; end
      class Unauthorized < Fog::Compute::Cloudstack::Error; end

      requires :cloudstack_host

      recognizes :cloudstack_api_key, :cloudstack_secret_access_key, :cloudstack_session_key, :cloudstack_session_id,
                 :cloudstack_port, :cloudstack_path, :cloudstack_scheme, :cloudstack_persistent

      request_path 'fog/cloudstack/requests/compute'


      model_path 'fog/cloudstack/models/compute'
      model :address
      model :flavor
      collection :flavors
      model :job
      collection :jobs
      model :server
      collection :servers
      model :image
      collection :images
      model :security_group
      collection :security_groups
      model :security_group_rule
      collection :security_group_rules
      model :volume
      collection :volumes
      model :zone
      collection :zones

      request :acquire_ip_address
      request :assign_to_load_balancer_rule
      request :assign_virtual_machine
      request :attach_volume
      request :authorize_security_group_egress
      request :authorize_security_group_ingress
      request :change_service_for_virtual_machine
      request :create_account
      request :create_domain
      request :create_load_balancer_rule
      request :create_network
      request :create_port_forwarding_rule
      request :create_security_group
      request :create_ssh_key_pair
      request :create_snapshot
      request :create_snapshot_policy
      request :create_user
      request :create_volume
      request :create_zone
      request :delete_account
      request :delete_domain
      request :delete_load_balancer_rule
      request :delete_port_forwarding_rule
      request :delete_security_group
      request :delete_ssh_key_pair
      request :delete_snapshot
      request :delete_snapshot_policies
      request :delete_template
      request :delete_user
      request :delete_volume
      request :detach_volume
      request :deploy_virtual_machine
      request :destroy_virtual_machine
      request :disable_user
      request :enable_user
      request :generate_usage_records
      request :get_vm_password
      request :list_accounts
      request :list_alerts
      request :list_async_jobs
      request :list_capacity
      request :list_capabilities
      request :list_clusters
      request :list_configurations
      request :list_disk_offerings
      request :list_capacity
      request :list_domains
      request :list_domain_children
      request :list_events
      request :list_external_firewalls
      request :list_external_load_balancers
      request :list_firewall_rules
      request :list_hosts
      request :list_hypervisors
      request :list_instance_groups
      request :list_isos
      request :list_load_balancer_rules
      request :list_load_balancer_rule_instances
      request :list_network_offerings
      request :list_networks
      request :list_os_categories
      request :list_os_types
      request :list_pods
      request :list_port_forwarding_rules
      request :list_public_ip_addresses
      request :list_resource_limits
      request :list_security_groups
      request :list_service_offerings
      request :list_snapshots
      request :list_snapshot_policies
      request :list_ssh_key_pairs
      request :list_storage_pools
      request :list_templates
      request :list_usage_records
      request :list_users
      request :list_virtual_machines
      request :list_volumes
      request :list_zones
      request :migrate_virtual_machine
      request :query_async_job_result
      request :reboot_virtual_machine
      request :recover_virtual_machine
      request :register_ssh_key_pair
      request :register_user_keys
      request :remove_from_load_balancer_rule
      request :reset_password_for_virtual_machine
      request :revoke_security_group_ingress
      request :revoke_security_group_egress
      request :start_virtual_machine      
      request :stop_virtual_machine
      request :update_account
      request :update_domain
      request :update_user
      request :update_resource_count
      request :update_virtual_machine

      class Real

        def initialize(options={})
          @cloudstack_api_key           = options[:cloudstack_api_key]
          @cloudstack_secret_access_key = options[:cloudstack_secret_access_key]
          @cloudstack_session_id        = options[:cloudstack_session_id]
          @cloudstack_session_key       = options[:cloudstack_session_key]
          @host                         = options[:cloudstack_host]
          @path                         = options[:cloudstack_path]    || '/client/api'
          @port                         = options[:cloudstack_port]    || 443
          @scheme                       = options[:cloudstack_scheme]  || 'https'
          @connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}#{@path}", options[:cloudstack_persistent], {:ssl_verify_peer => false})
        end

        def reload
          @connection.reset
        end

        def login(username,password,domain)
          response = issue_request({
            'response' => 'json',
            'command'  => 'login',
            'username' => username,
            'password' => Digest::MD5.hexdigest(password),
            'domain'   => domain
          })

          # Parse response cookies to retrive JSESSIONID token
          cookies   = CGI::Cookie.parse(response.headers['Set-Cookie'])
          sessionid = cookies['JSESSIONID'].first

          # Decode the login response
          response   = Fog::JSON.decode(response.body)

          user = response['loginresponse']
          user.merge!('sessionid' => sessionid)

          @cloudstack_session_id  = user['sessionid']
          @cloudstack_session_key = user['sessionkey']

          user
        end

        def request(params)
          params.reject!{|k,v| v.nil?}

          params.merge!('response' => 'json')

          if has_session?
            params, headers = authorize_session(params)
          elsif has_keys?
            params, headers = authorize_api_keys(params)
          end

          response = issue_request(params,headers)
          response = Fog::JSON.decode(response.body) unless response.body.empty?
          response
        end

      private
        def has_session?
          @cloudstack_session_id && @cloudstack_session_key
        end

        def has_keys?
          @cloudstack_api_key && @cloudstack_secret_access_key
        end

        def authorize_session(params)
          # set the session id cookie for the request
          headers = {'Cookie' => "JSESSIONID=#{@cloudstack_session_id};"}
          # set the sesion key for the request, params are not signed using session auth
          params.merge!('sessionkey' => @cloudstack_session_key)

          return params, headers
        end

        def authorize_api_keys(params)
          headers = {}
          # merge the api key into the params
          params.merge!('apiKey' => @cloudstack_api_key)
          # sign the request parameters
          signature = Fog::Cloudstack.signed_params(@cloudstack_secret_access_key,params)
          # merge signature into request param
          params.merge!({'signature' => signature})

          return params, headers
        end

        def issue_request(params={},headers={},method='GET',expects=200)
          begin
            @connection.request({
              :query => params,
              :headers => headers,
              :method => method,
              :expects => expects  
            })

          rescue Excon::Errors::HTTPStatusError => error
            error_response = Fog::JSON.decode(error.response.body)

            error_code = error_response.values.first['errorcode']
            error_text = error_response.values.first['errortext']

            case error_code
            when 401
              raise Fog::Compute::Cloudstack::Unauthorized, error_text
            when 431
              raise Fog::Compute::Cloudstack::BadRequest, error_text
            else
              raise Fog::Compute::Cloudstack::Error, error_text
            end
          end

        end
      end # Real

      class Mock
        def initialize(options={})
          @cloudstack_api_key = options[:cloudstack_api_key]
        end

        def self.data
          @data ||= begin
            zone_id     = Fog.credentials[:cloudstack_zone_id]             || Fog::Cloudstack.uuid
            image_id    = Fog.credentials[:cloudstack_template_id]         || Fog::Cloudstack.uuid
            flavor_id   = Fog.credentials[:cloudstack_service_offering_id] || Fog::Cloudstack.uuid
            network_id  = (Array(Fog.credentials[:cloudstack_network_ids]) || [Fog::Cloudstack.uuid]).first
            domain_name = "exampleorg"
            account_id, user_id, domain_id = Fog::Cloudstack.uuid, Fog::Cloudstack.uuid, Fog::Cloudstack.uuid
            domain = {
              "id"               => domain_id,
              "name"             => domain_name,
              "level"            => 1,
              "parentdomainid"   => Fog::Cloudstack.uuid,
              "parentdomainname" => "ROOT",
              "haschild"         => false,
              "path"             => "ROOT/accountname"
            }
            user = {
              "id"          => user_id,
              "username"    => "username",
              "firstname"   => "Bob",
              "lastname"    => "Lastname",
              "email"       => "email@example.com",
              "created"     => "2012-05-14T16:25:17-0500",
              "state"       => "enabled",
              "account"     => "accountname",
              "accounttype" => 2,
              "domainid"    => domain_id,
              "domain"      => domain_name,
              "apikey"      => Fog::Cloudstack.uuid,
              "secretkey"   => Fog::Cloudstack.uuid
            }
            {
              :users    => { user_id    => user },
              :networks => { network_id => {
                "id"                          => network_id,
                "name"                        => "10.56.23.0/26",
                "displaytext"                 => "10.56.23.0/26",
                "broadcastdomaintype"         => "Vlan",
                "traffictype"                 => "Guest",
                "gateway"                     => "10.56.23.1",
                "netmask"                     => "255.255.255.192",
                "cidr"                        => "10.56.23.0/26",
                "zoneid"                      => zone_id,
                "zonename"                    => "zone-00",
                "networkofferingid"           => "af0c9bd5-a1b2-4ad0-bf4b-d6fa9b1b9d5b",
                "networkofferingname"         => "DefaultSharedNetworkOffering",
                "networkofferingdisplaytext"  => "Offering for Shared networks",
                "networkofferingavailability" => "Optional",
                "issystem"                    => false,
                "state"                       => "Setup",
                "related"                     => "86bbc9fc-d92e-49db-9fdc-296189090017",
                "broadcasturi"                => "vlan://800",
                "dns1"                        => "10.0.80.11",
                "type"                        => "Shared",
                "vlan"                        => "800",
                "acltype"                     => "Domain",
                "subdomainaccess"             => true,
                "domainid"                    => domain_id,
                "domain"                      => "ROOT",
                "service" => [
                  {"name" => "UserData"},
                  {"name" => "Dhcp"},
                  {
                    "name"       => "Dns",
                    "capability" => [
                      {
                        "name"                       => "AllowDnsSuffixModification",
                        "value"                      => "true",
                        "canchooseservicecapability" => false
                      }
                    ]
                }],
                "networkdomain"     => "cs1cloud.internal",
                "physicalnetworkid" => "8f4627c5-1fdd-4504-8a92-f61b4e9cb3e3",
                "restartrequired"   => false,
                "specifyipranges"   => true}
              },
              :zones => { zone_id => {
                "id"                    => zone_id,
                "name"                  => "zone-00",
                "domainid"              => 1,
                "domainname"            => "ROOT",
                "networktype"           => "Advanced",
                "securitygroupsenabled" => false,
                "allocationstate"       => "Enabled",
                "zonetoken"             => Fog::Cloudstack.uuid,
                "dhcpprovider"          => "VirtualRouter"}},
              :images => { image_id => {
                "id"              => image_id,
                "name"            => "CentOS 5.6(64-bit) no GUI (XenServer)",
                "displaytext"     => "CentOS 5.6(64-bit) no GUI (XenServer)",
                "ispublic"        => true,
                "created"         => "2012-05-09T15:29:33-0500",
                "isready"         => true,
                "passwordenabled" => false,
                "format"          => "VHD",
                "isfeatured"      => true,
                "crossZones"      => true,
                "ostypeid"        => "a6a6694a-18f5-4765-8418-2b7a5f37cd0f",
                "ostypename"      => "CentOS 5.3 (64-bit)",
                "account"         => "system",
                "zoneid"          => zone_id,
                "zonename"        => "zone-00",
                "status"          => "Download Complete",
                "size"            => 21474836480,
                "templatetype"    => "BUILTIN",
                "domain"          => "ROOT",
                "domainid"        => "6023b6fe-5bef-4358-bc76-9f4e75afa52f",
                "isextractable"   => true,
                "checksum"        => "905cec879afd9c9d22ecc8036131a180"}},
              :flavors => { flavor_id => {
                "id"          => flavor_id,
                "name"        => "Medium Instance",
                "displaytext" => "Medium Instance",
                "cpunumber"   => 1,
                "cpuspeed"    => 1000,
                "memory"      => 1024,
                "created"     => "2012-05-09T14:48:36-0500",
                "storagetype" => "shared",
                "offerha"     => false,
                "limitcpuuse" => false,
                "issystem"    => false,
                "defaultuse"  => false}},
              :accounts => { account_id => {
                "id"                => account_id,
                "name"              => "accountname",
                "accounttype"       => 2,
                "domainid"          => domain_id,
                "domain"            => domain_name,
                "receivedbytes"     => 0,
                "sentbytes"         => 0,
                "vmlimit"           => "Unlimited",
                "vmtotal"           => 0,
                "vmavailable"       => "Unlimited",
                "iplimit"           => "Unlimited",
                "iptotal"           => 0,
                "ipavailable"       => "Unlimited",
                "volumelimit"       => "Unlimited",
                "volumetotal"       => 0,
                "volumeavailable"   => "Unlimited",
                "snapshotlimit"     => "Unlimited",
                "snapshottotal"     => 0,
                "snapshotavailable" => "Unlimited",
                "templatelimit"     => "Unlimited",
                "templatetotal"     => 0,
                "templateavailable" => "Unlimited",
                "vmstopped"         => 0,
                "vmrunning"         => 0,
                "projectlimit"      => "Unlimited",
                "projecttotal"      => 1,
                "projectavailable"  => "Unlimited",
                "networklimit"      => "Unlimited",
                "networktotal"      => 0,
                "networkavailable"  => "Unlimited",
                "state"             => "enabled",
                "user"              => [user]}
              },
              :domains         => {domain_id => domain},
              :servers         => {},
              :jobs            => {},
              :volumes         => {},
              :security_groups => {},
            }
          end
        end

        def self.reset
          @data = nil
        end

        def data
          self.class.data
        end

        def reset_data
          self.class.data.delete(@cloudstack_api_key)
        end
      end
    end # Cloudstack
  end # Compute
end # Fog
