from .. import db
from . import User, Test, Datum
from .action_type import ActionType


# This is a list of actions together with the type of objects that participate
# in that action.
ACTION_SCHEMAS = [
    (ActionType.SIGNUP, None, None, None,
     lambda u: f'{u} signed up'),
    (ActionType.CONFIRM_EMAIL, None, None, None,
     lambda u: f'{u} confirmed e-mail address {u.email}'),
    (ActionType.UPDATE_USER, User, None, None,
     lambda u, u2: f'{u} updated {u2}'),
    (ActionType.PROMOTE_USER, User, None, None,
     lambda u, u2: f'{u} promoted {u2} to admin'),
    (ActionType.LIST_USER, User, None, None,
     lambda u, u2: f'{u} requested details for {u2}'),
    (ActionType.UPLOAD_AVATAR, None, None, None,
     lambda u: f'{u} uploaded an avatar'),
    (ActionType.RESEND_EMAIL_CONFIRM, None, None, None,
     lambda u: f'{u} requested the e-mail confirmation e-mail to be resent'),
    (ActionType.CREATE_DATUM, User, Datum, None,
     lambda u, d: f'{u} added a new datum: {d}'),
    (ActionType.LIST_DATUM, User, Datum, None,
     lambda u, d: f'{u} requested details for datum {d}'),
    (ActionType.UPDATE_DATUM, User, Datum, None,
     lambda u, d: f'{u} updated datum {d}'),
    (ActionType.CREATE_TEST, User, Test, None,
     lambda u, d: f'{u} added a new test: {d}'),
    (ActionType.LIST_TEST, User, Test, None,
     lambda u, d: f'{u} requested details for test {d}'),
]


def get_action_message(action_type, user, *objs):
    schema = get_schema(action_type)
    objs = filter(lambda obj: obj is not None, objs)
    return schema[4](user, *objs)


def get_schema(type):
    for schema in ACTION_SCHEMAS:
        if schema[0] == type:
            return schema
    raise Exception(f'Could not find action schema with type {type}')


class Action(db.Model):
    """
    This class doesn't inherit from BaseModel because we don't want an
    updated_at field.

    Each action has one actor (a user), and up to 3 other interacted objects,
    which could be of any type, thus we can't make use of foreign keys for them.
    """

    id = db.Column(db.Integer, primary_key=True)
    created_at = db.Column(db.DateTime(timezone=True),
                           server_default=db.func.now(),
                           nullable=False)
    type = db.Column(db.Enum(ActionType), nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    obj1_id = db.Column(db.Integer, default=None)
    obj2_id = db.Column(db.Integer, default=None)
    obj3_id = db.Column(db.Integer, default=None)

    user = db.relationship('User', back_populates='actions', foreign_keys=[user_id])

    def __repr__(self):
        return get_action_message(self.type, self.user, self.obj1, self.obj2,
                                  self.obj3)

    @property
    def obj1(self):
        schema = get_schema(self.type)
        if schema[1] is not None:
            return schema[1].query.filter_by(id=self.obj1_id).first()

    @property
    def obj2(self):
        schema = get_schema(self.type)
        if schema[2] is not None:
            return schema[2].query.filter_by(id=self.obj2_id).first()

    @property
    def obj3(self):
        schema = get_schema(self.type)
        if schema[3] is not None:
            return schema[3].query.filter_by(id=self.obj3_id).first()

    def to_obj(self, whos_asking=None):
        res_obj = dict(
            id=self.id,
            created_at=self.created_at,
            type=self.type,
            user=self.user.to_obj(whos_asking=whos_asking)
        )
        res_obj['obj1'] = self.obj1.to_obj(whos_asking=whos_asking)
        res_obj['obj2'] = self.obj2.to_obj(whos_asking=whos_asking)
        res_obj['obj3'] = self.obj3.to_obj(whos_asking=whos_asking)
        res_obj['message'] = self.__repr__()
        return res_obj

    @staticmethod
    def create(type, user, *args):
        a = Action(type=type, user=user)
        if len(args) >= 1:
            a.obj1_id = args[0].id
        if len(args) >= 2:
            a.obj2_id = args[1].id
        if len(args) >= 3:
            a.obj3_id = args[2].id
        db.session.add(a)
        db.session.commit()
