from enum import Enum, unique
from typing import Optional, Dict

from django.core import serializers
from django.db import models

from staff.lib import json
from staff.users.models import User


@unique
class LogAction(Enum):
    CREATED = 'created'
    UPDATED = 'updated'
    DISABLED = 'disabled'
    DELETED = 'deleted'
    OCCUPIED = 'occupied'

    @classmethod
    def choices(cls):
        return tuple((i.value, i.name) for i in cls)


class LogValueError(ValueError):
    pass


class Log(models.Model):
    who = models.ForeignKey('users.User', related_name='map_logs')
    model_name = models.CharField(max_length=255)
    model_pk = models.IntegerField(null=True)
    action = models.CharField(max_length=255, choices=LogAction.choices())
    data = models.TextField(null=True)
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)

    def __repr__(self):
        return f'<Log: `{self.action}` for {self.model_name} #{self.model_pk}>'

    @staticmethod
    def get_model_name(model_class_or_obj):
        if isinstance(model_class_or_obj, models.Model):
            return f'{model_class_or_obj._meta.app_label}.{model_class_or_obj.__class__.__name__}'

        if isinstance(model_class_or_obj, type(models.Model)):
            return f'{model_class_or_obj._meta.app_label}.{model_class_or_obj.__name__}'

        raise ValueError(f'Value {model_class_or_obj} has an unsupported type {type(model_class_or_obj)}')


class LogFactory:
    @staticmethod
    def _build_log(who: User, obj: models.Model, action: LogAction, data: Optional[Dict] = None) -> Log:
        if not isinstance(action, LogAction):
            raise LogValueError(f"Unsupported value `{action}` for map.models.Log action")

        if data is None:
            serialized_data = serializers.serialize('json', [obj])
        else:
            serialized_data = json.dumps(data)

        model_name = Log.get_model_name(obj)
        log = Log(
            who=who,
            model_name=model_name,
            model_pk=obj.pk or None,
            action=action.value,
            data=serialized_data,
        )

        return log

    @staticmethod
    def create_log(who: User, obj: models.Model, action: LogAction, data: Optional[Dict] = None) -> Log:
        log = LogFactory._build_log(who, obj, action, data)
        log.save()
        return log
