# -*- coding: utf-8 -*-

from collections import OrderedDict
from common.models import Job, DeletedJob
from django.http import HttpResponse, HttpResponseNotFound, Http404, HttpResponseServerError, \
    HttpResponseForbidden, HttpResponseGone, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View
import json
import logging
import re
from common.util.clients import StartrekClient, TaskError
from mobilepage.models import MobileJob


class Api(View):
    http_method_names = ['get', 'post']

    @classmethod
    def as_view(cls, *args, **kwargs):
        return csrf_exempt(super(Api, cls).as_view(*args, **kwargs))

    def dispatch(self, request, *args, **kwargs):
        args = list(args)
        fmt = args.pop()

        resp_headers = {}
        # =======================================================================
        # CORS
        # =======================================================================
        origin = request.environ.get('HTTP_ORIGIN', '')
        if origin.endswith('.yandex-team.ru'):
            resp_headers['Access-Control-Allow-Origin'] = origin

        try:
            r = super(Api, self).dispatch(request, *args, **kwargs)
        except Http404 as e:
            if fmt == 'json':
                return HttpResponseNotFound(content=json.dumps({'success': False, 'error': str(e)}),
                                            content_type='application/json',
                                            )
            else:
                return HttpResponseNotFound(e)
        except Http400 as e:
            if fmt == 'json':
                return HttpResponse(
                    content=json.dumps({'success': False, 'error': str(e)}),
                    status=400,
                    content_type='application/json'
                )
            else:
                return HttpResponse(
                    content=e,
                    status=400,
                )
        except Http423 as e:
            if fmt == 'json':
                return HttpResponse(
                    content=json.dumps({'success': False, 'error': str(e)}),
                    status=423,
                    content_type='application/json'
                )
            else:
                return HttpResponse(
                    content=e,
                    status=423,
                )
        except Http403 as e:
            if fmt == 'json':
                return HttpResponseForbidden(json.dumps({'success': False, 'error': str(e)}),
                                             content_type='application/json')
            else:
                return HttpResponseForbidden(e)
        except Exception as e:
            logging.exception("API ERROR in {}:".format(request.path))
            if fmt == 'json':
                return HttpResponseServerError(json.dumps({'success': False, 'error': str(e)}),
                                               content_type='application/json')
            else:
                return HttpResponseServerError(e)
        coords = self.request.GET.get('coords', '')
        if coords:
            m = re.match(r'x:([^,]*),y:([^,]*),z:([^,]*)', coords)
            if m is not None:
                (x, y, z) = m.group(1, 2, 3)
                r = self.matrix(r, x, y, z)
        callback = self.request.GET.get('callback', '')
        if isinstance(r, HttpResponse):
            for header in list(resp_headers.keys()):
                r[header] = resp_headers[header]
            return r
        if fmt == 'json':
            if callback:
                response = HttpResponse(
                    callback + '(' + json.dumps(r) + ');',
                    content_type='application/json'
                )
            else:
                response = HttpResponse(json.dumps(r), content_type='application/json')
        elif fmt == 'csv':
            response = HttpResponse(self.render_csv(r), content_type='text/csv')
        elif fmt == 'csvs':
            response = HttpResponse(self.render_csvs(r), content_type='text/csv')
        else:
            response = HttpResponse(self.render_xml(r), content_type='application/xml')
        for header in list(resp_headers.keys()):
            response[header] = resp_headers[header]
        return response

    @staticmethod
    def render_xml(data):
        """
        Render data to XML format

        >>> Api().render_xml([
        ...    {'key1': 'value11', 'key2': 12},
        ...    {'key1': '<value21>', 'key2': 'value & 22'},])
        ["<?xml version='1.0' encoding='UTF-8'?>\\n", '<response>\\n', '    <result>\\n        <key2>12</key2>\\n
         <key1>value11</key1>\\n    </result>\\n', '    <result>\\n        <key2>value &amp; 22</key2>\\n
         <key1>&lt;value21&gt;</key1>\\n    </result>\\n', '</response>']
        """
        if not data:
            return "<?xml version='1.0' encoding='UTF-8'?>\n<response></response>\n"
        assert hasattr(data[0], 'keys')
        escaper = lambda x: x.replace('&', '&amp;'). \
            replace('>', '&gt;'). \
            replace('<', '&lt;')
        r = ["<?xml version='1.0' encoding='UTF-8'?>\n", '<response>\n']
        for item in data:
            line = ['    <result>']
            for k in item:
                line.append('        <{0}>{1}</{0}>'.format(k, escaper(str(item[k]))))
            line.append('    </result>\n')
            r.append('\n'.join(line))
        r.append('</response>')
        return r

    @staticmethod
    def _render_csv(data, escaper):
        assert hasattr(data[0], 'keys')
        r = [';'.join(list(data[0].keys())), '\r\n']
        for item in data:
            line = []
            for k in item:
                value = escaper(str(item[k]))
                if (value.find(';') != -1
                    or value.find(',') != -1
                    or value.find(' ') != -1
                    or value.find('-') != -1  # Yandex JIRA CSV formatting
                    or value.find('\n') != -1):
                    line.append('"{}"'.format(value.replace('"', '""')))
                else:
                    line.append(value)
            r.append(';'.join(line))
            r.append('\r\n')
        r.pop()
        return r

    def render_csv(self, data):
        """
        Render data to CSV format: https://ru.wikipedia.org/wiki/CSV
        Data must be list (tuple) of dicts

        >>> Api().render_csv([
        ...    {'key1': 'value11', 'key2': 'value12'},
        ...    {'key1': 'value21', 'key2': 'value22'},])
        ['key2;key1', '\\r\\n', 'value12;value11', '\\r\\n', 'value22;value21']

        >>> Api().render_csv([
        ...    {'key1': 'value; 11', 'key2': 'value12'},
        ...    {'key1': 'value, "21"', 'key2': 'value\\r\\n22'},])
        """
        if not data:
            return ""
        return self._render_csv(data, lambda x: x)

    def render_csvs(self, data):
        """
        Render data to simple CSV format - CSV without line breaks in values

        >>> Api().render_csvs([
        ...    {'key1': 'value; 11', 'key2': 'value12'},
        ...    {'key1': 'value, "21"', 'key2': 'value\\r\\n22'},])
        """
        if not data:
            return ''
        return self._render_csv(
            data,
            lambda x: x.replace('\r', '').replace('\n', ' ')
        )

    @staticmethod
    def matrix(r, x, y, z):
        row0 = r[0]
        r_cols = list(row0.keys())
        if x in r_cols and y in r_cols and z in r_cols:
            data = {}
            y_cols = {}
            x_rows = {}
            for row in r:
                y_cols[str(row[y])] = 1
                x_rows[str(row[x])] = 1
                if str(row[x]) not in data:
                    data[str(row[x])] = {}
                data[str(row[x])][str(row[y])] = row[z]
            x_rows = list(x_rows.keys())
            try:
                x_rows.sort(key=int)
            except:
                x_rows.sort()
            y_cols = list(y_cols.keys())
            try:
                y_cols.sort(key=int)
            except:
                y_cols.sort()
            # print y_cols
            for data_row in list(data.values()):
                for y_col in y_cols:
                    if y_col not in data_row:
                        data_row[str(y_col)] = 0
            res = []
            for x_row in x_rows:
                res_row = [(x, x_row)]
                for y_col in y_cols:
                    res_row.append((y_col, data[x_row][str(y_col)]))
                res.append(OrderedDict(res_row))
            return res
        if y in r_cols and z in r_cols and not x:
            res_row = []
            for row in r:
                res_row.append((row[y], row[z]))
            return [OrderedDict(res_row)]

        return r


class ApiJob(Api):
    def dispatch(self, request, *args, **kwargs):
        args = list(args)
        try:
            args[0] = Job.objects.get(n=args[0])
        except Job.DoesNotExist:
            deleted = False
            if DeletedJob.objects.filter(n=args[0]):
                deleted = True
                msg = 'Job had been deleted'
            else:
                msg = 'Job not found'

            if args[-1] == 'json':
                if deleted:
                    return HttpResponseGone(
                        json.dumps([{
                            'success': False,
                            'error': msg,
                            'msg': msg,  # backwards compatibility
                        }]),
                        content_type='application/json')
                else:
                    return HttpResponseNotFound(
                        json.dumps([{
                            'success': False,
                            'error': msg,
                            'msg': msg,  # backwards compatibility
                        }]),
                        content_type='application/json')
            else:
                if deleted:
                    return HttpResponseGone(msg)
                else:
                    return HttpResponseNotFound(msg)
        return super(ApiJob, self).dispatch(request, *args, **kwargs)


class ApiMobileJob(Api):
    def dispatch(self, request, *args, **kwargs):
        args = list(args)
        try:
            args[0] = MobileJob.objects.get(n=args[0])
        except MobileJob.DoesNotExist:
            msg = 'Job not found'
            if args[-1] == 'json':
                return HttpResponse(json.dumps([{'success': False,
                                                 'error': msg,
                                                 'msg': msg,  # backwards compatibility
                                                 }]),
                                    content_type='application/json')
            else:
                return HttpResponseNotFound(msg)
        return super(ApiMobileJob, self).dispatch(request, *args, **kwargs)


class ApiTask(Api):
    def dispatch(self, request, *args, **kwargs):
        args = list(args)
        try:
            if args[0] is None:
                if args[-1] == 'json':
                    return HttpResponseBadRequest(json.dumps([{'success': False,
                                                               'error': 'Task not specified'
                                                               }]),
                                                  content_type='application/json')
                else:
                    return HttpResponseBadRequest('Task not specified')
            args[0] = args[0].upper()
            StartrekClient().check_task_exists(args[0])
        except TaskError as e:
            if args[-1] == 'json':
                return HttpResponse(json.dumps([{'success': False,
                                                 'error': e.args
                                                 }]),
                                    content_type='application/json')
            else:
                return HttpResponseNotFound(e.message)
        return super(ApiTask, self).dispatch(request, *args, **kwargs)


class Http400(Exception):
    pass


class Http423(Exception):
    pass


class Http403(Exception):
    pass
