import Framework
from Framework.components.services import TEST_URLS_FUNCTION_NAME, TEST_QUERIES_FUNCTION_NAME
from base import BaseHandler, InternalRequestHandler
import tornado.httpserver

URL_SERVICE_TEST = dict(set_name = 'url_services', list_attr = 'test_urls', function_name = TEST_URLS_FUNCTION_NAME)
SEARCH_SERVICE_TEST = dict(set_name = 'search_services', list_attr = 'test_queries', function_name = TEST_QUERIES_FUNCTION_NAME)

class ServiceRequestHandler(InternalRequestHandler):

  # Instruction from the System bundle to reload services
  @BaseHandler.route('/reloadServices') # Legacy route
  @BaseHandler.route('/services/reload')
  def reload_services(self):
    self._core.log.debug('Reloading services')
    self._core.services.load_all_services()
    return True

  # External request for a URL lookup
  @BaseHandler.route('/url/lookup') # Legacy route
  @BaseHandler.route('/services/url/lookup')
  def url_lookup(self, url, limit=None):
    self._core.log.debug("Looking up URL '%s'", url)
    
    # Get the metadata object for the given URL
    c = self._core.services.metadata_object_for_url(url, in_container=True)

    if c != None:
      obj = c.objects[0]
      self._core.log.debug("TAG: %s", obj.xml_tag)
      if isinstance(obj, Framework.api.objectkit.DirectoryObject) or (isinstance(obj, Framework.modelling.objects.Object) and obj._template.xml_tag == 'Directory'):
        req = tornado.httpserver.HTTPRequest('GET', str(obj.key), headers=self._core.sandbox.context.request.headers)
        status, headers, body = self._core.runtime.handle_request(req)
        if status == 200:
          self._core.sandbox.context.response_headers.update(headers)
          c = body
        else:
          raise Framework.exceptions.FrameworkException("Status != 200")
        
      else:
        # Move the user agent and http cookies (if set) to the container instead of the object
        # TODO: Move this to metadata_object_for_url
        try:
          if obj.user_agent:
            c.user_agent = obj.user_agent
            obj.user_agent = None
        except:
          pass
          
        try:
          if obj.http_cookies:
            c.http_cookies = obj.http_cookies
            obj.http_cookies = None
        except:
          pass
          
        # TODO: Handle custom HTTP headers here too.
  
        if limit and len(obj) > int(limit):
          obj.items = obj.items[:int(limit)]
        
      return c

  # Search request
  @BaseHandler.route('/serviceSearch') # Legacy route
  @BaseHandler.route('/services/search')
  def search(self, query, identifier, name=None):
    return self._core.services.search(query, identifier, name)
    
  # Request for known service tests
  @BaseHandler.route('/serviceTestURLs', **URL_SERVICE_TEST) # Legacy route
  @BaseHandler.route('/serviceTestURLs/{identifier}', **URL_SERVICE_TEST) # Legacy route
  @BaseHandler.route('/serviceTestURLs/{identifier}/{service_name}', **URL_SERVICE_TEST) # Legacy route
  @BaseHandler.route('/serviceTestQueries', **SEARCH_SERVICE_TEST) # Legacy route
  @BaseHandler.route('/serviceTestQueries/{identifier}', **SEARCH_SERVICE_TEST) # Legacy route
  @BaseHandler.route('/serviceTestQueries/{identifier}/{service_name}', **SEARCH_SERVICE_TEST) # Legacy route

  @BaseHandler.route('/services/tests/urls', **URL_SERVICE_TEST)
  @BaseHandler.route('/services/tests/queries', **SEARCH_SERVICE_TEST)
  def tests(self, set_name, list_attr, function_name, identifier=None, service_name=None):
    sets = getattr(self._core.services, set_name)
    results = {}
    # Filter service sets based on identifier if one was provided
    service_sets = sets if identifier == None else {identifier: sets[identifier]}

    # Iterate over the service sets
    for identifier, service_set in service_sets.items():
      result = {}

      # Filter services based on name if one was provided
      services = service_set if service_name == None else {service_name: service_set[service_name]}

      # Iterate over services
      for name, service in services.items():
        
        # Get the list of test queries defined in the plist
        tests = list(getattr(service, list_attr))

        # Extend the list with tests defined in the code file, if found
        try:
          tests_from_code = self._core.services._call_named_function_in_service(function_name, service, [], {})
          if tests_from_code and isinstance(tests_from_code, list):
            tests.extend(tests_from_code)
        except:
          pass

        # Add the list of tests to the service's result dictionary
        result[name] = tests

      # Add the result dictionary to the full set of results
      results[identifier] = result
    
    # Return the results to the client
    return results


  def strip_extension_from_name(self, f_name):
    pos = f_name.find('.')
    if pos > -1:
      f_name = f_name[:pos]
    return f_name

  def unpack_arg_objects(self, args, kwargs, other_kwargs={}):
    f_args = Framework.utils.unpack(args) if args else []
    f_kwargs = Framework.utils.unpack(kwargs) if kwargs else {}    
    f_kwargs.update(other_kwargs)
    return f_args, f_kwargs

  # Request for a function call inside a service
  @BaseHandler.route('/serviceFunction/{service_type}/{service_identifier}/{service_name}/{f_name}') # Legacy route
  @BaseHandler.route('/services/url/function/{service_identifier}/{service_name}/{f_name}', service_type = 'url')
  @BaseHandler.route('/services/search/function/{service_identifier}/{service_name}/{f_name}', service_type = 'search')
  @BaseHandler.route('/services/relatedContent/function/{service_identifier}/{service_name}/{f_name}', service_type = 'related_content')
  def call_service_function(self, service_type, service_identifier, service_name, f_name, args=None, kwargs=None, **other_kwargs):
    # Strip the extension if one is found and extract args & kwargs for the function from the querystring args
    f_name = self.strip_extension_from_name(f_name)
    f_args, f_kwargs = self.unpack_arg_objects(args, kwargs, other_kwargs)
      
    # Get the correct dictionary of services
    attr_name = service_type + '_services'
    if not hasattr(self._core.services, attr_name):
      self._core.log.error("No services exist for type '%s'", service_type)
      return None
    services = getattr(self._core.services, attr_name)
    
    if service_identifier in services:
      service_set = services[service_identifier]
      if service_name in service_set:
        service = service_set[service_name]
        result = self._core.services._call_named_function_in_service(f_name, service, f_args, f_kwargs)
        return result


  # Request for a function call inside shared service code
  @BaseHandler.route('/sharedServiceFunction/{mod_name}/{f_name}') # Legacy route
  @BaseHandler.route('/services/shared/function/{mod_name}/{f_name}')
  def call_shared_service_function(self, mod_name, f_name, args=None, kwargs=None, **other_kwargs):
    # Strip the extension if one is found and extract args & kwargs for the function from the querystring args
    f_name = self.strip_extension_from_name(f_name)
    f_args, f_kwargs = self.unpack_arg_objects(args, kwargs, other_kwargs)

    # Call the function and return the result
    return self._core.services._call_named_function_in_shared_service_code(f_name, mod_name, f_args, f_kwargs)

