# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime as dt
import functools
import logging

from trytond.config import config
from trytond.exceptions import UserError, UserWarning
from trytond.model import ModelSQL, ModelView, Workflow, fields
from trytond.tools import firstline
from trytond.transaction import Transaction
from trytond.pool import Pool
from trytond.pyson import Eval

logger = logging.getLogger(__name__)
clean_days = config.getint('error', 'clean_days', default=90)


def set_user(field):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(cls, records, *args, **kwargs):
            result = func(cls, records, *args, **kwargs)
            cls.write(
                [r for r in records
                    if not getattr(r, field)], {
                    field: Transaction().user,
                    })
            return result
        return wrapper
    return decorator


def reset_user(*fields):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(cls, records, *args, **kwargs):
            result = func(cls, records, *args, **kwargs)
            cls.write(records, {f: None for f in fields})
            return result
        return wrapper
    return decorator


class Error(Workflow, ModelView, ModelSQL):
    "Error"
    __name__ = 'ir.error'

    origin = fields.Reference("Origin", [
            ('ir.cron', "Action"),
            ('ir.queue', "Task"),
            ], readonly=True)
    origin_string = origin.translated('origin')
    message = fields.Text("Message", readonly=True)
    description = fields.Text("Description", readonly=True)
    summary = fields.Function(fields.Char("Summary"), 'on_change_with_summary')

    processed_by = fields.Many2One(
        'res.user', "Processed by",
        states={
            'readonly': Eval('state').in_(['processing', 'solved']),
            },
        depends=['state'])
    solved_by = fields.Many2One(
        'res.user', "Solved by",
        states={
            'readonly': Eval('state').in_(['solved']),
            },
        depends=['state'])

    state = fields.Selection([
            ('open', "Open"),
            ('processing', "Processing"),
            ('solved', "Solved"),
            ], "State", readonly=True, select=True)

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._transitions |= {
            ('open', 'processing'),
            ('processing', 'solved'),
            ('processing', 'open'),
            }
        cls._buttons.update({
                'open': {
                    'invisible': Eval('state') != 'processing',
                    'depends': ['state'],
                    },
                'process': {
                    'invisible': Eval('state') != 'open',
                    'depends': ['state'],
                    },
                'solve': {
                    'invisible': Eval('state') != 'processing',
                    'depends': ['state'],
                    },
                })

    @classmethod
    def default_state(cls):
        return 'open'

    @fields.depends('message')
    def on_change_with_summary(self, name=None):
        return firstline(self.message or '')

    def get_rec_name(self, name):
        if self.origin:
            return "%s - %s" % (self.origin_string, self.origin.rec_name)
        return super().get_rec_name(name)

    @classmethod
    def log(cls, origin, exception):
        try:
            assert isinstance(exception, (UserError, UserWarning))
            with Transaction().new_transaction(autocommit=True):
                if not cls.search([
                            ('origin', '=', str(origin)),
                            ('message', '=', exception.message),
                            ('description', '=', exception.description),
                            ('state', '!=', 'solved'),
                            ]):
                    cls.create([{
                                'origin': str(origin),
                                'message': exception.message,
                                'description': exception.description,
                                }])
        except Exception:
            logger.critical(
                "failed to store exception %s of %s", exception, origin,
                exc_info=True)

    @classmethod
    def clean(cls, date=None):
        if date is None:
            date = (
                dt.datetime.now() - dt.timedelta(days=clean_days))
        errors = cls.search([('create_date', '<', date)])
        cls.delete(errors)

    @classmethod
    @ModelView.button
    @Workflow.transition('open')
    @reset_user('processed_by')
    def open(cls, errors):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('processing')
    @set_user('processed_by')
    def process(cls, errors):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('solved')
    @set_user('solved_by')
    def solve(cls, errors):
        pool = Pool()
        Cron = pool.get('ir.cron')
        Queue = pool.get('ir.queue')
        for error in errors:
            if isinstance(error.origin, Cron):
                Cron.__queue__.run_once([error.origin])
            elif isinstance(error.origin, Queue):
                task = error.origin
                Queue.push(task.name, task.data)
