# coding: utf-8
import logging
import geoalchemy2 as ga
import sqlalchemy as sa
import re

import geoalchemy2.shape
from shapely import wkb

import simplejson  # does not cast to unicode strings

from yandex.maps.wiki.utils import require, geojson_to_mercator_wkb, geoalchemy_to_geojson
from yandex.maps.wiki import db, fastcgihelpers as fh
from yandex.maps.wiki import tasks
from yandex.maps.wiki import config
from yandex.maps.wiki import pgpool3
from yandex.maps.wiki.tasks import EM, grinder
from yandex.maps.wiki.tasks import models

from maps.wikimap.mapspro.libs.python import acl
from maps.wikimap.mapspro.libs.python.social import feedback

TASK_NAME = 'reject_feedback'
ACL_PATH = 'mpro/tools/group-operations/reject-feedback'

GEOMETRY = 'geometry'
SOURCES = 'sources'
WORKFLOWS = 'workflows'
TYPES = 'types'
UID = 'uid'
REJECT_REASON = 'reject-reason'


def validateGeometry(geometry):
    require(
        geometry.is_valid and geometry.geom_type == 'Polygon',
        fh.ServiceException(
            "Invalid geometry type: \'" + geometry.geom_type + "'",
            status='ERR_BAD_REQUEST')
    )


def validateType(feedback_type):
    require(
        feedback_type in feedback.all_types(),
        fh.ServiceException(
            "Malformed type '" + feedback_type + "'",
            status='ERR_BAD_REQUEST')
    )


def validateWorkflow(feedback_workflow):
    require(
        feedback_workflow in feedback.all_workflows(),
        fh.ServiceException(
            "Malformed workflow '" + feedback_workflow + "'",
            status='ERR_BAD_REQUEST')
    )


def validateSource(feedback_source):
    require(
        re.match(r'^[a-z\d_-]+$', feedback_source),
        fh.ServiceException(
            "source must contain only low register letters, digits, and '-' as separator. Failed for '" + feedback_source + "'",
            status='ERR_BAD_REQUEST'
        )
    )


def checkPermission(uid):
    require(
        acl.is_permission_granted(pgpool3.get_pgpool(
            db.CORE_DB), ACL_PATH, uid, None),
        fh.ServiceException('forbidden', status='ERR_FORBIDDEN')
    )


def get_reject_feedback_task_or_context_args(geometry_str, workflows, sources, types, reject_reason):
    assert geometry_str is not None
    assert reject_reason is not None

    if workflows is None:
        workflows_em = EM.workflows()
    else:
        workflows_em = EM.workflows(*[EM.workflow(w) for w in workflows])

    if sources is None:
        sources_em = EM.sources()
    else:
        sources_em = EM.sources(*[EM.source(s) for s in sources])

    if types is None:
        types_em = EM.types()
    else:
        types_em = EM.types(*[EM.type(t) for t in types])

    return (
        EM.aoi(EM.geometry(geometry_str)),
        workflows_em,
        sources_em,
        types_em,
        EM.rejectReason(reject_reason)
    )


def get_reject_feedback_task_type(geometry_str, workflows, sources, types, reject_reason):
    args_tuple = get_reject_feedback_task_or_context_args(
        geometry_str, workflows, sources, types, reject_reason)
    return EM.reject_feedback_task_type(
        *args_tuple
    )


def get_reject_feedback_context(geometry_str, workflows, sources, types, reject_reason):
    args_tuple = get_reject_feedback_task_or_context_args(
        geometry_str, workflows, sources, types, reject_reason)
    return EM.reject_feedback_context(
        *args_tuple
    )


@tasks.register_task_type(name=TASK_NAME)
class RejectFeedback:
    @staticmethod
    def capabilities_ET():
        return get_reject_feedback_task_type(
            geometry_str='{"type": "Polygon", "coordinates": [[[0,0],[1,1],[2,2],[2,0],[0,0]]]}',
            workflows=feedback.all_workflows(),
            sources=['some', 'possible', 'sources'],
            types=feedback.all_types(),
            reject_reason='spam'
        )

    @staticmethod
    def create(uid, request):
        checkPermission(uid)
        try:
            for param in UID, GEOMETRY, REJECT_REASON:
                require(
                    param in request.values,
                    fh.ServiceException('param {0!r} required'.format(param), status='ERR_BAD_REQUEST')
                )

            task = RejectFeedbackTask()

            task.reject_reason = request.values[REJECT_REASON]

            geometry = wkb.loads(geojson_to_mercator_wkb(
                str(request.values[GEOMETRY])))
            validateGeometry(geometry)
            task.aoi = ga.shape.from_shape(geometry, srid=3395)

            if WORKFLOWS in request.values:
                task.workflows = request.values.get(WORKFLOWS).split(',')
                for feedback_workflow in task.workflows:
                    validateWorkflow(feedback_workflow)

            if SOURCES in request.values:
                task.sources = request.values.get(SOURCES).split(',')
                for feedback_source in task.sources:
                    validateSource(feedback_source)

            if TYPES in request.values:
                task.types = request.values.get(TYPES).split(',')
                for feedback_type in task.types:
                    validateType(feedback_type)

        except fh.ServiceException:
            raise
        except Exception:
            logging.exception('invalid request')
            raise fh.ServiceException('error while parsing request',
                                      status='ERR_BAD_REQUEST')

        task.on_create(uid)
        return task

    @staticmethod
    def launch(session, task_id, request):
        task = session.query(RejectFeedbackTask).get(task_id)
        gateway = grinder.GrinderGateway(
            config.get_config().grinder_params.host)
        return gateway.submit(task.get_grinder_args())


class RejectFeedbackTask(models.Task):
    __tablename__ = 'reject_feedback_task'
    __table_args__ = {'schema': 'service'}
    __mapper_args__ = {'polymorphic_identity': 'reject_feedback'}

    id = sa.Column(sa.BigInteger, sa.ForeignKey('service.task.id'), primary_key=True)
    aoi = sa.Column(ga.Geometry('POLYGON', srid=3395), nullable=False)
    workflows = sa.Column(sa.ARRAY(sa.String))
    sources = sa.Column(sa.ARRAY(sa.String))
    types = sa.Column(sa.ARRAY(sa.String))
    reject_reason = sa.Column(sa.String, nullable=False)

    def context_ET_brief(self, *args, **kwargs):
        return self.context_ET_full(*args, **kwargs)

    def context_ET_full(self, *args, **kwargs):
        return get_reject_feedback_context(
            simplejson.dumps(geoalchemy_to_geojson(self.aoi)),
            self.workflows,
            self.sources,
            self.types,
            self.reject_reason
        )

    def get_grinder_args(self):
        return {
            'type': TASK_NAME,
            'taskId': self.id,
        }
