"""
Put the Super Administrator account on this install onto the right address.

Every tenant site carries one Super Administrator and it belongs to whoever
runs the fleet, not to the client. New installs get that address from the
install script. This command is for sites installed before that default
existed, which are sitting on whatever address was typed at the time.

The address itself is deliberately not written down here. This file ships to
every client server, and an operator's address is no more theirs to hold than
an operator's password would be.

It changes an address. It does not create an account out of nothing and it
does not set a password, because an account appearing on a live client site
should be a decision someone made rather than a side effect of a rename.

    manage.py set_super_admin --show
    manage.py set_super_admin --email you@example.com
    manage.py set_super_admin --email you@example.com --from old@somewhere

Add --settings=config.settings.production on a server.
"""
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.core.management.base import BaseCommand, CommandError
from django.core.validators import validate_email
from django.db import transaction


class Command(BaseCommand):
    help = 'Show or correct the Super Administrator address on this install.'

    def add_arguments(self, parser):
        parser.add_argument('--email', help='The address it should be.')
        parser.add_argument(
            '--from', dest='from_email',
            help='Only move this one account. Use when the install has more '
                 'than one top level account and you mean a particular one.',
        )
        parser.add_argument('--show', action='store_true',
                            help='List the top level accounts and change nothing.')
        parser.add_argument('--dry-run', action='store_true',
                            help='Say what would change and write nothing.')

    def handle(self, *args, **options):
        User = get_user_model()

        # Both flags matter. role is what the panel reads for its menus, and
        # is_superuser is what Django reads for the admin site. An account can
        # carry one without the other, and either one is top level access.
        tops = User.objects.filter(is_superuser=True) | User.objects.filter(role='super_admin')
        tops = tops.distinct().order_by('pk')

        if options['show'] or not options['email']:
            if not tops:
                self.stdout.write(self.style.WARNING('No top level account on this install.'))
                return
            self.stdout.write('Top level accounts:')
            for u in tops:
                self.stdout.write(
                    f'  {u.pk:>3}  {u.email:<38} role={getattr(u, "role", "?"):<14}'
                    f' superuser={u.is_superuser}  active={u.is_active}'
                )
            if not options['email']:
                self.stdout.write('\nPass --email to change one.')
            return

        new_email = options['email'].strip().lower()
        try:
            validate_email(new_email)
        except ValidationError:
            raise CommandError(f'{new_email} is not an email address.')

        if options['from_email']:
            target = tops.filter(email__iexact=options['from_email'].strip()).first()
            if not target:
                raise CommandError(f'No top level account at {options["from_email"]}.')
        elif tops.count() == 1:
            target = tops.first()
        elif tops.filter(email__iexact=new_email).exists():
            self.stdout.write(self.style.SUCCESS(
                f'Already correct: {new_email} is the Super Administrator.'))
            return
        else:
            # Guessing which of several top level accounts to rename is how a
            # client loses their own login. Make the caller name it.
            self.stdout.write(self.style.ERROR(
                f'{tops.count()} top level accounts here. Name the one you mean:'))
            for u in tops:
                self.stdout.write(f'  --from {u.email}')
            raise CommandError('Nothing changed.')

        if target.email.lower() == new_email:
            self.stdout.write(self.style.SUCCESS(f'Already correct: {new_email}'))
            return

        # Someone else already holds the address. Renaming into it would raise
        # IntegrityError on a unique field, or silently merge on a non unique
        # one, and the second is worse.
        clash = User.objects.filter(email__iexact=new_email).exclude(pk=target.pk).first()
        if clash:
            raise CommandError(
                f'{new_email} already exists here as user {clash.pk} '
                f'(role={getattr(clash, "role", "?")}). Sort that out first.')

        old = target.email
        if options['dry_run']:
            self.stdout.write(f'[dry run] user {target.pk}: {old} would become {new_email}')
            return

        with transaction.atomic():
            target.email = new_email
            # username is a separate field and stays in step, otherwise the
            # account still says the old name everywhere the panel shows it.
            if getattr(target, 'username', '') == old.split('@')[0]:
                target.username = new_email.split('@')[0]
            target.is_staff = True
            target.is_superuser = True
            # The role is left exactly as it was. An install whose top account
            # is system_admin is the platform admin sitting on a client site,
            # which is a rung above super_admin, and writing super_admin here
            # to match this command's name would demote it. This changes an
            # address. Use the tenant page if a role is genuinely wrong.
            if hasattr(target, 'role') and target.role not in ('system_admin', 'super_admin'):
                target.role = 'super_admin'
            target.save()

        self.stdout.write(self.style.SUCCESS(
            f'User {target.pk}: {old} is now {new_email}'))
        self.stdout.write(
            'The password did not change. Sign in with the old one, or reset it '
            'from the login page now that the address is right.')
