#!/usr/bin/env python
# Licensed to Cloudera, Inc. under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  Cloudera, Inc. licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import logging
import re
import sys
import time

from django import forms
from django.contrib import messages
from django.contrib.auth.models import User
from django.db.models import Q
from django.http import HttpResponse, QueryDict
from django.shortcuts import redirect
from django.utils.html import escape
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse

from desktop.appmanager import get_apps_dict
from desktop.context_processors import get_app_name
from desktop.lib.paginator import Paginator
from desktop.lib.django_util import JsonResponse
from desktop.lib.django_util import copy_query_dict, format_preserving_redirect, render
from desktop.lib.django_util import login_notrequired, get_desktop_uri_prefix
from desktop.lib.exceptions_renderable import PopupException
from desktop.models import Document
from desktop.lib.parameterization import find_variables
from notebook.models import escape_rows

import beeswax.forms
import beeswax.design
import beeswax.management.commands.beeswax_install_examples

from beeswax import common, data_export, models
from beeswax.models import QueryHistory, SavedQuery, Session
from beeswax.server import dbms
from beeswax.server.dbms import expand_exception, get_query_server_config, QueryServerException


LOG = logging.getLogger(__name__)


def index(request):
  return execute_query(request)

"""
Design views
"""

def save_design(request, form, type_, design, explicit_save):
  """
  save_design(request, form, type_, design, explicit_save) -> SavedQuery

  A helper method to save the design:
    * If ``explicit_save``, then we save the data in the current design.
    * If the user clicked the submit button, we do NOT overwrite the current
      design. Instead, we create a new "auto" design (iff the user modified
      the data). This new design is named after the current design, with the
      AUTO_DESIGN_SUFFIX to signify that it's different.

  Need to return a SavedQuery because we may end up with a different one.
  Assumes that form.saveform is the SaveForm, and that it is valid.
  """
  authorized_get_design(request, design.id)
  assert form.saveform.is_valid()
  sub_design_form = form # Beeswax/Impala case

  if type_ == models.HQL:
    design_cls = beeswax.design.HQLdesign
  elif type_ == models.IMPALA:
    design_cls = beeswax.design.HQLdesign
  elif type_ == models.SPARK:
    from spark.design import SparkDesign
    design_cls = SparkDesign
    sub_design_form = form.query
  else:
    raise ValueError(_('Invalid design type %(type)s') % {'type': type_})

  design_obj = design_cls(sub_design_form, query_type=type_)
  name = form.saveform.cleaned_data['name']
  desc = form.saveform.cleaned_data['desc']

  return _save_design(request.user, design, type_, design_obj, explicit_save, name, desc)


def _save_design(user, design, type_, design_obj, explicit_save, name=None, desc=None):
  # Design here means SavedQuery
  old_design = design
  new_data = design_obj.dumps()

  # Auto save if (1) the user didn't click "save", and (2) the data is different.
  # Create an history design if the user is executing a shared design.
  # Don't generate an auto-saved design if the user didn't change anything.
  if explicit_save and (not design.doc.exists() or design.doc.get().can_write_or_exception(user)):
    design.name = name
    design.desc = desc
    design.is_auto = False
  elif design_obj != old_design.get_design():
    # Auto save iff the data is different
    if old_design.id is not None:
      # Clone iff the parent design isn't a new unsaved model
      design = old_design.clone(new_owner=user)
      if not old_design.is_auto:
        design.name = old_design.name + models.SavedQuery.AUTO_DESIGN_SUFFIX
    else:
      design.name = models.SavedQuery.DEFAULT_NEW_DESIGN_NAME
    design.is_auto = True

  design.name = design.name[:64]
  design.type = type_
  design.data = new_data

  design.save()

  LOG.info('Saved %s design "%s" (id %s) for %s' % (explicit_save and '' or 'auto ', design.name, design.id, design.owner))

  if design.doc.exists():
    design.doc.update(name=design.name, description=design.desc)
  else:
    Document.objects.link(design, owner=design.owner, extra=design.type, name=design.name, description=design.desc)

  if design.is_auto:
    design.doc.get().add_to_history()

  return design


def delete_design(request):
  if request.method == 'POST':
    ids = request.POST.getlist('designs_selection')
    designs = dict([(design_id, authorized_get_design(request, design_id, owner_only=True)) for design_id in ids])

    if None in designs.values():
      LOG.error('Cannot delete non-existent design(s) %s' % ','.join([key for key, name in designs.items() if name is None]))
      return list_designs(request)

    for design in designs.values():
      if request.POST.get('skipTrash', 'false') == 'false':
        design.doc.get().send_to_trash()
      else:
        design.doc.all().delete()
        design.delete()
    return redirect(reverse(get_app_name(request) + ':list_designs'))
  else:
    return render('confirm.mako', request, {'url': request.path, 'title': _('Delete design(s)?')})


def restore_design(request):
  if request.method == 'POST':
    ids = request.POST.getlist('designs_selection')
    designs = dict([(design_id, authorized_get_design(request, design_id)) for design_id in ids])

    if None in designs.values():
      LOG.error('Cannot restore non-existent design(s) %s' % ','.join([key for key, name in designs.items() if name is None]))
      return list_designs(request)

    for design in designs.values():
      design.doc.get().restore_from_trash()
    return redirect(reverse(get_app_name(request) + ':list_designs'))
  else:
    return render('confirm.mako', request, {'url': request.path, 'title': _('Restore design(s)?')})


def clone_design(request, design_id):
  """Clone a design belonging to any user"""
  design = authorized_get_design(request, design_id)

  if design is None:
    LOG.error('Cannot clone non-existent design %s' % (design_id,))
    return list_designs(request)

  copy = design.clone(request.user)
  copy.save()

  name = copy.name + '-copy'
  design.doc.get().copy(content_object=copy, name=name, owner=request.user)

  messages.info(request, _('Copied design: %(name)s') % {'name': design.name})

  return format_preserving_redirect(request, reverse(get_app_name(request) + ':execute_design', kwargs={'design_id': copy.id}))


def list_designs(request):
  """
  View function for show all saved queries.

  We get here from /beeswax/list_designs?filterargs, with the options being:
    page=<n>    - Controls pagination. Defaults to 1.
    user=<name> - Show design items belonging to a user. Default to all users.
    type=<type> - <type> is "hql", for saved query type. Default to show all.
    sort=<key>  - Sort by the attribute <key>, which is one of:
                    "date", "name", "desc", and "type" (design type)
                  Accepts the form "-date", which sort in descending order.
                  Default to "-date".
    text=<frag> - Search for fragment "frag" in names and descriptions.
  """
  DEFAULT_PAGE_SIZE = 20
  app_name = get_app_name(request)

  # Extract the saved query list.
  prefix = 'q-'
  querydict_query = _copy_prefix(prefix, request.GET)
  # Manually limit up the user filter.
  querydict_query[ prefix + 'type' ] = app_name
  # Get search filter input if any
  search_filter = request.GET.get('text', None)
  if search_filter is not None:
    querydict_query[ prefix + 'text' ] = search_filter

  page, filter_params = _list_designs(request.user, querydict_query, DEFAULT_PAGE_SIZE, prefix)

  return render('list_designs.mako', request, {
    'page': page,
    'filter_params': filter_params,
    'prefix': prefix,
    'user': request.user,
    'designs_json': json.dumps([query.id for query in page.object_list])
  })


def list_trashed_designs(request):
  DEFAULT_PAGE_SIZE = 20
  app_name= get_app_name(request)

  user = request.user

  # Extract the saved query list.
  prefix = 'q-'
  querydict_query = _copy_prefix(prefix, request.GET)
  # Manually limit up the user filter.
  querydict_query[ prefix + 'type' ] = app_name
  # Get search filter input if any
  search_filter = request.GET.get('text', None)
  if search_filter is not None:
    querydict_query[ prefix + 'text' ] = search_filter

  page, filter_params = _list_designs(user, querydict_query, DEFAULT_PAGE_SIZE, prefix, is_trashed=True)

  return render('list_trashed_designs.mako', request, {
    'page': page,
    'filter_params': filter_params,
    'prefix': prefix,
    'user': request.user,
    'designs_json': json.dumps([query.id for query in page.object_list])
  })


def my_queries(request):
  """
  View a mix of history and saved queries.
  It understands all the GET params in ``list_query_history`` (with a ``h-`` prefix)
  and those in ``list_designs`` (with a ``q-`` prefix). The only thing it disallows
  is the ``user`` filter, since this view only shows what belongs to the user.
  """
  DEFAULT_PAGE_SIZE = 30
  app_name= get_app_name(request)

  # Extract the history list.
  prefix = 'h-'
  querydict_history = _copy_prefix(prefix, request.GET)
  # Manually limit up the user filter.
  querydict_history[ prefix + 'user' ] = request.user
  querydict_history[ prefix + 'type' ] = app_name

  hist_page, hist_filter = _list_query_history(request.user,
                                               querydict_history,
                                               DEFAULT_PAGE_SIZE,
                                               prefix)
  # Extract the saved query list.
  prefix = 'q-'
  querydict_query = _copy_prefix(prefix, request.GET)
  # Manually limit up the user filter.
  querydict_query[ prefix + 'user' ] = request.user
  querydict_query[ prefix + 'type' ] = app_name

  query_page, query_filter = _list_designs(request.user, querydict_query, DEFAULT_PAGE_SIZE, prefix)

  filter_params = hist_filter
  filter_params.update(query_filter)

  return render('my_queries.mako', request, {
    'request': request,
    'h_page': hist_page,
    'q_page': query_page,
    'filter_params': filter_params,
    'designs_json': json.dumps([query.id for query in query_page.object_list])
  })


def list_query_history(request):
  """
  View the history of query (for the current user).
  We get here from /beeswax/query_history?filterargs, with the options being:
    page=<n>            - Controls pagination. Defaults to 1.
    user=<name>         - Show history items from a user. Default to current user only.
                          Also accepts ':all' to show all history items.
    type=<type>         - <type> is "beeswax|impala", for design type. Default to show all.
    design_id=<id>      - Show history for this particular design id.
    sort=<key>          - Sort by the attribute <key>, which is one of:
                            "date", "state", "name" (design name), and "type" (design type)
                          Accepts the form "-date", which sort in descending order.
                          Default to "-date".
    auto_query=<bool>   - Show auto generated actions (drop table, read data, etc). Default True
  """
  DEFAULT_PAGE_SIZE = 100
  prefix = 'q-'

  share_queries = request.user.is_superuser

  querydict_query = request.GET.copy()
  if not share_queries:
    querydict_query[prefix + 'user'] = request.user.username

  app_name = get_app_name(request)
  querydict_query[prefix + 'type'] = app_name

  page, filter_params = _list_query_history(request.user, querydict_query, DEFAULT_PAGE_SIZE, prefix)

  filter = request.GET.get(prefix + 'search') and request.GET.get(prefix + 'search') or ''

  if request.GET.get('format') == 'json':
    resp = {
      'queries': [massage_query_history_for_json(app_name, query_history) for query_history in page.object_list]
    }
    return JsonResponse(resp)


  return render('list_history.mako', request, {
    'request': request,
    'page': page,
    'filter_params': filter_params,
    'share_queries': share_queries,
    'prefix': prefix,
    'filter': filter,
  })


def massage_query_history_for_json(app_name, query_history):
  return {
    'id': query_history.id,
    'design_id': query_history.design.id,
    'query': escape(query_history.query),
    'timeInMs': time.mktime(query_history.submission_date.timetuple()),
    'timeFormatted': query_history.submission_date.strftime("%x %X"),
    'designUrl': reverse(app_name + ':execute_design', kwargs={'design_id': query_history.design.id}),
    'resultsUrl': not query_history.is_failure() and reverse(app_name + ':watch_query_history', kwargs={'query_history_id': query_history.id}) or ""
  }


def download(request, id, format):
  try:
    query_history = authorized_get_query_history(request, id, must_exist=True)
    db = dbms.get(request.user, query_history.get_query_server_config())
    LOG.debug('Download results for query %s: [ %s ]' % (query_history.server_id, query_history.query))

    return data_export.download(query_history.get_handle(), format, db)
  except Exception, e:
    if not hasattr(e, 'message') or not e.message:
      message = e