"""
Admin mixins providing reusable functionality for Django admin classes.

These mixins can be combined with BaseModelAdmin to add specific functionality
to admin classes without code duplication.
"""

import csv
import json
from datetime import datetime
from io import StringIO

from django.contrib import admin, messages
from django.core.serializers.json import DjangoJSONEncoder
from django.http import HttpResponse


class QueryOptimizationMixin:
    """
    Mixin that provides automatic query optimization based on list_display.

    This mixin analyzes the list_display fields and automatically applies
    select_related for ForeignKey fields to prevent N+1 queries.

    Attributes:
        list_select_related: Explicit list of related fields to select
        list_prefetch_related: Explicit list of related fields to prefetch

    Usage:
        class MyModelAdmin(QueryOptimizationMixin, BaseModelAdmin):
            list_display = ['name', 'park', 'manufacturer']
            list_select_related = ['park', 'manufacturer']
            list_prefetch_related = ['reviews', 'photos']
    """

    list_select_related = []
    list_prefetch_related = []

    def get_queryset(self, request):
        """
        Optimize queryset with select_related and prefetch_related.

        Args:
            request: The HTTP request object

        Returns:
            QuerySet: The optimized queryset
        """
        qs = super().get_queryset(request)

        if self.list_select_related:
            qs = qs.select_related(*self.list_select_related)

        if self.list_prefetch_related:
            qs = qs.prefetch_related(*self.list_prefetch_related)

        return qs


class ReadOnlyAdminMixin:
    """
    Mixin that disables add, change, and delete permissions.

    Use this mixin for models that contain auto-generated data that should
    not be modified through the admin interface (e.g., rankings, logs, history).

    The mixin allows viewing but not modifying records. Superusers can still
    delete records if needed for maintenance.

    Usage:
        class RankingAdmin(ReadOnlyAdminMixin, BaseModelAdmin):
            list_display = ['ride', 'rank', 'calculated_at']
    """

    def has_add_permission(self, request):
        """Disable adding new records."""
        return False

    def has_change_permission(self, request, obj=None):
        """Disable changing existing records."""
        return False

    def has_delete_permission(self, request, obj=None):
        """Allow only superusers to delete records."""
        return request.user.is_superuser


class TimestampFieldsMixin:
    """
    Mixin that provides standard handling for timestamp fields.

    Automatically adds created_at and updated_at to readonly_fields and
    provides a standard fieldset for metadata display.

    Attributes:
        timestamp_fields: Tuple of timestamp field names (default: created_at, updated_at)

    Usage:
        class MyModelAdmin(TimestampFieldsMixin, BaseModelAdmin):
            fieldsets = [
                ('Basic Info', {'fields': ['name', 'description']}),
            ] + TimestampFieldsMixin.get_timestamp_fieldset()
    """

    timestamp_fields = ("created_at", "updated_at")

    def get_readonly_fields(self, request, obj=None):
        """Add timestamp fields to readonly_fields."""
        readonly = list(super().get_readonly_fields(request, obj))
        for field in self.timestamp_fields:
            if hasattr(self.model, field) and field not in readonly:
                readonly.append(field)
        return readonly

    @classmethod
    def get_timestamp_fieldset(cls):
        """
        Get a standard fieldset for timestamp fields.

        Returns:
            list: A fieldset tuple for use in admin fieldsets configuration
        """
        return [
            (
                "Metadata",
                {
                    "fields": cls.timestamp_fields,
                    "classes": ("collapse",),
                    "description": "Record creation and modification timestamps.",
                },
            )
        ]


class SlugFieldMixin:
    """
    Mixin that provides standard prepopulated_fields configuration for slug.

    Automatically configures the slug field to be populated from the name field.

    Attributes:
        slug_source_field: The field to populate slug from (default: 'name')

    Usage:
        class MyModelAdmin(SlugFieldMixin, BaseModelAdmin):
            # slug will be auto-populated from name
            pass

        class OtherModelAdmin(SlugFieldMixin, BaseModelAdmin):
            slug_source_field = 'title'  # Use title instead
    """

    slug_source_field = "name"
    prepopulated_fields = {}

    def get_prepopulated_fields(self, request, obj=None):
        """Get prepopulated fields including slug configuration."""
        prepopulated = dict(super().get_prepopulated_fields(request, obj))
        if hasattr(self.model, "slug") and hasattr(self.model, self.slug_source_field):
            prepopulated["slug"] = (self.slug_source_field,)
        return prepopulated


class ExportActionMixin:
    """
    Mixin that provides CSV and JSON export functionality.

    Adds admin actions to export selected records in CSV or JSON format.
    The export includes all fields specified in export_fields or list_display.

    Attributes:
        export_fields: List of field names to export (defaults to list_display)
        export_filename_prefix: Prefix for exported filenames

    Usage:
        class MyModelAdmin(ExportActionMixin, BaseModelAdmin):
            list_display = ['name', 'status', 'created_at']
            export_fields = ['id', 'name', 'status', 'created_at', 'updated_at']
            export_filename_prefix = 'my_model'
    """

    export_fields = None
    export_filename_prefix = "export"

    def get_export_fields(self):
        """Get the list of fields to export."""
        if self.export_fields:
            return self.export_fields
        return [f for f in self.list_display if not callable(getattr(self, f, None))]

    def get_export_filename(self, format_type):
        """Generate export filename with timestamp."""
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        return f"{self.export_filename_prefix}_{timestamp}.{format_type}"

    def get_export_value(self, obj, field_name):
        """Get the value of a field for export, handling related objects."""
        try:
            value = getattr(obj, field_name, None)
            if callable(value):
                value = value()
            if hasattr(value, "pk"):
                return str(value)
            return value
        except Exception:
            return ""

    @admin.action(description="Export selected to CSV")
    def export_to_csv(self, request, queryset):
        """Export selected records to CSV format."""
        fields = self.get_export_fields()
        output = StringIO()
        writer = csv.writer(output)

        # Write header
        writer.writerow(fields)

        # Write data rows
        for obj in queryset:
            row = [self.get_export_value(obj, f) for f in fields]
            writer.writerow(row)

        response = HttpResponse(output.getvalue(), content_type="text/csv")
        response["Content-Disposition"] = f'attachment; filename="{self.get_export_filename("csv")}"'

        self.message_user(request, f"Successfully exported {queryset.count()} records to CSV.")
        return response

    @admin.action(description="Export selected to JSON")
    def export_to_json(self, request, queryset):
        """Export selected records to JSON format."""
        fields = self.get_export_fields()
        data = []

        for obj in queryset:
            record = {}
            for field in fields:
                value = self.get_export_value(obj, field)
                # Handle datetime objects
                if isinstance(value, datetime):
                    value = value.isoformat()
                record[field] = value
            data.append(record)

        response = HttpResponse(
            json.dumps(data, indent=2, cls=DjangoJSONEncoder),
            content_type="application/json",
        )
        response["Content-Disposition"] = f'attachment; filename="{self.get_export_filename("json")}"'

        self.message_user(request, f"Successfully exported {queryset.count()} records to JSON.")
        return response

    def get_actions(self, request):
        """Add export actions to the admin."""
        actions = super().get_actions(request)
        actions["export_to_csv"] = (
            self.export_to_csv,
            "export_to_csv",
            "Export selected to CSV",
        )
        actions["export_to_json"] = (
            self.export_to_json,
            "export_to_json",
            "Export selected to JSON",
        )
        return actions


class BulkStatusChangeMixin:
    """
    Mixin that provides bulk status change actions.

    Adds admin actions to change status of multiple records at once.
    Supports FSM-managed status fields with proper transition validation.

    Attributes:
        status_field: Name of the status field (default: 'status')
        status_choices: List of (value, label) tuples for available statuses

    Usage:
        class MyModelAdmin(BulkStatusChangeMixin, BaseModelAdmin):
            status_field = 'status'
            status_choices = [
                ('active', 'Activate'),
                ('inactive', 'Deactivate'),
            ]
    """

    status_field = "status"
    status_choices = []

    def get_bulk_status_actions(self):
        """Generate bulk status change actions based on status_choices."""
        actions = {}

        for status_value, label in self.status_choices:

            def make_action(value, action_label):
                @admin.action(description=f"Set status to: {action_label}")
                def action_func(modeladmin, request, queryset):
                    return modeladmin._bulk_change_status(request, queryset, value)

                return action_func

            action_name = f"set_status_{status_value}"
            actions[action_name] = make_action(status_value, label)

        return actions

    def _bulk_change_status(self, request, queryset, new_status):
        """
        Change status for all selected records.

        Handles both regular status fields and FSM-managed fields.
        """
        updated = 0
        errors = 0

        for obj in queryset:
            try:
                setattr(obj, self.status_field, new_status)
                obj.save(update_fields=[self.status_field])
                updated += 1
            except Exception as e:
                errors += 1
                self.message_user(
                    request,
                    f"Error updating {obj}: {str(e)}",
                    level=messages.ERROR,
                )

        if updated:
            self.message_user(
                request,
                f"Successfully updated status for {updated} records.",
                level=messages.SUCCESS,
            )

        if errors:
            self.message_user(
                request,
                f"Failed to update {errors} records.",
                level=messages.WARNING,
            )

    def get_actions(self, request):
        """Add bulk status change actions to the admin."""
        actions = super().get_actions(request)
        for name, action in self.get_bulk_status_actions().items():
            actions[name] = (action, name, action.short_description)
        return actions


class AuditLogMixin:
    """
    Mixin that provides audit logging for admin actions.

    Logs all changes made through the admin interface including
    who made the change, when, and what was changed.

    Usage:
        class MyModelAdmin(AuditLogMixin, BaseModelAdmin):
            pass
    """

    def log_addition(self, request, obj, message):
        """Log addition of a new object."""
        super().log_addition(request, obj, message)

    def log_change(self, request, obj, message):
        """Log change to an existing object."""
        super().log_change(request, obj, message)

    def log_deletion(self, request, obj, object_repr):
        """Log deletion of an object."""
        super().log_deletion(request, obj, object_repr)


class ModerationMixin:
    """
    Mixin that provides standard moderation functionality.

    Adds moderation actions (approve, reject) and filters for
    user-generated content that requires moderation.

    Attributes:
        moderation_status_field: Name of the moderation status field
        moderated_by_field: Name of the field storing the moderator
        moderated_at_field: Name of the field storing moderation time

    Usage:
        class ReviewAdmin(ModerationMixin, BaseModelAdmin):
            moderation_status_field = 'moderation_status'
    """

    moderation_status_field = "moderation_status"
    moderated_by_field = "moderated_by"
    moderated_at_field = "moderated_at"

    @admin.action(description="Approve selected items")
    def bulk_approve(self, request, queryset):
        """Approve all selected items."""
        from django.utils import timezone

        updated = queryset.update(
            **{
                self.moderation_status_field: "approved",
                self.moderated_by_field: request.user,
                self.moderated_at_field: timezone.now(),
            }
        )
        self.message_user(request, f"Successfully approved {updated} items.")

    @admin.action(description="Reject selected items")
    def bulk_reject(self, request, queryset):
        """Reject all selected items."""
        from django.utils import timezone

        updated = queryset.update(
            **{
                self.moderation_status_field: "rejected",
                self.moderated_by_field: request.user,
                self.moderated_at_field: timezone.now(),
            }
        )
        self.message_user(request, f"Successfully rejected {updated} items.")

    def get_actions(self, request):
        """Add moderation actions to the admin."""
        actions = super().get_actions(request)
        actions["bulk_approve"] = (
            self.bulk_approve,
            "bulk_approve",
            "Approve selected items",
        )
        actions["bulk_reject"] = (
            self.bulk_reject,
            "bulk_reject",
            "Reject selected items",
        )
        return actions
