#!/usr/bin/env ruby
require 'rubygems'
require 'omf_rc'
require 'omf_common'
require 'yaml'
require "net/https"
require "uri"
require "json"
require 'time'

$stdout.sync = true

@config = YAML.load_file('/etc/nitos_testbed_rc/cm_proxy_conf.yaml')
# @config = YAML.load_file(File.join(File.dirname(File.expand_path(__FILE__)), '../etc/cm_proxy_conf.yaml'))
@auth = @config[:auth]
@xmpp = @config[:xmpp]

require 'nitos_testbed_rc/cm_factory'

cm_entity_cert = File.expand_path(@auth[:entity_cert])
cm_entity_key = File.expand_path(@auth[:entity_key])
cm_entity = OmfCommon::Auth::Certificate.create_from_pem(File.read(cm_entity_cert))#, File.read(cm_entity_key))

trusted_roots = File.expand_path(@auth[:root_cert_dir])

opts = {
  communication: {
    url:  "xmpp://#{@xmpp[:username]}:#{@xmpp[:password]}@#{@xmpp[:server]}",
    auth: {
      authenticate: true,
      pdp: {
        constructor: 'CmPDP'
      }
    }
  }
}

class CmPDP
  def initialize(opts = {})
    debug "AUTH INIT>>> #{opts}"
    @config = YAML.load_file('/etc/nitos_testbed_rc/cm_proxy_conf.yaml')
    # @config = YAML.load_file(File.join(File.dirname(File.expand_path(__FILE__)), '../etc/cm_proxy_conf.yaml'))
  end

  def authorize(msg, &block)
    debug "AUTH message received: #{msg.operation}"
    if msg.operation.to_sym == :configure
      acc = _get_account_name(msg)

      if acc.nil?
        error "AUTH error: Account not found"
        msg.properties.state.error_msg = "Account name not found"
        return msg# next
      end

      node_name = msg.properties.state.node
      broker = @config[:broker_url]

      uri = URI.parse("#{broker}/resources/nodes?name=#{node_name}")
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = true
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE
      request = Net::HTTP::Get.new(uri.request_uri)
      response = http.request(request)
      resp = JSON.parse(response.body, :symbolize_names => true)

      if response.header.code != '200'
        error "AUTH error: empty response"
        msg.properties.state.error_msg = "#{resp[:reason]} - code: '#{resp[:exception][:code]}'"
        return msg# next
      end

      node = resp[:resource_response]
      if node.nil?
        error "AUTH error: Node '#{node_name}' not found"
        msg.properties.state.error_msg = "Node '#{node_name}' does not exist - code: '#{resp[:exception][:code]}'"
        return msg
      end

      node = node[:resources].first
      lease = nil
      node[:leases].each do |l|
        l = l[:lease]
        next if l[:account][:name] != acc
        if Time.parse(l[:valid_from]) <= Time.now && Time.parse(l[:valid_until]) >= Time.now
          lease = l
          break
        end
      end

      if lease.nil?
        error "AUTH error: node not available"
        msg.properties.state.error_msg = "Node '#{node_name}' is not leased by your account."
        return msg
      else
        debug "AUTH PASSED"
        msg.properties.state.node = node
        return msg
      end
      # wait = true
      # result = nil
      # OmfCommon.comm.subscribe(@config[:testbedTopic]) do |am_con|
      #   acc = _get_account_name(msg)

      #   if acc.nil?
      #     error "AUTH error: acc nill"
      #     msg.properties.state.error_msg = "Account name not found"
      #     result = msg
      #     wait = false
      #     next
      #   end

      #   node_name = msg.properties.state.node
      #   am_con.request([:nodes]) do |n_msg|
      #     nodes = n_msg.read_property("nodes")[:resources]
      #     node = nil
      #     nodes.each do |n|
      #       if n[:resource][:name].to_s == node_name.to_s
      #         node = n
      #         break
      #       end
      #     end

      #     lease = nil
      #     if node.nil?
      #       error "AUTH error: Node nill"
      #       msg.properties.state.error_msg = "Wrong node name."
      #       result = msg
      #       wait = false
      #       next
      #     else
      #       am_con.request([:leases]) do |l_msg|
      #         leases = l_msg.read_property("leases")[:resources]
      #         leases.each do |l|
      #           if Time.parse(l[:resource][:valid_from]) <= Time.now && Time.parse(l[:resource][:valid_until]) >= Time.now
      #             unless l[:resource][:components].nil?
      #               l[:resource][:components].each do |c|
      #                 if c[:component][:name] == node_name.to_s && l[:resource][:account][:name] == acc
      #                   lease = l
      #                   break #found the correct lease
      #                 end
      #               end
      #             end
      #           end
      #         end

      #         if lease.nil? #if lease is nil it means no matching lease is found
      #           error "AUTH error: Lease nill"
      #           msg.properties.state.error_msg = "Node is not leased by your account."
      #           result = msg
      #           wait = false
      #           next
      #         else
      #           debug "AUTH PASSED"
      #           msg.properties.state.node = node
      #           result = msg
      #           wait = false
      #           next
      #         end
      #       end
      #     end
      #   end
      # end

      # #waiting for the whole process to be completed
      # while wait
      #   sleep 1
      # end

      # return result if result
    else
      debug "AUTH PASSED"
      return msg
    end
#     msg
  end

  private
  def _get_account_name(msg)
    #subject is ~ /C=US/ST=CA/O=ACME/OU=Roadrunner/CN=37a96f60-c53d-50d9-bbbf-3c552b89bdc5/emailAddress=root@nitlab.inf.uth.gr
    subj = msg.issuer.subject.to_s
    subj.gsub!(/.*CN=/, '')
    subj.gsub!(/.*emailAddress=/, '')
    subj.gsub!(/@.*/, '')
    debug "AUTH user: #{subj}"
    return subj
  end
end

OmfCommon.init(@config[:operationMode], opts) do |el|#communication: { url: "xmpp://#{@xmpp[:proxy_user]}:#{@xmpp[:password]}@#{@xmpp[:server]}", auth: {} }) do
  OmfCommon.comm.on_connected do |comm|
    OmfCommon::Auth::CertificateStore.instance.register_default_certs(trusted_roots)
    cm_entity.resource_id = OmfCommon.comm.local_topic.address
    OmfCommon::Auth::CertificateStore.instance.register(cm_entity)

    info "CM Factory >> Connected to XMPP server"
    cmFact = OmfRc::ResourceFactory.create(:cm_factory, { uid: 'cm_factory', certificate: cm_entity })

    comm.on_interrupted {
      cmFact.disconnect
    }
  end
end