import json

from django import forms
from django.conf import settings
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User
from django.contrib.auth.forms import PasswordResetForm

from troggle.core.models.troggle import DataIssue, Person
from troggle.parsers.users import register_user, get_encryptor, ENCRYPTED_DIR, USERS_FILE
from troggle.parsers.people import troggle_slugify
from troggle.core.utils import (
    add_commit,
    is_identified_user,
    is_admin_user,
    )
from troggle.core.views.auth import expologout

"""
This is the new 2025 individual user login registration, instead of everyone signing
in as "expo". This may be useful for the kanban expo organisation tool. It also will
eventually (?) replace the cookie system for assigning responsibility to git commits.

This registration system has logic spread between these functions and the Django templates.
It seriously needs refactoring as the logic is opaque. Before that, it needs a full set of
tests to check all the combinations of users/logged-on/pre-registered/admin etc. etc.
"""

todo = """  
- Make all this work with New people who have never been on expo before - add a queue/email to be approved by an admin
- Stop anyone re-registering an email for an id which already has an email (unless by an admin)
- login automatically, and redirect to control panel ?
"""

class ExpoPasswordResetForm(PasswordResetForm):
    """Because we are using the Django-standard django.contrib.auth mechanisms, the way Django wants us to 
    modify them is to subclass their stuff and insert our extras in the subclasses. This is completely 
    unlike how the rest of troggle works because we avoid Class-based views.
    We avoid them because they are very hard to debug for newcomer programmers who don't know Django:
    the logic is spread out up a tree of preceding ancestor classes.
    
    This is where we would override the template so make the form look visually like the rest of troggle
    """
    def clean_email(self):
        email = self.cleaned_data.get('email')
        # Add custom validation logic etc. 
        print(f" * ExpoPasswordResetForm PASSWORD reset email posted '{email=}'")
        # method_list = [attribute for attribute in dir(PasswordResetForm) if callable(getattr(PasswordResetForm, attribute)) and attribute.startswith('__') is False]
        # print(method_list)
        return email
        
def reset_done(request):
    """This page is called when a password reset has successively occurred.
    Unfortunately by this point, we do not know the name of the user who initiated the
    password reset, so when we do the git commit of the encrypted users file
    we do not have a name to put to the responsible person. To do that,
    we would have to intercept at the previous step, the url:
    "reset/<uidb64>/<token>/",
        views.PasswordResetConfirmView.as_view(),
    and this class-based view is a lot more complicated to replace or sub-class.
    
    Currently we are doing the git commit anonymously.. though I guess we could attempt to
    read the cookie... if it is set. 
    """
    current_user = request.user
    save_users(request, current_user)
    if current_user.is_anonymous:
        # What we expect, for a completely new user
        return HttpResponseRedirect("/accounts/login/?next=/handbook/troggle/training/trogbegin.html")
    else:
        # This would be for someone already looged in "expo" for example
        return HttpResponseRedirect("/handbook/troggle/training/trogbegin.html")
 
def newregister(request, username=None):
    """To register a COMPLETELY new user on the troggle system, 
    WITHOUT any previous expo attendance. Currently allows random anyone to add suff to our system. Yuk.
    This is DISABLED pending implementing a queue/confirm mechanism 
    except for admin users.
    """
    current_user = request.user # if not logged in, this is 'AnonymousUser'
    warning = ""
            
    if request.method == "POST":
        form = newregister_form(request.POST)
        if form.is_valid():
            fullname = form.cleaned_data["fullname"]
            email = form.cleaned_data["email"]
                        
            nameslug = troggle_slugify(fullname)
            print(f"NEW user slug {nameslug}")
                        
            if User.objects.filter(username=nameslug).count() != 0:            
                # Disallow a name which already exists, use the other form.
                return HttpResponseRedirect(f"/accounts/register/{nameslug}")
            # create User in the system and refresh stored encrypted user list and git commit it:   
            if is_admin_user(request.user):
                updated_user = register_user(nameslug, email, password=None, pwhash=None, fullname=fullname)
                save_users(request, updated_user, email)
                return HttpResponseRedirect("/accounts/password_reset/")
            else:
                return render(request, "login/register.html", {"form": form, "warning": "Only ADMINs can do this", "newuser": True})  
    else: # GET
        form = newregister_form(initial={"visible-passwords": "True"})

    return render(request, "login/register.html", {"form": form, "warning": warning, "newuser": True})    


def re_register_email(request):
    """For a logged-on user:
    - change the email address
    - trigger reset password ( by email token)
    
    and we ignore any username specified in the URL of the request.
    """
    logged_in = (identified_login := is_identified_user(request.user)) # logged_in used on form
    if not logged_in:
        return HttpResponseRedirect("/accounts/login/")

    u = request.user
    initial_values = {}
    initial_values.update({"username": u.username}) 
    initial_values.update({"email": u.email}) 

    if request.method == "POST":
        form = register_email_form(request.POST) # only username and password
        if form.is_valid():
            print("POST VALID")
            email = form.cleaned_data["email"]
            u.email = email
            u.save()
            save_users(request, u, email)
            return render(request, "login/register_email.html", {"form": form, "confirmed": True})
        else:
            print("POST INVALID")
            return render(request, "login/register_email.html", {"form": form})
    else: # GET
        form = register_email_form(initial=initial_values)
        return render(request, "login/register_email.html", {"form": form})    

def reshow_disabled(request, url_username, initial_values, warning, admin_notice):
    """Shows the form, but completely disabled, with messages showing what the user did
    wrong or inappropriately. Could all be replaced by form validation methods ?
    """
    print(warning)
    print(admin_notice)
    form = register_form(initial=initial_values)
    form.fields["username"].widget.attrs["readonly"]="readonly"
    form.fields["email"].widget.attrs["readonly"]="readonly"
    form.fields["password1"].widget.attrs["readonly"]="readonly"
    form.fields["password2"].widget.attrs["readonly"]="readonly"
    
    return render(request, "login/register.html", {"form": form, "admin_notice": admin_notice, "warning": warning}) 
    # not used, loops: return HttpResponse warning, admin_notice): Redirect(f"/accounts/login/?next=/accounts/register/{url_username}") 

    
def register(request, url_username=None):
    """To register an old expoer as a new user on the troggle system, 
    for someone who has previously attended expo.
    Authority this gives is the same as the "expo" user
    (with cavey:beery password) but specific to an individual.
        
    """
    warning = ""
    admin_notice = ""
    initial_values={"visible-passwords": "True"}
    print(f"{url_username=} {request.user.username=}")
  
    # Since this form is for old expoers, we can expect that they know how to login as 'expo'. So require this at least.
    if request.user.is_anonymous:
        warning = "ANONYMOUS users not allowed to Register old expoers. Login, e.g. as 'expo', and try again."
        return reshow_disabled(request, url_username, initial_values, warning, admin_notice)
    
    if url_username: # if provided in URL, but if POST then this could alternatively be in the POST data field ["username"] 
        # print(url_username, "Person count",Person.objects.filter(slug=url_username).count())
        # print(url_username, "User count",User.objects.filter(username=url_username).count())            

        if (Person.objects.filter(slug=url_username).count() != 1):
            # not an old expoer, so redirect to the other form for completely new cavers,
            # but suppose it was a typo? Better to check this in response to a POST surely?
            # To do: do this as a form-valiation action.
            return HttpResponseRedirect("/accounts/newregister/")

        already_registered = (User.objects.filter(username=url_username).count() == 1)
        if already_registered:
            if is_admin_user(request.user):
                admin_notice = "ADMIN OVERRIDE ! Can re-set everything." # can proceed to reset everything
            else:
                admin_notice = f"This former expoer '{url_username}' already has a registered email address and individual troggle access password."
                return reshow_disabled(request, url_username, initial_values, warning, admin_notice)

    logged_in = (identified_login := is_identified_user(request.user)) # used on the form       
       
    if url_username:
        initial_values.update({"username": url_username})
        form = register_form(initial=initial_values)
        form.fields["username"].widget.attrs["readonly"]="readonly"
    else:
        form = register_form(initial=initial_values)
    
    if request.method == "POST":
        form = register_form(request.POST)
        if form.is_valid():
            print("POST VALID") # so now username and email fields are readonly
            un = form.cleaned_data["username"]
            pw= form.cleaned_data["password1"]
            email = form.cleaned_data["email"]
            expoers = User.objects.filter(username=un)
            # if this is a LOGONABLE user and we are not logged on
            # NOT just save the data ! Anyone could do  that..
            # we are now in a state where password should only be re-set by email token
            # but rather than redirect (off-putting) we just make the password fields read-only
            if len(expoers) > 0:
                form.fields["password1"].widget.attrs["readonly"]="readonly"
                form.fields["password2"].widget.attrs["readonly"]="readonly"

            # create User in the system and refresh stored encrypted user list and git commit it:   
            updated_user = register_user(un, email, password=pw, pwhash=None)
            save_users(request, updated_user, email)
            # to do, login automatically, and redirect to control panel ?
            form.fields["username"].widget.attrs["readonly"]="readonly"
            form.fields["email"].widget.attrs["readonly"]="readonly"
            return render(request, "login/register.html", {"form": form, "email_stored": True, "admin_notice": admin_notice, "warning": warning}) 
            # return HttpResponseRedirect("/accounts/login/")
    else: # GET
        pass
    return render(request, "login/register.html", {"form": form, "admin_notice": admin_notice, "warning": warning})    


def save_users(request, updated_user, email="troggle@exposerver.expo"):
    f = get_encryptor()
    ru = []
    
    print(f"\n + Saving users, encrypted emails, and password hashes")
    for u in User.objects.all():
        if u.username in ["expo",  "expoadmin"]:
            continue
        e_email = f.encrypt(u.email.encode("utf8")).decode()
        ru.append({"username":u.username, "email": e_email, "pwhash": u.password, "encrypted": True})
        # print(u.username, e_email)
        original = f.decrypt(e_email).decode()
        print(f" - {u.username} -  {original}")

    if updated_user.is_anonymous:
        git_string = f"troggle <troggle@exposerver.expo>"
    else:
        git_string = f"{updated_user.username} <{email}>"
    encryptedfile = settings.EXPOWEB / ENCRYPTED_DIR / USERS_FILE
    try:
        print(f"- Rewriting the entire encrypted set of registered users to disc ")
        write_users(ru, encryptedfile, git_string)
    except:
        message = f'! - Users encrypted data saving failed - \n!! Permissions failure ?! on attempting to save file "{encryptedfile}"'
        print(message)
        raise
        return render(request, "errors/generic.html", {"message": message})

def write_users(registered_users, encryptedfile, git_string):       
    jsondict = { "registered_users": registered_users }
    try:
        with open(encryptedfile, 'w', encoding='utf-8') as json_f:
            json.dump(jsondict, json_f, indent=1)
    except Exception as e:
         print(f" ! Exception dumping json <{e}>")
         raise
       
    commit_msg = f"Online (re-)registration of a troggle User"
    try:
        add_commit(encryptedfile, commit_msg, git_string)
    except Exception as e:
         print(f" ! Exception doing git add/commit <{e}>")
         raise
    return True

class newregister_form(forms.Form):  # not a model-form, just a form-form
    """This is the form for a new user who has not been on expo before and 
    does notalready have a username.
    """
    fullname = forms.CharField(strip=True, required=True, 
        label="Forename Surname",
        widget=forms.TextInput(
            attrs={"size":  35, "placeholder": "e.g. Anathema Device", 
            "style": "vertical-align: text-top;"}
        ))
    email = forms.CharField(strip=True, required=True, 
        label="email",
        widget=forms.TextInput(
            attrs={"size":  35, "placeholder": "e.g. anathema@tackle_store.expo", 
            "style": "vertical-align: text-top;"}
        ))

    def clean(self):
        cleaned_data = super().clean()
        fullname = cleaned_data.get("fullname")            
        email = cleaned_data.get("email")
        
        users = User.objects.filter(email=email)
        if len(users) != 0:
            raise ValidationError(
                "Duplicate email address. Another registered user is already using this email address. Email addresses must be unique as that is how we reset forgotten passwords."
            )  
        userslug = troggle_slugify(fullname)
        people = Person.objects.filter(slug=userslug)
        if len(people) != 0:
            raise ValidationError(
                "Duplicate name. There is already a username correspondng to this Forename Surname. " +
                "If you have been on expo before, you need to use the other form at expo.survex.com/accounts/register/ ." 
             )  

class register_email_form(forms.Form):  # not a model-form, just a form-form
    """The widgets are being used EVEN THOUGH we are not using form.as_p() to create the
    HTML form"""
    username = forms.CharField(strip=True, required=True, 
        label="Username",
        widget=forms.TextInput(
            attrs={"size":  35, "placeholder": "e.g. anathema-device", 
            "style": "vertical-align: text-top;", "readonly": "readonly"} # READONLY for when changing email
        ))
    email = forms.CharField(strip=True, required=True, 
        label="email",
        widget=forms.TextInput(
            attrs={"size":  35, "placeholder": "e.g. anathema@potatohut.expo", 
            "style": "vertical-align: text-top;"}
        ))

    def clean(self):
        cleaned_data = super().clean()
            
        email = cleaned_data.get("email")
        users = User.objects.filter(email=email)
        print(f"{len(users)=}")
        if len(users) > 1: # allow 0 (change) or 1 (confirm)
            print("ValidationError")
            raise ValidationError(
                "Duplicate email address. Another registered user is already using this email address. Email addresses must be unique as that is how we reset forgotten passwords."
            )              
            
class register_form(forms.Form):  # not a model-form, just a form-form
    """The widgets are being used EVEN THOUGH we are not using form.as_p() to create the
    HTML form"""
    username = forms.CharField(strip=True, required=True, 
        label="Username",
        widget=forms.TextInput(
            attrs={"size":  35, "placeholder": "e.g. anathema-device", 
            "style": "vertical-align: text-top;"}
        ))
    email = forms.CharField(strip=True, required=True, 
        label="email",
        widget=forms.TextInput(
            attrs={"size":  35, "placeholder": "e.g. anathema@potatohut.expo", 
            "style": "vertical-align: text-top;"}
        ))
    password1 = forms.CharField(strip=True, required=True,         
        label="Troggle password",
        widget=forms.TextInput(
            attrs={"size":  30, "placeholder": "your new login password", 
            "style": "vertical-align: text-top;"}
        ))
    password2 = forms.CharField(strip=True, required=True, 
        label="Re-type your troggle password",
        widget=forms.TextInput(
            attrs={"size":  30, "placeholder": "same as the password above", 
            "style": "vertical-align: text-top;"}
        ) )

    def clean(self):
        cleaned_data = super().clean()
        pw1 = cleaned_data.get("password1")
        pw2 = cleaned_data.get("password2")

        if pw1 != pw2:
            raise ValidationError(
                "Retyped password does not match initial password: please fix this."
            ) 
        un = cleaned_data.get("username") 
        if un in ["expo", "expoadmin"]:
            raise ValidationError(
                "Sorry, please do not try to be clever. This username is hard-coded and you can't edit it here. We were students once, too."
            ) 
            
        expoers = Person.objects.filter(slug=un)
        if len(expoers) == 0:
            raise ValidationError(
                "Sorry, this is the form for people who have already been to expo. Use the New User registration form (link above)."
            ) 
        if len(expoers) != 1:
            raise ValidationError(
                "Sorry, that troggle identifier has duplicates. Contact a nerd on the Nerd email list, or (better) the Matrix website chat."
            ) 
        email = cleaned_data.get("email")
        users = User.objects.filter(email=email)
        if len(users) > 1:
            raise ValidationError(
                f"Duplicate email address. Another registered user {users[0]} is already using this email address. Email addresses must be unique as that is how we reset forgotten passwords."
            )              
        if len(users) == 1:
            if users[0].username != un:
                raise ValidationError(
                    f"Duplicate email address. Another registered user '{users[0]}' is already using this email address. Email addresses must be unique as that is how we reset forgotten passwords."
                    )
                 