import warnings

from django.conf import settings
from django.core.exceptions import FieldError
from django.db import models
from django.db.models.constants import LOOKUP_SEP
from django.db.models.expressions import Expression
from django.db.models.fields import FieldDoesNotExist
from django.db.models.fields.related import ForeignObjectRel
from django.utils import six, timezone

try:
    from django.forms.utils import pretty_name
except ImportError:  # Django 1.8
    from django.forms.forms import pretty_name

from .compat import remote_field, remote_model
from .exceptions import FieldLookupError


def deprecate(msg, level_modifier=0):
    warnings.warn(
        "%s See: https://django-filter.readthedocs.io/en/latest/migration.html" % msg,
        DeprecationWarning, stacklevel=3 + level_modifier)


def try_dbfield(fn, field_class):
    """
    Try ``fn`` with the DB ``field_class`` by walking its
    MRO until a result is found.

    ex::
        _try_dbfield(field_dict.get, models.CharField)

    """
    # walk the mro, as field_class could be a derived model field.
    for cls in field_class.mro():
        # skip if cls is models.Field
        if cls is models.Field:
            continue

        data = fn(cls)
        if data:
            return data


# TODO: remove field_types arg with deprecations
def get_all_model_fields(model, field_types=None):
    opts = model._meta

    if field_types is not None:
        return [
            f.name for f in sorted(opts.fields + opts.many_to_many)
            if not isinstance(f, models.AutoField) and
            not (getattr(remote_field(f), 'parent_link', False)) and
            f.__class__ in field_types
        ]

    return [
        f.name for f in sorted(opts.fields + opts.many_to_many)
        if not isinstance(f, models.AutoField) and
        not (getattr(remote_field(f), 'parent_link', False))
    ]


def get_model_field(model, field_name):
    """
    Get a ``model`` field, traversing relationships
    in the ``field_name``.

    ex::

        f = get_model_field(Book, 'author__first_name')

    """
    parts = field_name.split(LOOKUP_SEP)
    opts = model._meta

    # walk relationships
    for name in parts[:-1]:
        try:
            rel = opts.get_field(name)
        except FieldDoesNotExist:
            return None
        if isinstance(rel, ForeignObjectRel):
            opts = rel.related_model._meta
        else:
            opts = remote_model(rel)._meta

    try:
        return opts.get_field(parts[-1])
    except FieldDoesNotExist:
        return None


def resolve_field(model_field, lookup_expr):
    """
    Resolves a ``lookup_expr`` into its final output field, given
    the initial ``model_field``. The lookup expression should only contain
    transforms and lookups, not intermediary model field parts.

    Note:
    This method is based on django.db.models.sql.query.Query.build_lookup

    For more info on the lookup API:
    https://docs.djangoproject.com/en/1.9/ref/models/lookups/

    """
    query = model_field.model._default_manager.all().query
    lhs = Expression(model_field)
    lookups = lookup_expr.split(LOOKUP_SEP)

    assert len(lookups) > 0

    try:
        while lookups:
            name = lookups[0]
            # If there is just one part left, try first get_lookup() so
            # that if the lhs supports both transform and lookup for the
            # name, then lookup will be picked.
            if len(lookups) == 1:
                final_lookup = lhs.get_lookup(name)
                if not final_lookup:
                    # We didn't find a lookup. We are going to interpret
                    # the name as transform, and do an Exact lookup against
                    # it.
                    lhs = query.try_transform(lhs, name, lookups)
                    final_lookup = lhs.get_lookup('exact')
                return lhs.output_field, final_lookup.lookup_name
            lhs = query.try_transform(lhs, name, lookups)
            lookups = lookups[1:]
    except FieldError as e:
        six.raise_from(FieldLookupError(model_field, lookup_expr), e)


def handle_timezone(value):
    if settings.USE_TZ and timezone.is_naive(value):
        return timezone.make_aware(value, timezone.get_default_timezone())
    elif not settings.USE_TZ and timezone.is_aware(value):
        return timezone.make_naive(value, timezone.UTC())
    return value
