import json
import socket
from collections import defaultdict, namedtuple

from couchdbkit import Server

from django.conf import settings
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext_lazy
from django.views.decorators.http import require_POST

import requests
from requests.exceptions import HTTPError

from dimagi.utils.couch.database import is_bigcouch
from pillowtop.exceptions import PillowNotFoundError
from pillowtop.utils import (
    get_all_pillows_json,
    get_pillow_config_by_name,
    get_pillow_json,
)

from corehq.apps.domain.decorators import (
    require_superuser,
    require_superuser_or_contractor,
)
from corehq.apps.domain.views.internal import get_project_limits_context
from corehq.apps.hqadmin import escheck, service_checks
from corehq.apps.hqadmin.models import HqDeploy
from corehq.apps.hqadmin.service_checks import run_checks
from corehq.apps.hqadmin.utils import get_celery_stats
from corehq.apps.hqadmin.views.utils import (
    BaseAdminSectionView,
    get_hqadmin_base_context,
)
from corehq.apps.hqwebapp.decorators import use_datatables, use_jquery_ui
from corehq.apps.receiverwrapper.rate_limiter import (
    global_submission_rate_limiter,
)
from corehq.toggles import SUPPORT


class SystemInfoView(BaseAdminSectionView):
    page_title = ugettext_lazy("System Info")
    urlname = 'system_info'
    template_name = "hqadmin/system_info.html"

    @use_datatables
    @use_jquery_ui
    @method_decorator(require_superuser_or_contractor)
    def dispatch(self, request, *args, **kwargs):
        return super(SystemInfoView, self).dispatch(request, *args, **kwargs)

    @property
    def page_context(self):
        environment = settings.SERVER_ENVIRONMENT

        context = get_hqadmin_base_context(self.request)
        context['couch_update'] = self.request.GET.get('couch_update', 5000)
        context['celery_update'] = self.request.GET.get('celery_update', 10000)
        context['db_update'] = self.request.GET.get('db_update', 30000)
        context['self.request'] = getattr(settings, 'CELERY_FLOWER_URL', None)

        context['is_bigcouch'] = is_bigcouch()
        context['rabbitmq_url'] = get_rabbitmq_management_url()
        context['hide_filters'] = True
        context['current_system'] = socket.gethostname()
        context['deploy_history'] = HqDeploy.objects.filter(environment=environment)[:5]

        context['user_is_support'] = hasattr(self.request, 'user') and SUPPORT.enabled(self.request.user.username)

        context['redis'] = service_checks.check_redis()
        context['rabbitmq'] = service_checks.check_rabbitmq(settings.CELERY_BROKER_URL)
        context['celery_stats'] = get_celery_stats()

        context['cluster_health'] = escheck.check_es_cluster_health()

        return context


@require_superuser_or_contractor
def system_ajax(request):
    """
    Utility ajax functions for polling couch and celerymon
    """
    type = request.GET.get('api', None)
    task_limit = getattr(settings, 'CELERYMON_TASK_LIMIT', 12)
    celery_monitoring = getattr(settings, 'CELERY_FLOWER_URL', None)
    if type == "_active_tasks":
        server = Server(settings.COUCH_DATABASE)
        try:
            tasks = [x for x in server.active_tasks() if x['type'] == "indexer"]
        except HTTPError as e:
            if e.response.status_code == 403:
                return JsonResponse({'error': "Unable to access CouchDB Tasks (unauthorized)."}, status_code=500)
            else:
                return JsonResponse({'error': "Unable to access CouchDB Tasks."}, status_code=500)

        if not is_bigcouch():
            return JsonResponse(tasks, safe=False)
        else:
            # group tasks by design doc
            task_map = defaultdict(dict)
            for task in tasks:
                meta = task_map[task['design_document']]
                tasks = meta.get('tasks', [])
                tasks.append(task)
                meta['tasks'] = tasks

            design_docs = []
            for dd, meta in task_map.items():
                meta['design_document'] = dd[len('_design/'):]
                total_changes = sum(task['total_changes'] for task in meta['tasks'])
                for task in meta['tasks']:
                    task['progress_contribution'] = task['changes_done'] * 100 // total_changes

                design_docs.append(meta)
            return JsonResponse(design_docs, safe=False)
    elif type == "_stats":
        return JsonResponse({})
    elif type == "_logs":
        pass
    elif type == 'pillowtop':
        pillow_meta = get_all_pillows_json()
        return JsonResponse(sorted(pillow_meta, key=lambda m: m['name'].lower()), safe=False)

    if celery_monitoring:
        if type == "flower_poll":
            ret = []
            try:
                all_tasks = requests.get(
                    celery_monitoring + '/api/tasks',
                    params={'limit': task_limit},
                    timeout=3,
                ).json()
            except Exception as ex:
                return JsonResponse({'error': "Error with getting from celery_flower: %s" % ex}, status_code=500)

            for task_id, traw in all_tasks.items():
                # it's an array of arrays - looping through [<id>, {task_info_dict}]
                if 'name' in traw and traw['name']:
                    traw['name'] = '.'.join(traw['name'].split('.')[-2:])
                else:
                    traw['name'] = None
                ret.append(traw)
            ret = sorted(ret, key=lambda x: x['succeeded'], reverse=True)
            return HttpResponse(json.dumps(ret), content_type='application/json')
    return HttpResponse('{}', content_type='application/json')


@require_superuser_or_contractor
def check_services(request):

    def get_message(service_name, result):
        if result.exception:
            status = "EXCEPTION"
            msg = repr(result.exception)
        else:
            status = "SUCCESS" if result.success else "FAILURE"
            msg = result.msg
        return "{} (Took {:6.2f}s) {:15}: {}<br/>".format(status, result.duration, service_name, msg)

    statuses = run_checks(list(service_checks.CHECKS))
    results = [
        get_message(name, status) for name, status in statuses
    ]
    return HttpResponse("<pre>" + "".join(results) + "</pre>")


@require_POST
@require_superuser_or_contractor
def pillow_operation_api(request):
    pillow_name = request.POST["pillow_name"]
    operation = request.POST["operation"]
    try:
        pillow_config = get_pillow_config_by_name(pillow_name)
        pillow = pillow_config.get_instance()
    except PillowNotFoundError: