from ...conversation import StatementMixin
from ... import constants
from django.db import models
from django.apps import apps
from django.utils import timezone
from django.conf import settings


DJANGO_APP_NAME = constants.DEFAULT_DJANGO_APP_NAME
STATEMENT_MODEL = 'Statement'
RESPONSE_MODEL = 'Response'

if hasattr(settings, 'CHATTERBOT'):
    """
    Allow related models to be overridden in the project settings.
    Default to the original settings if one is not defined.
    """
    DJANGO_APP_NAME = settings.CHATTERBOT.get(
        'django_app_name',
        DJANGO_APP_NAME
    )
    STATEMENT_MODEL = settings.CHATTERBOT.get(
        'statement_model',
        STATEMENT_MODEL
    )
    RESPONSE_MODEL = settings.CHATTERBOT.get(
        'response_model',
        RESPONSE_MODEL
    )


class AbstractBaseStatement(models.Model, StatementMixin):
    """
    The abstract base statement allows other models to
    be created using the attributes that exist on the
    default models.
    """

    text = models.CharField(
        unique=True,
        blank=False,
        null=False,
        max_length=constants.STATEMENT_TEXT_MAX_LENGTH
    )

    extra_data = models.CharField(
        max_length=500,
        blank=True
    )

    # This is the confidence with which the chat bot believes
    # this is an accurate response. This value is set when the
    # statement is returned by the chat bot.
    confidence = 0

    class Meta:
        abstract = True

    def __str__(self):
        if len(self.text.strip()) > 60:
            return '{}...'.format(self.text[:57])
        elif len(self.text.strip()) > 0:
            return self.text
        return '<empty>'

    def __init__(self, *args, **kwargs):
        super(AbstractBaseStatement, self).__init__(*args, **kwargs)

        # Responses to be saved if the statement is updated with the storage adapter
        self.response_statement_cache = []

    @property
    def in_response_to(self):
        """
        Return the response objects that are for this statement.
        """
        ResponseModel = apps.get_model(DJANGO_APP_NAME, RESPONSE_MODEL)
        return ResponseModel.objects.filter(statement=self)

    def add_extra_data(self, key, value):
        """
        Add extra data to the extra_data field.
        """
        import json

        if not self.extra_data:
            self.extra_data = '{}'

        extra_data = json.loads(self.extra_data)
        extra_data[key] = value

        self.extra_data = json.dumps(extra_data)

    def add_tags(self, tags):
        """
        Add a list of strings to the statement as tags.
        (Overrides the method from StatementMixin)
        """
        for tag in tags:
            self.tags.create(
                name=tag
            )

    def add_response(self, statement):
        """
        Add a response to this statement.
        """
        self.response_statement_cache.append(statement)

    def remove_response(self, response_text):
        """
        Removes a response from the statement's response list based
        on the value of the response text.

        :param response_text: The text of the response to be removed.
        :type response_text: str
        """
        is_deleted = False
        response = self.in_response.filter(response__text=response_text)

        if response.exists():
            is_deleted = True

        return is_deleted

    def get_response_count(self, statement):
        """
        Find the number of times that the statement has been used
        as a response to the current statement.

        :param statement: The statement object to get the count for.
        :type statement: chatterbot.conversation.Statement

        :returns: Return the number of times the statement has been used as a response.
        :rtype: int
        """
        return self.in_response.filter(response__text=statement.text).count()

    def serialize(self):
        """
        :returns: A dictionary representation of the statement object.
        :rtype: dict
        """
        import json
        data = {}

        if not self.extra_data:
            self.extra_data = '{}'

        data['text'] = self.text
        data['in_response_to'] = []
        data['extra_data'] = json.loads(self.extra_data)

        for response in self.in_response.all():
            data['in_response_to'].append(response.serialize())

        return data


class AbstractBaseResponse(models.Model):
    """
    The abstract base response allows other models to
    be created using the attributes that exist on the
    default models.
    """

    statement = models.ForeignKey(
        STATEMENT_MODEL,
        related_name='in_response',
        on_delete=models.CASCADE
    )

    response = models.ForeignKey(
        STATEMENT_MODEL,
        related_name='responses',
        on_delete=models.CASCADE
    )

    created_at = models.DateTimeField(
        default=timezone.now,
        help_text='The date and time that this response was created at.'
    )

    class Meta:
        abstract = True

    @property
    def occurrence(self):
        """
        Return a count of the number of times this response has occurred.
        """
        ResponseModel = apps.get_model(DJANGO_APP_NAME, RESPONSE_MODEL)

        return ResponseModel.objects.filter(
            statement__text=self.statement.text,
            response__text=self.response.text
        ).count()

    def __str__(self):
        statement = self.statement.text
        response = self.response.text
        return '{} => {}'.format(
            statement if len(statement) <= 20 else statement[:17] + '...',
            response if len(response) <= 40 else response[:37] + '...'
        )

    def serialize(self):
        """
        :returns: A dictionary representation of the statement object.
        :rtype: dict
        """
        data = {}

        data['text'] = self.response.text
        data['created_at'] = self.created_at.isoformat()
        data['occurrence'] = self.occurrence

        return data


class AbstractBaseConversation(models.Model):
    """
    The abstract base conversation allows other models to
    be created using the attributes that exist on the
    default models.
    """

    responses = models.ManyToManyField(
        RESPONSE_MODEL,
        related_name='conversations',
        help_text='The responses in this conversation.'
    )

    class Meta:
        abstract = True

    def __str__(self):
        return str(self.id)


class AbstractBaseTag(models.Model):
    """
    The abstract base tag allows other models to
    be created using the attributes that exist on the
    default models.
    """

    name = models.SlugField(
        max_length=constants.TAG_NAME_MAX_LENGTH
    )

    statements = models.ManyToManyField(
        STATEMENT_MODEL,
        related_name='tags'
    )

    class Meta:
        abstract = True

    def __str__(self):
        return self.name