from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.db.models import IntegerField
from django.utils import formats
from django.utils.encoding import force_text
from django.utils import six


class ModelFieldStringifier(object):

    custom_stringify_methods = {
        models.DateTimeField: 'stringify_datetime',
        models.DateField: 'stringify_date',
        models.ForeignKey: 'stringify_fk'
    }

    @classmethod
    def stringify(cls, field, value=None):
        value = value
        field_class = field.__class__

        if field_class in cls.custom_stringify_methods:
            stringifier = cls.custom_stringify_methods[field_class]

            if isinstance(stringifier, six.string_types):
                stringifier = getattr(cls, stringifier)

            return stringifier(value, field)

        if value is None:
            return None

        if isinstance(field, IntegerField):
            value = int(value)

        if isinstance(field, ArrayField):
            return force_text(', '.join(value))

        if getattr(field, 'choices', None):
            try:
                choices_dict = dict(field.choices)
                value = choices_dict[value]
                return force_text(value)
            except (KeyError, TypeError):
                return force_text(value)

        return force_text(value)

    @staticmethod
    def stringify_datetime(value, *args):
        if value is None:
            return None

        # Like models.DateField(default=date.today)
        if hasattr(value, '__call__'):
            value = value()

        return formats.date_format(value, "DATETIME_FORMAT")

    @staticmethod
    def stringify_date(value, *args):
        if value is None:
            return None

        # Like models.DateField(default=date.today)
        if hasattr(value, '__call__'):
            value = value()

        return formats.date_format(value, "DATE_FORMAT")

    @staticmethod
    def stringify_fk(value, field):
        if value is None:
            return None

        # iterate over to fields trying to look up the fk
        if not isinstance(value, field.related_model):
            success = False
            to_fields = field.to_fields or []
            if 'pk' not in to_fields:
                to_fields.append('pk')

            for to_field in to_fields:
                if not to_field:
                    continue

                try:
                    value = field.related_model.objects.get(**{to_field: value})
                    success = True
                    break
                except ObjectDoesNotExist:
                    pass
            if not success:
                return None

        return force_text(value)

    @classmethod
    def add_stringifier(cls, field_class, callback):
        cls.custom_stringify_methods[field_class] = callback