import json
import logging

from flask import request, url_for
from flask.ext.restful import abort
from urllib import quote
from urlparse import urlunparse

from app import app
from endpoints.api import (RepositoryParamResource, nickname, resource, require_repo_admin,
                           log_action, request_error, query_param, parse_args,
                           validate_json_request)
from endpoints.api.build import build_status_view, trigger_view
from endpoints.common import start_build
from endpoints.trigger import (BuildTrigger, TriggerDeactivationException,
                               TriggerActivationException, EmptyRepositoryException)
from data import model
from auth.permissions import UserPermission


logger = logging.getLogger(__name__)


def _prepare_webhook_url(scheme, username, password, hostname, path):
  auth_hostname = '%s:%s@%s' % (quote(username), quote(password), hostname)
  return urlunparse((scheme, auth_hostname, path, '', '', ''))


@resource('/v1/repository/<path:repository>/trigger/')
class BuildTriggerList(RepositoryParamResource):
  """ Resource for listing repository build triggers. """

  @require_repo_admin
  @nickname('listBuildTriggers')
  def get(self, namespace, repository):
    """ List the triggers for the specified repository. """
    triggers = model.list_build_triggers(namespace, repository)
    return {
      'triggers': [trigger_view(trigger) for trigger in triggers]
    }


@resource('/v1/repository/<path:repository>/trigger/<trigger_uuid>')
class BuildTrigger(RepositoryParamResource):
  """ Resource for managing specific build triggers. """

  @require_repo_admin
  @nickname('getBuildTrigger')
  def get(self, namespace, repository, trigger_uuid):
    """ Get information for the specified build trigger. """
    try:
      trigger = model.get_build_trigger(namespace, repository, trigger_uuid)
    except model.InvalidBuildTriggerException:
      abort(404)

    return trigger_view(trigger)

  @require_repo_admin
  @nickname('deleteBuildTrigger')
  def delete(self, namespace, repository, trigger_uuid):
    """ Delete the specified build trigger. """
    try:
      trigger = model.get_build_trigger(namespace, repository, trigger_uuid)
    except model.InvalidBuildTriggerException:
      abort(404)
      return

    handler = BuildTrigger.get_trigger_for_service(trigger.service.name)
    config_dict = json.loads(trigger.config)
    if handler.is_active(config_dict):
      try:
        handler.deactivate(trigger.auth_token, config_dict)
      except TriggerDeactivationException as ex:
        # We are just going to eat this error
        logger.warning('Trigger deactivation problem: %s', ex)

      log_action('delete_repo_trigger', namespace, 
                 {'repo': repository, 'trigger_id': trigger_uuid,
                  'service': trigger.service.name, 'config': config_dict},
                 repo=model.get_repository(namespace, repository))

    trigger.delete_instance(recursive=True)
    return 'No Content', 204


@resource('/v1/repository/<path:repository>/trigger/<trigger_uuid>/subdir')
class BuildTriggerSubdirs(RepositoryParamResource):
  """ Custom verb for fetching the subdirs which are buildable for a trigger. """
  schemas = {
    'BuildTriggerSubdirRequest': {
      'id': 'BuildTriggerSubdirRequest',
      'type': 'object',
      'description': 'Arbitrary json.',
    },
  }

  @require_repo_admin
  @nickname('listBuildTriggerSubdirs')
  @validate_json_request('BuildTriggerSubdirRequest')
  def post(self, namespace, repository, trigger_uuid):
    """ List the subdirectories available for the specified build trigger and source. """
    try:
      trigger = model.get_build_trigger(namespace, repository, trigger_uuid)
    except model.InvalidBuildTriggerException:
      abort(404)
      return

    handler = BuildTrigger.get_trigger_for_service(trigger.service.name)
    user_permission = UserPermission(trigger.connected_user.username)
    if user_permission.can():
      new_config_dict = request.get_json()

      try:
        subdirs = handler.list_build_subdirs(trigger.auth_token, new_config_dict)
        return {
          'subdir': subdirs,
          'status': 'success'
        }
      except EmptyRepositoryException as exc:
        return {
          'status': 'error',
          'message': exc.msg
        }
    else:
      abort(403)


@resource('/v1/repository/<path:repository>/trigger/<trigger_uuid>/activate')
class BuildTriggerActivate(RepositoryParamResource):
  """ Custom verb for activating a build trigger once all required information has been collected.
  """
  schemas = {
    'BuildTriggerActivateRequest': {
      'id': 'BuildTriggerActivateRequest',
      'type': 'object',
      'description': 'Arbitrary json.',
    },
  }

  @require_repo_admin
  @nickname('activateBuildTrigger')
  @validate_json_request('BuildTriggerActivateRequest')
  def post(self, namespace, repository, trigger_uuid):
    """ Activate the specified build trigger. """
    try:
      trigger = model.get_build_trigger(namespace, repository, trigger_uuid)
    except model.InvalidBuildTriggerException:
      abort(404)

    handler = BuildTrigger.get_trigger_for_service(trigger.service.name)
    existing_config_dict = json.loads(trigger.config)
    if handler.is_active(existing_config_dict):
      abort(400)

    user_permission = UserPermission(trigger.connected_user.username)
    if user_permission.can():
      new_config_dict = request.get_json()

      token_name = 'Build Trigger: %s' % trigger.service.name
      token = model.create_delegate_token(namespace, repository, token_name,
                                          'write')

      try:
        repository_path = '%s/%s' % (trigger.repository.namespace,
                                     trigger.repository.name)
        path = url_for('webhooks.build_trigger_webhook',
                       repository=repository_path, trigger_uuid=trigger.uuid)
        authed_url = _prepare_webhook_url(app.config['URL_SCHEME'], '$token',
                                          token.code, app.config['URL_HOST'],
                                          path)

        final_config = handler.activate(trigger.uuid, authed_url,
                                        trigger.auth_token, new_config_dict)
      except TriggerActivationException as exc:
        token.delete_instance()
        return request_error(message=exc.message)

      # Save the updated config.
      trigger.config = json.dumps(final_config)
      trigger.write_token = token
      trigger.save()

      # Log the trigger setup.
      repo = model.get_repository(namespace, repository)
      log_action('setup_repo_trigger', namespace,
               {'repo': repository, 'namespace': namespace,
                'trigger_id': trigger.uuid, 'service': trigger.service.name, 
                'config': final_config}, repo=repo)

      return trigger_view(trigger)
    else:
      abort(403)


@resource('/v1/repository/<path:repository>/trigger/<trigger_uuid>/start')
class ActivateBuildTrigger(RepositoryParamResource):
  """ Custom verb to manually activate a build trigger. """

  @require_repo_admin
  @nickname('manuallyStartBuildTrigger')
  def post(self, namespace, repository, trigger_uuid):
    """ Manually start a build from the specified trigger. """
    try:
      trigger = model.get_build_trigger(namespace, repository, trigger_uuid)
    except model.InvalidBuildTriggerException:
      abort(404)

    handler = BuildTrigger.get_trigger_for_service(trigger.service.name)
    existing_config_dict = json.loads(trigger.config)
    if not handler.is_active(existing_config_dict):
      abort(400)

    specs = handler.manual_start(trigger.auth_token, json.loads(trigger.config))
    dockerfile_id, tags, name, subdir = specs

    repo = model.get_repository(namespace, repository)

    build_request = start_build(repo, dockerfile_id, tags, name, subdir, True)

    resp = build_status_view(build_request, True)
    repo_string = '%s/%s' % (namespace, repository)
    headers = {
      'Location': url_for('api_bp.get_repo_build_status', repository=repo_string,
                          build_uuid=build_request.uuid),
    }
    return resp, 201, headers


@resource('/v1/repository/<path:repository>/trigger/<trigger_uuid>/builds')
class TriggerBuildList(RepositoryParamResource):
  """ Resource to represent builds that were activated from the specified trigger. """
  @parse_args
  @query_param('limit', 'The maximum number of builds to return', type=int, default=5)
  @require_repo_admin
  @nickname('listTriggerRecentBuilds')
  def get(self, args, namespace, repository, trigger_uuid):
    """ List the builds started by the specified trigger. """
    limit = args['limit']
    builds = list(model.list_trigger_builds(namespace, repository,
                                            trigger_uuid, limit))
    return {
      'builds': [build_status_view(build, True) for build in builds]
    }


@resource('/v1/repository/<path:repository>/trigger/<trigger_uuid>/sources')
class BuildTriggerSources(RepositoryParamResource):
  """ Custom verb to fetch the list of build sources for the trigger config. """
  @require_repo_admin
  @nickname('listTriggerBuildSources')
  def get(self, namespace, repository, trigger_uuid):
    """ List the build sources for the trigger configuration thus far. """
    try:
      trigger = model.get_build_trigger(namespace, repository, trigger_uuid)
    except model.InvalidBuildTriggerException:
      abort(404)

    user_permission = UserPermission(trigger.connected_user.username)
    if user_permission.can():
      trigger_handler = BuildTrigger.get_trigger_for_service(trigger.service.name)
        
      return {
        'sources': trigger_handler.list_build_sources(trigger.auth_token)
      }
    else:
      abort(403)