#!/usr/bin/env ruby

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/frisbee_proxy_conf.yaml')
# @config = YAML.load_file(File.join(File.dirname(File.expand_path(__FILE__)), '.../etc/frisbee_proxy_conf.yaml'))
@auth = @config[:auth]
@xmpp = @config[:xmpp]

require 'nitos_testbed_rc/frisbee'
require 'nitos_testbed_rc/frisbeed'
require 'nitos_testbed_rc/imagezip_server'
require 'nitos_testbed_rc/imagezip_client'
require 'nitos_testbed_rc/frisbee_factory'

frisbee_entity_cert = File.expand_path(@auth[:entity_cert])
frisbee_entity_key = File.expand_path(@auth[:entity_key])
frisbee_entity = OmfCommon::Auth::Certificate.create_from_pem(File.read(frisbee_entity_cert))#, File.read(frisbee_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: 'FrisbeePDP'
      }
    }
  }
}

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

  def authorize(msg, &block)
    debug "AUTH message received: #{msg.operation}"
    if msg.operation.to_sym == :create
      if msg.rtype.to_sym == :frisbee || msg.rtype.to_sym == :imagezip_client
        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.node_topic
        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'
          if resp[:exception] == "No resources matching the request."
            error "AUTH error: Node '#{node_name}' does not exist."
            msg.properties.state.error_msg = "Node #{node_name} does not exist."
          else
            error "AUTH error: empty response"
            msg.properties.state.error_msg = "#{resp[:reason]} - code: '#{resp[:exception][:code]}'"
          end
          return msg# next
        end

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

        node = node[:resources].first

        if @config[:testbedDomain] != "ALL" && node[:domain] != @config[:testbedDomain] 
          debug "This node does not belong to the domain '#{@config[:testbedDomain]}'"
          msg.properties.state.ignore_msg = true
          return msg
        end

        if acc == 'root'
          debug "AUTH PASSED (root account)"
          msg.properties.node = node
          return msg
        end

        lease = nil
        unless node[:leases].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
        end

        if lease.nil?
          error "AUTH error: node not available"
          msg.properties.error_msg = "Node is not leased by your account."
          return msg
        else
          debug "AUTH PASSED"
          msg.properties.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.propertie.error_msg = "Account name not found"
        #     result = msg
        #     wait = false
        #     next
        #   end

        #   node_name = msg.properties.node_topic
        #   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.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
        #             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

        #         if lease.nil? #if lease is nil it means no matching lease is found
        #           error "AUTH error: Lease nill"
        #           msg.properties.error_msg = "Node is not leased by your account."
        #           result = msg
        #           wait = false
        #           next
        #         else
        #           debug "AUTH PASSED"
        #           msg.properties.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

    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)
    frisbee_entity.resource_id = OmfCommon.comm.local_topic.address
    OmfCommon::Auth::CertificateStore.instance.register(frisbee_entity)
    info "Frisbee Factory >> Connected to XMPP server"

    frisbeeFact = OmfRc::ResourceFactory.create(:frisbee_factory, { uid: 'frisbee_factory', certificate: frisbee_entity })

    comm.on_interrupted {
      frisbeeFact.disconnect
    }
  end
end