# w.c.s. - web application for online forms
# Copyright (C) 2005-2017  Entr'ouvert
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.

import datetime

from .qommon.misc import simplify
from .qommon.xml_storage import XmlStorableObject
from wcs.formdef import FormDef
from wcs.workflows import Workflow

class LoggedError(XmlStorableObject):
    _names = 'logged-errors'
    _xml_tagname = 'error'
    _indexes = ['tech_id']
    _hashed_indexes = ['formdef_id', 'workflow_id']

    summary = None
    formdata_id = None
    formdef_id = None
    workflow_id = None
    status_id = None
    status_item_id = None
    expression = None
    expression_type = None
    traceback = None
    exception_class = None
    exception_message = None
    occurences_count = 0
    first_occurence_timestamp = None
    latest_occurence_timestamp = None
    acked = False

    # declarations for serialization
    XML_NODES = [
            ('summary', 'str'), ('traceback', 'str'),
            ('exception_class', 'str'), ('exception_message', 'str'),
            ('expression', 'str'), ('expression_type', 'str'),
            ('formdata_id', 'str'), ('formdef_id', 'str'), ('workflow_id', 'str'),
            ('status_id', 'str'), ('status_item_id', 'str'),
            ('occurences_count', 'int'),
            ('first_occurence_timestamp', 'datetime'),
            ('latest_occurence_timestamp', 'datetime'),
            ('acked', 'bool')]

    @classmethod
    def record(cls, error_summary, plain_error_msg=None, formdata=None,
               formdef=None, workflow=None, status=None, status_item=None,
               expression=None, expression_type=None, exception=None):
        error = cls()
        error.summary = error_summary
        error.traceback = plain_error_msg
        error.expression = expression
        error.expression_type = expression_type

        if exception:
            error.exception_class = exception.__class__.__name__
            error.exception_message = str(exception)

        if formdata:
            error.formdata_id = str(formdata.id)
            error.formdef_id = formdata.formdef.id
            error.workflow_id = formdata.formdef.workflow.id
        elif formdef:
            error.formdef_id = formdef.id
            error.workflow_id = formdef.workflow.id

        if not error.formdef_id:
            # cannot attach error to formdef, don't record in journal, it will
            # still be sent by email to administrators.
            return

        if not error.workflow_id:
            error.workflow_id = workflow.id

        if status_item:
            error.status_item_id = status_item.id
            if getattr(status_item, 'parent', None):
                error.status_id = status_item.parent.id
        if status:
            error.status_id = status.id

        error.first_occurence_timestamp = datetime.datetime.now()
        error.id = '%s-%s' % (
                error.first_occurence_timestamp.strftime('%Y%m%d-%H%M%S'),
                error.tech_id,
                )
        existing_error = cls.get_on_index(error.tech_id, 'tech_id', ignore_errors=True)
        if existing_error:
            error = existing_error
        error.occurences_count += 1
        error.latest_occurence_timestamp = datetime.datetime.now()
        error.store()
        return error

    @classmethod
    def record_exception(cls, error_summary, plain_error_msg, publisher):
        try:
            context = publisher.substitutions.get_context_variables()
        except Exception as e:
            return
        formdata_id = context.get('form_number_raw')
        formdef_urlname = context.get('form_slug')
        if not formdef_urlname:
            # cannot attach error to formdef, don't record in journal, it will
            # still be sent by email to administrators.
            return
        formdef = FormDef.get_by_urlname(formdef_urlname)
        formdata = formdef.data_class().get(formdata_id, ignore_errors=True)
        return cls.record(error_summary, plain_error_msg, formdata=formdata,
                   formdef=formdef, workflow=formdef.workflow)

    @property
    def tech_id(self):
        tech_id = '%s-%s-' % (self.formdef_id, self.workflow_id)
        if self.status_id:
            tech_id += '%s-' % self.status_id
        if self.status_item_id:
            tech_id += '%s-' % self.status_item_id
        tech_id += '%s' % simplify(self.summary)
        if self.exception_class:
            tech_id += '-%s' % self.exception_class
        if self.exception_message:
            tech_id += '-%s' % simplify(self.exception_message)
        return tech_id[:200]

    def get_formdef(self):
        return FormDef.get(self.formdef_id, ignore_errors=True)

    def get_workflow(self):
        return Workflow.get(self.workflow_id, ignore_errors=True)

    def get_formdata(self):
        if not self.formdata_id:
            return None
        return self.get_formdef().data_class().get(self.formdata_id, ignore_errors=True)

    def get_status(self):
        if not self.status_id:
            return None
        workflow = self.get_workflow()
        if not workflow:
            return None
        for status in workflow.possible_status:
            if status.id == self.status_id:
                return status
        return None

    def get_status_item(self):
        status = self.get_status()
        if not status or not status.items:
            return None
        for status_item in status.items:
            if status_item.id == self.status_item_id:
                return status_item
        return None
