from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from nepali.datetime import nepalidate, nepalidatetime

from django_nepkit.models import NepaliDateField
from django_nepkit.utils import (
    try_parse_nepali_date,
    try_parse_nepali_datetime,
)


def format_nepali_date(date_value, format_string="%B %d, %Y"):
    """
    Format a nepalidate object with Nepali month names.

    Args:
        date_value: A nepalidate object or string in YYYY-MM-DD format
        format_string: strftime format string (default: '%B %d, %Y')
                      %B = Full month name (Baishak, Jestha, etc.)
                      %b = Short month name
                      %d = Day of month
                      %Y = Year

    Returns:
        Formatted date string with Nepali month names, or empty string if invalid
    """
    if date_value is None:
        return ""

    try:
        parsed = try_parse_nepali_date(date_value)
        if parsed is not None:
            return parsed.strftime(format_string)
        if isinstance(date_value, nepalidate):
            return date_value.strftime(
                format_string
            )  # defensive; should be covered above
    except (ValueError, TypeError, AttributeError):
        pass

    return str(date_value) if date_value else ""


def format_nepali_datetime(datetime_value, format_string="%B %d, %Y %I:%M %p"):
    """
    Format a nepalidatetime object with Nepali month names.

    Default output uses **12-hour time with AM/PM**.
    """
    if datetime_value is None:
        return ""

    try:
        parsed = try_parse_nepali_datetime(datetime_value)
        if parsed is not None:
            return parsed.strftime(format_string)
        if isinstance(datetime_value, nepalidatetime):
            return datetime_value.strftime(
                format_string
            )  # defensive; should be covered above
    except (ValueError, TypeError, AttributeError):
        pass

    return str(datetime_value) if datetime_value else ""


class NepaliDateFilter(admin.FieldListFilter):
    """
    A list filter for NepaliDateField to filter by BS Year.
    """

    def __init__(self, field, request, params, model, model_admin, field_path):
        self.parameter_name = f"{field_path}_bs_year"
        super().__init__(field, request, params, model, model_admin, field_path)
        self.title = _("Nepali Date (Year)")

    def expected_parameters(self):
        return [self.parameter_name]

    def choices(self, changelist):
        yield {
            "selected": self.used_parameters.get(self.parameter_name) is None,
            "query_string": changelist.get_query_string(remove=[self.parameter_name]),
            "display": _("All"),
        }
        current_year = nepalidate.today().year
        for year in range(current_year - 10, current_year + 2):
            yield {
                "selected": self.used_parameters.get(self.parameter_name) == str(year),
                "query_string": changelist.get_query_string(
                    {self.parameter_name: str(year)}
                ),
                "display": str(year),
            }

    def queryset(self, request, queryset):
        value = self.used_parameters.get(self.parameter_name)
        if value:
            year = int(value)
            # Convert BS year range to AD date range
            start_date_bs = nepalidate(year, 1, 1)
            # Find last day of the year. Some BS years end on the 30th,
            # so we try 31 first and then fall back to 30 if that date
            # is not valid.
            try:
                end_date_bs = nepalidate(year, 12, 31)
            except ValueError:
                end_date_bs = nepalidate(year, 12, 30)

            start_date_ad = start_date_bs.to_date()
            end_date_ad = end_date_bs.to_date()

            return queryset.filter(
                **{f"{self.field_path}__range": (start_date_ad, end_date_ad)}
            )
        return queryset


# Register NepaliDateFilter as the default list filter for NepaliDateField
admin.FieldListFilter.register(
    lambda f: isinstance(f, NepaliDateField),
    NepaliDateFilter,
    take_priority=True,
)


class NepaliAdminMixin:
    """
    Mixin for Django admin classes that provides Nepali date utilities.
    Makes format_nepali_date and NepaliDateFilter available without explicit imports.
    """

    def format_nepali_date(self, date_value, format_string="%B %d, %Y"):
        """
        Format a nepalidate object with Nepali month names.
        Available as a method on admin classes using this mixin.
        """
        return format_nepali_date(date_value, format_string)

    def format_nepali_datetime(
        self, datetime_value, format_string="%B %d, %Y %I:%M %p"
    ):
        return format_nepali_datetime(datetime_value, format_string)


class NepaliModelAdmin(NepaliAdminMixin, admin.ModelAdmin):
    """
    Base ModelAdmin class with Nepali date utilities built-in.
    Use this instead of admin.ModelAdmin to have format_nepali_date available.

    Example:
        from django_nepkit import NepaliModelAdmin, NepaliDateFilter

        @admin.register(MyModel)
        class MyModelAdmin(NepaliModelAdmin):
            list_filter = (('date_field', NepaliDateFilter),)

            def display_date(self, obj):
                return self.format_nepali_date(obj.date_field)
    """

    # Make NepaliDateFilter available as a class attribute
    NepaliDateFilter = NepaliDateFilter

    # Ensure admin forms render Nepali fields with the proper widget,
    # even if a project doesn't provide custom ModelForms.
    def formfield_for_dbfield(self, db_field, request, **kwargs):
        """
        Force Nepali widgets in Django admin without requiring user forms.
        """
        try:
            from django_nepkit.models import NepaliDateField, NepaliDateTimeField
            from django_nepkit.widgets import NepaliDatePickerWidget
        except Exception:
            return super().formfield_for_dbfield(db_field, request, **kwargs)

        if isinstance(db_field, (NepaliDateField, NepaliDateTimeField)):
            kwargs.setdefault("widget", NepaliDatePickerWidget)

        return super().formfield_for_dbfield(db_field, request, **kwargs)

    class Media:
        """
        Django admin ships jQuery as `django.jQuery` (not `window.jQuery`).
        The Nepali date picker library expects a global `jQuery`, so we bridge it.
        """

        css = {
            "all": (
                "https://nepalidatepicker.sajanmaharjan.com.np/v5/nepali.datepicker/css/nepali.datepicker.v5.0.6.min.css",
                "django_nepkit/css/admin-nepali-datepicker.css",
            )
        }
        js = (
            # Bridge admin's `django.jQuery` -> `window.jQuery`
            "django_nepkit/js/admin-jquery-bridge.js",
            # Date picker lib
            "https://nepalidatepicker.sajanmaharjan.com.np/v5/nepali.datepicker/js/nepali.datepicker.v5.0.6.min.js",
            # Init
            "django_nepkit/js/nepali-datepicker-init.js",
        )


# Exporting for easy usage
__all__ = [
    "NepaliDateFilter",
    "format_nepali_date",
    "format_nepali_datetime",
    "NepaliAdminMixin",
    "NepaliModelAdmin",
]
