from decimal import Decimal, InvalidOperation

from django import forms
from django.contrib import admin, messages
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
from django.contrib.admin.models import LogEntry, CHANGE
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from django.shortcuts import render
from django.utils import timezone
from django.utils.html import format_html

from . import services
from .models import Profile, Agent, Transaction, WalletBinding, WithdrawalRequest, MovieRating, TicketBooking, PremiumTicket, GoldenEggTicket, SpecialTicket, DepositAddress, DepositRequest, SetApprovalRequest, FeaturedVideo


@admin.action(description='Approve selected withdrawals (mark as proven/paid)')
def approve_withdrawals(modeladmin, request, queryset):
    now = timezone.now()
    count = 0
    for wr in queryset.exclude(status=WithdrawalRequest.APPROVED):
        wr.status = WithdrawalRequest.APPROVED
        wr.processed_at = now
        wr.save()

        # Create a negative transaction to deduct from user balance.
        Transaction.objects.create(
            user=wr.user,
            kind=Transaction.WITHDRAWAL,
            amount=-wr.amount,
            created_at=now
        )
        count += 1
    modeladmin.message_user(request, f'{count} withdrawal(s) approved and deducted from balance.')


@admin.action(description='Reject selected withdrawals')
def reject_withdrawals(modeladmin, request, queryset):
    updated = queryset.exclude(status=WithdrawalRequest.REJECTED).update(
        status=WithdrawalRequest.REJECTED, processed_at=timezone.now())
    modeladmin.message_user(request, f'{updated} withdrawal(s) rejected.')


@admin.register(WithdrawalRequest)
class WithdrawalRequestAdmin(admin.ModelAdmin):
    list_display = ('user', 'amount', 'method', 'status', 'address', 'created_at', 'processed_at')
    list_filter = ('status', 'method', 'created_at')
    search_fields = ('user__username', 'address')
    date_hierarchy = 'created_at'
    readonly_fields = ('user', 'method', 'address', 'amount', 'service_charge', 'created_at')
    actions = [approve_withdrawals, reject_withdrawals]


@admin.action(description='Approve & credit selected deposits (verified)')
def approve_deposits(modeladmin, request, queryset):
    now = timezone.now()
    count = 0
    for dr in queryset.exclude(status=DepositRequest.APPROVED):
        dr.status = DepositRequest.APPROVED
        dr.processed_at = now
        dr.save(update_fields=['status', 'processed_at'])
        # Credit the verified amount to the user's balance.
        Transaction.objects.create(
            user=dr.user,
            kind=Transaction.DEPOSIT,
            amount=dr.amount,
            created_at=now,
        )
        count += 1
    modeladmin.message_user(request, f'{count} deposit(s) approved and credited.')


@admin.action(description='Reject selected deposits')
def reject_deposits(modeladmin, request, queryset):
    updated = queryset.exclude(status=DepositRequest.REJECTED).update(
        status=DepositRequest.REJECTED, processed_at=timezone.now())
    modeladmin.message_user(request, f'{updated} deposit(s) rejected.')


@admin.register(DepositRequest)
class DepositRequestAdmin(admin.ModelAdmin):
    list_display = ('user', 'uid', 'amount', 'method', 'status', 'proof_link', 'created_at', 'processed_at')
    list_filter = ('status', 'method', 'created_at')
    search_fields = ('user__username', 'user__profile__uid')
    date_hierarchy = 'created_at'
    readonly_fields = ('user', 'method', 'amount', 'proof', 'proof_preview', 'created_at')
    actions = [approve_deposits, reject_deposits]

    @admin.display(description='UID')
    def uid(self, obj):
        return obj.uid

    @admin.display(description='Proof')
    def proof_link(self, obj):
        if obj.proof:
            return format_html('<a href="{}" target="_blank">View proof</a>', obj.proof.url)
        return '—'

    @admin.display(description='Proof preview')
    def proof_preview(self, obj):
        if obj.proof:
            return format_html('<img src="{}" style="max-width:320px; max-height:320px;" />', obj.proof.url)
        return '—'


@admin.register(DepositAddress)
class DepositAddressAdmin(admin.ModelAdmin):
    """Admin-managed (rotatable) receiving address + QR per deposit method."""
    list_display = ('method', 'label', 'address', 'is_active', 'updated_at')
    list_editable = ('address', 'is_active')
    search_fields = ('method', 'label', 'address')


@admin.register(WalletBinding)
class WalletBindingAdmin(admin.ModelAdmin):
    list_display = ('user', 'network', 'address', 'account_name', 'updated_at')
    list_filter = ('network',)
    search_fields = ('user__username', 'address', 'account_name', 'account_number', 'revtag')
    readonly_fields = ('created_at', 'updated_at')


@admin.register(Transaction)
class TransactionAdmin(admin.ModelAdmin):
    list_display = ('user', 'kind', 'amount', 'created_at')
    list_filter = ('kind', 'created_at')
    search_fields = ('user__username',)
    date_hierarchy = 'created_at'


@admin.register(MovieRating)
class MovieRatingAdmin(admin.ModelAdmin):
    list_display = ('user', 'movie_title', 'stars', 'created_at')
    list_filter = ('stars', 'created_at')
    search_fields = ('user__username', 'movie_title')


@admin.register(TicketBooking)
class TicketBookingAdmin(admin.ModelAdmin):
    list_display = ('user', 'movie_title', 'amount', 'commission', 'status', 'is_premium', 'created_at')
    list_filter = ('status', 'is_premium', 'created_at')
    search_fields = ('user__username', 'movie_title')


class _SpecialTicketAdmin(admin.ModelAdmin):
    """Base admin for an admin-set special ticket (premium / golden egg /
    special): pick a user and an amount. While pending it is the only ticket the
    user can book next (cannot be skipped); booking it pays the bonus commission.
    Set the user's balance negative (Users → Adjust balance) so they must deposit
    to afford it. Subclasses pin ``kind`` so each kind gets its own section."""
    kind = None  # set by subclasses

    list_display = ('user', 'user_balance_display', 'amount', 'commission', 'set_number', 'ticket_number', 'is_used', 'created_at', 'used_at')
    list_filter = ('is_used', 'created_at')
    search_fields = ('user__username',)
    readonly_fields = ('user_balance_display', 'is_used', 'used_at', 'created_at')
    # ``kind`` is pinned per subclass, so hide it from the form.
    exclude = ('kind',)

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        return qs.filter(kind=self.kind) if self.kind else qs

    def get_form(self, request, obj=None, **kwargs):
        form = super().get_form(request, obj, **kwargs)
        if 'set_number' in form.base_fields:
            form.base_fields['set_number'].help_text = (
                'Which set (1, 2, 3 …) this ticket applies to. Leave blank to use '
                "the user's current set.")
        if 'ticket_number' in form.base_fields:
            form.base_fields['ticket_number'].help_text = (
                'Which ticket within that set (1–32) it appears on. Leave blank to '
                'apply on the next ticket the user reserves.')
        return form

    @admin.display(description='Current balance')
    def user_balance_display(self, obj):
        if obj.user_id is None:
            return '—'
        return services.format_eur(services.user_balance(obj.user))

    def save_model(self, request, obj, form, change):
        # Stamp this section's kind and default the target set to the user's
        # current set if left blank.
        if self.kind:
            obj.kind = self.kind
        if obj.set_number is None:
            obj.set_number = services.current_set_number(obj.user)
        super().save_model(request, obj, form, change)


@admin.register(PremiumTicket)
class PremiumTicketAdmin(_SpecialTicketAdmin):
    kind = PremiumTicket.PREMIUM


@admin.register(GoldenEggTicket)
class GoldenEggTicketAdmin(_SpecialTicketAdmin):
    kind = PremiumTicket.GOLDEN_EGG


@admin.register(SpecialTicket)
class SpecialTicketAdmin(_SpecialTicketAdmin):
    kind = PremiumTicket.SPECIAL


class AgentAdminForm(forms.ModelForm):
    """Agent form with a write-only password field so the hash is never shown."""
    new_password = forms.CharField(
        label='Set portal password', required=False,
        widget=forms.PasswordInput(render_value=False),
        help_text='Set or change the agent portal password. Leave blank to keep the current one.',
    )

    class Meta:
        model = Agent
        fields = ('name', 'role', 'email', 'username', 'uid', 'referral_code', 'is_active')

    def save(self, commit=True):
        agent = super().save(commit=False)
        new_password = self.cleaned_data.get('new_password')
        if new_password:
            agent.set_password(new_password)
        if commit:
            agent.save()
        return agent


@admin.register(Agent)
class AgentAdmin(admin.ModelAdmin):
    """Register and manage agents. Each agent gets a unique 6-digit UID and a
    6-char referral code (auto-generated if left blank). Set a username +
    password to give the agent access to the agent portal (/agent/login/).
    Share the registration link so users who sign up through it are grouped
    under this agent."""
    form = AgentAdminForm
    list_display = ('name', 'role', 'username', 'portal_login', 'email', 'uid', 'referral_code', 'user_count', 'registration_link', 'is_active', 'created_at')
    list_editable = ('role',)
    list_filter = ('role', 'is_active', 'created_at')
    search_fields = ('name', 'email', 'uid', 'referral_code', 'username')
    readonly_fields = ('login_status', 'user_count', 'registration_link', 'created_at')
    fields = ('name', 'role', 'email', 'username', 'new_password', 'login_status', 'uid', 'referral_code', 'is_active', 'user_count', 'registration_link', 'created_at')

    @admin.display(boolean=True, description='Portal login')
    def portal_login(self, obj):
        return obj.has_login

    @admin.display(description='Portal login set')
    def login_status(self, obj):
        return 'Yes' if obj.has_login else 'No — set a username and password to enable'

    @admin.display(description='Users')
    def user_count(self, obj):
        return obj.user_count

    @admin.display(description='Registration link')
    def registration_link(self, obj):
        if not obj.pk or not obj.referral_code:
            return '— (save to generate)'
        url = self.request.build_absolute_uri(obj.registration_path) if getattr(self, 'request', None) else obj.registration_path
        return format_html('<a href="{}" target="_blank">{}</a>', url, url)

    def get_form(self, request, obj=None, **kwargs):
        # Stash the request so registration_link can build an absolute URL.
        self.request = request
        # UID / referral code auto-generate on save; tell the admin they're optional.
        form = super().get_form(request, obj, **kwargs)
        for name in ('uid', 'referral_code'):
            if name in form.base_fields:
                form.base_fields[name].required = False
                form.base_fields[name].help_text = 'Leave blank to auto-generate.'
        if 'role' in form.base_fields and not request.user.is_superuser:
            form.base_fields['role'].choices = [
                choice for choice in Agent.ROLE_CHOICES
                if choice[0] != Agent.SUPER_ADMIN
            ]
            form.base_fields['role'].help_text = (
                'Admins can promote agents to Admin. Only a superuser can assign Super admin.')
        return form

    def save_model(self, request, obj, form, change):
        if not request.user.is_superuser and obj.role == Agent.SUPER_ADMIN:
            obj.role = Agent.ADMIN
            self.message_user(
                request,
                'Only a superuser can assign the Super admin role. Saved as Admin instead.',
                level=messages.WARNING,
            )
        super().save_model(request, obj, form, change)

    def changelist_view(self, request, extra_context=None):
        self.request = request
        return super().changelist_view(request, extra_context)


@admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
    list_display = ('user', 'uid', 'referral_code', 'agent', 'activated_by', 'online', 'phone', 'balance_override', 'tickets_override', 'bonus_override', 'processing_override', 'earnings_override', 'approved_set', 'withdraw_unlocked', 'withdraw_locked', 'last_seen', 'last_ip', 'created_at')
    # Sets (approved_set), the tickets-booked override, and the withdrawal unlock
    # are all editable inline so an admin can configure a user's progress directly.
    list_editable = ('balance_override', 'tickets_override', 'bonus_override', 'processing_override', 'earnings_override', 'approved_set', 'withdraw_unlocked', 'withdraw_locked')
    list_filter = ('agent', 'activated_by', 'status', 'withdraw_unlocked', 'withdraw_locked')
    search_fields = ('user__username', 'uid', 'phone', 'invitation_code', 'referral_code')
    # UID is editable; leave it blank to auto-generate a unique 6-digit value.
    # The withdrawal PIN is stored hashed, so it is shown only as a set/not-set
    # flag and excluded from the editable form.
    readonly_fields = ('referral_code', 'last_seen', 'last_ip', 'activated_by', 'activated_at', 'pin_set')
    exclude = ('withdrawal_pin',)

    def get_form(self, request, obj=None, **kwargs):
        form = super().get_form(request, obj, **kwargs)
        if 'uid' in form.base_fields:
            form.base_fields['uid'].required = False
            form.base_fields['uid'].help_text = 'Leave blank to auto-generate a unique 6-digit UID.'
        if 'tickets_override' in form.base_fields:
            form.base_fields['tickets_override'].help_text = (
                'Leave blank to use the live completed-ticket count. Enter a number '
                'to set how many tickets the user has booked (also sets their set).')
        for name, label in (
                ('balance_override', 'balance'),
                ('bonus_override', 'bonus'),
                ('processing_override', 'processing amount'),
                ('earnings_override', 'earnings')):
            if name in form.base_fields:
                form.base_fields[name].help_text = (
                    f'Leave blank to use the live {label}. Enter a value to display '
                    f'and use this {label} on the user side.')
        if 'withdraw_locked' in form.base_fields:
            form.base_fields['withdraw_locked'].help_text = (
                'Lock withdrawals while tax or government fees are being processed.')
        if 'withdraw_lock_reason' in form.base_fields:
            form.base_fields['withdraw_lock_reason'].help_text = (
                'Shown to the user when withdrawal is locked.')
        return form

    @admin.display(boolean=True, description='Online')
    def online(self, obj):
        return obj.is_online

    @admin.display(boolean=True, description='Withdrawal PIN set')
    def pin_set(self, obj):
        return obj.has_withdrawal_pin


@admin.action(description='Reset user — approve to proceed to the next set')
def approve_set_requests(modeladmin, request, queryset):
    now = timezone.now()
    count = 0
    for sar in queryset.filter(status=SetApprovalRequest.PENDING):
        # Approve the user up to the set they have actually reached. Using the
        # live completed count (current_set_number) as well as set_completed + 1
        # means a stale request can never leave them stuck a set behind.
        profile, _ = Profile.objects.get_or_create(user=sar.user)
        target = max(sar.set_completed + 1, services.current_set_number(sar.user))
        profile.approved_set = max(profile.approved_set, target)
        profile.save(update_fields=['approved_set'])
        sar.status = SetApprovalRequest.APPROVED
        sar.processed_at = now
        sar.save(update_fields=['status', 'processed_at'])
        count += 1
    modeladmin.message_user(request, f'{count} user(s) reset and approved for the next set.')


@admin.register(SetApprovalRequest)
class SetApprovalRequestAdmin(admin.ModelAdmin):
    """Notification list of users who finished a set and need approval to go on.
    Use the 'Reset user' action to approve them for the next set."""
    list_display = ('user', 'uid', 'set_completed', 'status', 'created_at', 'processed_at')
    list_filter = ('status', 'created_at')
    search_fields = ('user__username', 'user__profile__uid')
    readonly_fields = ('user', 'set_completed', 'created_at')
    actions = [approve_set_requests]

    @admin.display(description='UID')
    def uid(self, obj):
        return obj.uid


class ProfileInline(admin.StackedInline):
    model = Profile
    can_delete = False
    extra = 0
    # UID is editable here so admins can change a user's UID from the user page;
    # leave it blank to keep/auto-generate. approved_set (the set the user is on),
    # tickets_override (tickets booked), and withdraw_unlocked are editable here too.
    # The withdrawal PIN is hashed, so it is shown only as a set/not-set flag.
    readonly_fields = ('referral_code', 'last_seen', 'last_ip', 'activated_by', 'activated_at', 'pin_set')
    exclude = ('withdrawal_pin',)

    def get_formset(self, request, obj=None, **kwargs):
        formset = super().get_formset(request, obj, **kwargs)
        for name, label in (
                ('balance_override', 'balance'),
                ('tickets_override', 'completed ticket count'),
                ('bonus_override', 'bonus'),
                ('processing_override', 'processing amount'),
                ('earnings_override', 'earnings')):
            if name in formset.form.base_fields:
                formset.form.base_fields[name].help_text = (
                    f'Leave blank to use the live {label}. Enter a value to override it.')
        return formset

    @admin.display(boolean=True, description='Withdrawal PIN set')
    def pin_set(self, obj):
        return obj.has_withdrawal_pin


class WalletBindingInline(admin.StackedInline):
    model = WalletBinding
    can_delete = True
    extra = 0
    readonly_fields = ('created_at', 'updated_at')


def _set_status(queryset, status, active, protect_superusers=True):
    """Apply an account status to users, syncing is_active, and keeping each
    user's Profile.status in step. Superusers are skipped for blocking states."""
    target = queryset.exclude(is_superuser=True) if protect_superusers else queryset
    count = 0
    for user in target:
        user.is_active = active
        user.save(update_fields=['is_active'])
        defaults = {'status': status}
        if not active:
            # A blocked (frozen/banned) account has no current approver.
            defaults['activated_by'] = None
            defaults['activated_at'] = None
        Profile.objects.update_or_create(user=user, defaults=defaults)
        count += 1
    skipped = queryset.count() - count
    return count, skipped


@admin.action(description='Activate selected users (allow login)')
def activate_users(modeladmin, request, queryset):
    count, _ = _set_status(queryset, Profile.ACTIVE, True, protect_superusers=False)
    modeladmin.message_user(request, f'{count} user(s) activated and allowed to log in.')


@admin.action(description='Freeze selected users (temporary login block)')
def freeze_users(modeladmin, request, queryset):
    count, skipped = _set_status(queryset, Profile.FROZEN, False)
    msg = f'{count} user(s) frozen and blocked from logging in.'
    if skipped:
        msg += f' Skipped {skipped} superuser(s).'
    modeladmin.message_user(request, msg)


@admin.action(description='Ban selected users (permanent login block)')
def ban_users(modeladmin, request, queryset):
    count, skipped = _set_status(queryset, Profile.BANNED, False)
    msg = f'{count} user(s) banned and blocked from logging in.'
    if skipped:
        msg += f' Skipped {skipped} superuser(s).'
    modeladmin.message_user(request, msg)


@admin.action(description='Adjust balance (add positive / set negative)')
def adjust_balance(modeladmin, request, queryset):
    """Credit or debit accounts by a signed amount.

    A positive amount adds to the balance; a negative amount deducts and can
    push the balance negative. Shows an intermediate form to capture the
    amount, then records an Admin Adjustment transaction per selected user.
    """
    if request.POST.get('apply'):
        raw = (request.POST.get('amount') or '').strip()
        try:
            amount = Decimal(raw)
        except (InvalidOperation, ValueError):
            modeladmin.message_user(request, 'Enter a valid amount, e.g. 100 or -50.', level=messages.ERROR)
            return
        if amount == 0:
            modeladmin.message_user(request, 'Amount must not be zero.', level=messages.ERROR)
            return

        now = timezone.now()
        count = 0
        for user in queryset:
            Transaction.objects.create(
                user=user,
                kind=Transaction.ADJUSTMENT,
                amount=amount,
                created_at=now,
            )
            count += 1
        verb = 'added to' if amount > 0 else 'deducted from'
        modeladmin.message_user(
            request, f'{services.format_eur(abs(amount))} {verb} {count} account(s).')
        return

    return render(request, 'admin/adjust_balance.html', {
        'users': queryset,
        'selected': list(queryset.values_list('pk', flat=True)),
        'action_checkbox_name': ACTION_CHECKBOX_NAME,
        'title': 'Adjust account balance',
        'opts': modeladmin.model._meta,
    })


@admin.action(description='Reset to normal game (skip trial 32-ticket set)')
def reset_to_normal_game(modeladmin, request, queryset):
    """Bypass the free 32-ticket trial round for the selected users.

    For each user this sets ``Profile.skip_trial`` (so completions count toward
    the 3-set game from the first ticket), reverses the welcome bonus where it is
    still active (so they run the game on their real deposit only), and optionally
    credits a deposit entered on the intermediate form.
    """
    if request.POST.get('apply'):
        raw = (request.POST.get('deposit') or '').strip()
        deposit = None
        if raw:
            try:
                deposit = Decimal(raw)
            except (InvalidOperation, ValueError):
                modeladmin.message_user(
                    request, 'Enter a valid deposit amount, e.g. 1000, or leave it blank.',
                    level=messages.ERROR)
                return
            if deposit <= 0:
                modeladmin.message_user(
                    request, 'Deposit must be a positive amount (or blank).',
                    level=messages.ERROR)
                return

        now = timezone.now()
        count = 0
        for user in queryset:
            profile, _ = Profile.objects.get_or_create(user=user)
            profile.skip_trial = True
            profile.approved_set = max(profile.approved_set, 1)
            profile.save(update_fields=['skip_trial', 'approved_set'])

            # Reverse the welcome bonus if it is still live, so the trial €300 is
            # removed and the user runs the normal game on their real deposit.
            if (services.welcome_bonus_granted(user)
                    and not services.welcome_bonus_reversed(user)):
                Transaction.objects.create(
                    user=user,
                    kind=Transaction.BONUS_REVERSAL,
                    amount=-services.WELCOME_BONUS,
                    created_at=now,
                )

            if deposit is not None:
                Transaction.objects.create(
                    user=user,
                    kind=Transaction.DEPOSIT,
                    amount=deposit,
                    created_at=now,
                )

            LogEntry.objects.log_actions(
                user_id=request.user.pk,
                queryset=User.objects.filter(pk=user.pk),
                action_flag=CHANGE,
                change_message='Reset to normal game (trial skipped, bonus reversed).',
                single_object=True,
            )
            count += 1

        msg = f'{count} account(s) reset to the normal game (trial skipped).'
        if deposit is not None:
            msg += f' Credited {services.format_eur(deposit)} deposit each.'
        modeladmin.message_user(request, msg)
        return

    return render(request, 'admin/reset_to_normal_game.html', {
        'users': queryset,
        'selected': list(queryset.values_list('pk', flat=True)),
        'action_checkbox_name': ACTION_CHECKBOX_NAME,
        'welcome_bonus': '{:,.2f}'.format(services.WELCOME_BONUS),
        'title': 'Reset to normal game',
        'opts': modeladmin.model._meta,
    })


@admin.action(description='Super admin: make selected users admins')
def make_site_admins(modeladmin, request, queryset):
    if not request.user.is_superuser:
        modeladmin.message_user(
            request,
            'Only a superuser can change site admin roles.',
            level=messages.ERROR,
        )
        return
    updated = queryset.update(is_staff=True)
    modeladmin.message_user(request, f'{updated} user(s) promoted to site admin.')


@admin.action(description='Super admin: remove selected users from admins')
def remove_site_admins(modeladmin, request, queryset):
    if not request.user.is_superuser:
        modeladmin.message_user(
            request,
            'Only a superuser can change site admin roles.',
            level=messages.ERROR,
        )
        return
    queryset = queryset.exclude(is_superuser=True)
    updated = queryset.update(is_staff=False)
    modeladmin.message_user(request, f'{updated} user(s) removed from site admin role.')


class ApprovalUserAdmin(UserAdmin):
    """Default user admin plus account-control actions (activate / freeze /
    ban / balance adjustment) and balance + status visibility."""
    inlines = [ProfileInline, WalletBindingInline]
    actions = [
        activate_users,
        freeze_users,
        ban_users,
        adjust_balance,
        reset_to_normal_game,
        make_site_admins,
        remove_site_admins,
    ]
    list_display = ('username', 'uid', 'online', 'account_status', 'agent', 'approved_by', 'balance_display', 'tickets_booked', 'set_progress', 'last_seen', 'last_ip', 'is_active', 'is_staff', 'date_joined')
    list_filter = ('is_active', 'is_staff', 'is_superuser', 'profile__status', 'profile__agent', 'profile__activated_by')
    search_fields = ('username', 'profile__uid', 'profile__phone', 'profile__referral_code')

    def save_model(self, request, obj, form, change):
        if change and not request.user.is_superuser:
            original = User.objects.filter(pk=obj.pk).only('is_staff', 'is_superuser').first()
            if original is not None:
                obj.is_staff = original.is_staff
                obj.is_superuser = original.is_superuser
        super().save_model(request, obj, form, change)

    @admin.display(description='UID')
    def uid(self, obj):
        profile = getattr(obj, 'profile', None)
        return profile.uid if profile else '—'

    @admin.display(description='Status')
    def account_status(self, obj):
        profile = getattr(obj, 'profile', None)
        return profile.get_status_display() if profile else '—'

    @admin.display(description='Approved by')
    def approved_by(self, obj):
        profile = getattr(obj, 'profile', None)
        return profile.activated_by if profile and profile.activated_by_id else '—'

    @admin.display(description='Balance')
    def balance_display(self, obj):
        return services.format_eur(services.user_balance(obj))

    @admin.display(description='Tickets booked')
    def tickets_booked(self, obj):
        """Live count of completed tickets (refreshes on every changelist load)."""
        return services.completed_tickets(obj)

    @admin.display(description='Set progress')
    def set_progress(self, obj):
        """Live trial/set position, e.g. 'Set 2/3 · 18/32' or 'Awaiting reset'."""
        return services.set_progress_label(obj)

    @admin.display(boolean=True, description='Online')
    def online(self, obj):
        profile = getattr(obj, 'profile', None)
        return bool(profile and profile.is_online)

    @admin.display(description='Agent')
    def agent(self, obj):
        profile = getattr(obj, 'profile', None)
        return profile.agent if profile and profile.agent_id else '—'

    @admin.display(description='Last seen')
    def last_seen(self, obj):
        profile = getattr(obj, 'profile', None)
        return profile.last_seen if profile else None

    @admin.display(description='IP address')
    def last_ip(self, obj):
        profile = getattr(obj, 'profile', None)
        return (profile.last_ip if profile else None) or '—'


@admin.register(FeaturedVideo)
class FeaturedVideoAdmin(admin.ModelAdmin):
    """Set the 'now playing' movie on the dashboard hero. Upload a video file or
    paste a YouTube link; the most recently updated active row is used."""
    list_display = ('title', 'source', 'is_active', 'updated_at')
    list_editable = ('is_active',)
    fields = ('title', 'youtube_url', 'video_file', 'poster', 'is_active')

    @admin.display(description='Source')
    def source(self, obj):
        if obj.video_file:
            return 'Uploaded file'
        if obj.youtube_url:
            return 'YouTube'
        return '—'


# Replace the default User admin with the approval-aware one.
admin.site.unregister(User)
admin.site.register(User, ApprovalUserAdmin)

# Custom Admin Site Headers
admin.site.site_header = 'ODEON CORE CONTROL'
admin.site.site_title = 'Admin Portal'
admin.site.index_title = 'System Infrastructure'

# Use a custom home page that renders the statistics dashboard above the app
# list. The template pulls its figures from the ``admin_dashboard`` template tag.
admin.site.index_template = 'admin/dashboard_index.html'
