using System;
using System.Net.Mail;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.UIElements;
using Button = UnityEngine.UIElements.Button;

/// <summary>
///     class to handle the account view ui
/// </summary>
public class AccountUI : MonoBehaviour
{
    /// <summary>
    ///     current state of the account view
    /// </summary>
    [SerializeField] private State state = State.UnassociatedState;

    /// <summary>
    ///     default text colour
    /// </summary>
    private readonly StyleColor _defaultInputFieldValueTextColour = new(new Color(0.0588f, 0.051f, 0.1059f));

    /// <summary>
    ///     error text colour
    /// </summary>
    private readonly StyleColor _errorInputFieldValueTextColour = new Color(1f, 0.5059f, 0.502f);

    /// <summary>
    ///     accompanying text for the account input fields, used when an error/notice is needed
    /// </summary>
    private Label _accompanyingText;

    /// <summary>
    ///     email text field
    /// </summary>
    private TextField _emailField;

    /// <summary>
    ///     button to update the email
    /// </summary>
    private Button _emailUpdateButton;

    /// <summary>
    ///     account view header text
    /// </summary>
    private Label _header;

    /// <summary>
    ///     password text field
    /// </summary>
    private TextField _passwordField;

    /// <summary>
    ///     button to update the password
    /// </summary>
    private Button _passwordUpdateButton;

    /// <summary>
    ///     either 'continue', 'log in', or 'sign out' button
    ///     (in order of 'initial', 'after', and 'post' states)
    /// </summary>
    private Button _primaryActionButton;

    /// <summary>
    ///     either 'forgot password' or 'create an account'
    ///     (in order of 'initial' and 'after' states, is hidden in 'post' state)
    /// </summary>
    private Button _secondaryActionButton;

    /// <summary>
    ///     either 'delete local data' or 'delete account'
    ///     (in order of 'initial', and 'post' states, is hidden in 'after' state)
    /// </summary>
    private Button _tertiaryActionButton;

    /// <summary>
    ///     username text field
    /// </summary>
    private TextField _usernameField;

    /// <summary>
    ///     button to update the username
    /// </summary>
    private Button _usernameUpdateButton;

    /// <summary>
    ///     function called when the object is enabled,
    ///     subscribes button events to their respective functions
    /// </summary>
    private void OnEnable()
    {
        var ui = GetComponent<UIDocument>().rootVisualElement;

        _header = ui.Q<Label>("AccountHeader");

        _usernameField = ui.Q<TextField>("UsernameField");
        _emailField = ui.Q<TextField>("EmailField");
        _passwordField = ui.Q<TextField>("PasswordField");

        _usernameUpdateButton = ui.Q<Button>("UsernameUpdateButton");
        _usernameUpdateButton.clicked += OnUsernameUpdateButtonClick;

        _emailUpdateButton = ui.Q<Button>("EmailUpdateButton");
        _emailUpdateButton.clicked += OnEmailUpdateButtonClick;

        _passwordUpdateButton = ui.Q<Button>("PasswordUpdateButton");
        _passwordUpdateButton.clicked += OnPasswordUpdateButtonClick;

        _accompanyingText = ui.Q<Label>("AccompanyingText");

        _primaryActionButton = ui.Q<Button>("PrimaryActionButton");
        _primaryActionButton.clicked += OnPrimaryActionButtonClick;

        _secondaryActionButton = ui.Q<Button>("SecondaryActionButton");
        _secondaryActionButton.clicked += OnSecondaryActionButtonClick;

        _tertiaryActionButton = ui.Q<Button>("TertiaryActionButton");
        _tertiaryActionButton.clicked += OnTertiaryActionButtonClick;

        TransitionStateTo(State.NotSignedIn);

        if (state == State.UnassociatedState) throw new Exception("unreachable state");
        GameManager.Instance.Backend.RegisterOnSignInCallback(_ =>
        {
            Debug.Log("post-authentication callback, updating AccountView fields");
            var username = GameManager.Instance.Backend.GetUsername();
            _header.text = $"Signed in as {username}";
            _passwordField.value = "";
            _usernameField.value = username;
            _emailField.value = GameManager.Instance.Backend.GetUser().Email;
        });
        GameManager.Instance.RegisterOnLocalPlayerDataChangeCallback(OnLocalPlayerDataChangeCallback);
    }

    private void TransitionStateTo(State newState, bool keepAccompanyingText = false)
    {
        // if we're transitioning to the same state, do nothing
        if (state == newState) return;

        // else, hide the accompanying text
        if (!keepAccompanyingText) _accompanyingText.style.display = DisplayStyle.None;

        // hide update buttons if not signed in
        if (newState != State.SignedIn)
        {
            _usernameUpdateButton.style.display = DisplayStyle.None;
            _passwordUpdateButton.style.display = DisplayStyle.None;
            _emailUpdateButton.style.display = DisplayStyle.None;
        }
        else
        {
            _usernameUpdateButton.style.display = DisplayStyle.Flex;
            _passwordUpdateButton.style.display = DisplayStyle.Flex;
            _emailUpdateButton.style.display = DisplayStyle.Flex;
        }

        // set primary/secondary buttons
        switch (newState)
        {
            case State.NotSignedIn:
                _header.text = "You are not signed in.";

                _usernameField.style.display = DisplayStyle.None;
                _emailField.style.display = DisplayStyle.Flex;
                _passwordField.style.display = DisplayStyle.Flex;

                _primaryActionButton.style.display = DisplayStyle.Flex;
                _secondaryActionButton.style.display = DisplayStyle.Flex;
                _tertiaryActionButton.style.display = DisplayStyle.Flex;

                _primaryActionButton.text = "Continue \u2192";
                _secondaryActionButton.text = "Forgot Password \u2192";
                _tertiaryActionButton.text = "Delete Local Data \u2192";
                break;

            case State.AfterContinue:
                _header.text = "You are not signed in.";

                _usernameField.style.display = DisplayStyle.None;
                _emailField.style.display = DisplayStyle.Flex;
                _passwordField.style.display = DisplayStyle.Flex;

                _primaryActionButton.style.display = DisplayStyle.Flex;
                _secondaryActionButton.style.display = DisplayStyle.Flex;
                _tertiaryActionButton.style.display = DisplayStyle.None;

                _primaryActionButton.text = "Retry Log In \u2192";
                _secondaryActionButton.text = "Create an Account \u2192";
                break;

            case State.SignedIn:
                Debug.Log("transitioning to signed in state");

                var username = GameManager.Instance.Backend.GetUsername();
                _header.text = string.IsNullOrEmpty(username) ? "You are signed in." : $"Signed in as {username}";

                _usernameField.style.display = DisplayStyle.Flex;
                _emailField.style.display = DisplayStyle.Flex;
                _passwordField.style.display = DisplayStyle.Flex;

                _primaryActionButton.style.display = DisplayStyle.Flex;
                _secondaryActionButton.style.display = DisplayStyle.None;
                _tertiaryActionButton.style.display = DisplayStyle.Flex;

                _primaryActionButton.text = "Sign Out \u2192";
                _tertiaryActionButton.text = "Delete Account \u2192";
                break;

            case State.UnassociatedState:
            default:
                throw new ArgumentOutOfRangeException();
        }

        state = newState;
    }

    private void ValidateUsername(TextField usernameField)
    {
        // just has to be min. 5 characters
        usernameField.style.color = _defaultInputFieldValueTextColour;
        if (usernameField.value.Length >= 5) return;
        usernameField.style.color = _errorInputFieldValueTextColour;
        throw new Exception("Username must be at least 5 characters long.");
    }

    /// <summary>
    ///     validate the email field
    /// </summary>
    /// <param name="emailField">the email field to validate</param>
    /// <exception cref="Exception">if the email field is invalid</exception>
    private void ValidateEmailField(TextField emailField)
    {
        emailField.style.color = _defaultInputFieldValueTextColour;
        try
        {
            var dot = emailField.value.LastIndexOf(".", StringComparison.Ordinal);

            // check if a dot exists
            Assert.IsTrue(dot != -1);

            // if a dot exists, check if the first part has the @ symbol
            // so emails like 'hello.bro@domain' are invalid (I mean, they are, but not for this)
            Assert.IsTrue(emailField.value.IndexOf("@", StringComparison.Ordinal) < dot);

            _ = new MailAddress(emailField.value);
        }
        catch
        {
            emailField.style.color = _errorInputFieldValueTextColour;
            throw new Exception("Invalid email.");
        }
    }

    /// <summary>
    ///     validate the password field
    /// </summary>
    /// <param name="passwordField">the password field to validate</param>
    /// <exception cref="Exception">if the password field is invalid</exception>
    private void ValidatePasswordField(TextField passwordField)
    {
        passwordField.style.color = _defaultInputFieldValueTextColour;
        if (passwordField.value.Length >= 10) return;
        passwordField.style.color = _errorInputFieldValueTextColour;
        throw new Exception("Password must be at least 10 characters long.");
    }

    /// <summary>
    ///     validate both the email and password fields
    /// </summary>
    /// <param name="emailField">the email field to validate</param>
    /// <param name="passwordField">the password field to validate</param>
    /// <exception cref="Exception">if either the email or password field is invalid</exception>
    private void ValidateFields(TextField emailField, TextField passwordField)
    {
        var invalidEmail = false;
        var invalidPassword = false;

        try
        {
            ValidateEmailField(emailField);
        }
        catch (Exception)
        {
            invalidEmail = true;
        }

        try
        {
            ValidatePasswordField(passwordField);
        }
        catch (Exception)
        {
            invalidPassword = true;
        }

        if (!invalidEmail && !invalidPassword) return;
        var errorMessage = (invalidEmail, invalidPassword) switch
        {
            (true, true) => "Invalid email and password is too short.",
            (true, false) => "Invalid email.",
            (false, true) => "Passwords must be at least 10 characters long.",
            _ => null
        };
        throw new Exception(errorMessage);
    }

    /// <summary>
    ///     validate both the email and password fields
    /// </summary>
    /// <param name="emailField">the email field to validate</param>
    /// <param name="passwordField">the password field to validate</param>
    /// <param name="usernameField">the username field to validate</param>
    /// <exception cref="Exception">if either the email or password field is invalid</exception>
    private void ValidateFields(TextField emailField, TextField passwordField, TextField usernameField)
    {
        var invalidEmail = false;
        var invalidPassword = false;
        var invalidUsername = false;

        try
        {
            ValidateEmailField(emailField);
        }
        catch (Exception)
        {
            invalidEmail = true;
        }

        try
        {
            ValidatePasswordField(passwordField);
        }
        catch (Exception)
        {
            invalidPassword = true;
        }

        try
        {
            ValidateUsername(usernameField);
        }
        catch (Exception)
        {
            invalidUsername = true;
        }

        if (!invalidEmail && !invalidPassword && !invalidUsername) return;
        var errorMessage = (invalidEmail, invalidPassword, invalidUsername) switch
        {
            (true, true, true) => "Invalid email, password is too short, and username is too short.",
            (true, true, false) => "Invalid email, and password is too short.",
            (true, false, true) => "Invalid email, and username is too short.",
            (true, false, false) => "Invalid email.",
            (false, true, true) => "Password is too short, and username is too short.",
            (false, true, false) => "Password is too short.",
            (false, false, true) => "Username is too short.",
            _ => null
        };

        throw new Exception(errorMessage);
    }

    /// <summary>
    ///     function to handle the username update button click
    /// </summary>
    private void OnUsernameUpdateButtonClick()
    {
        try
        {
            ValidateUsername(_usernameField);
        }
        catch (Exception e)
        {
            _accompanyingText.style.display = DisplayStyle.Flex;
            _accompanyingText.text = e.Message;
            return;
        }

        GameManager.Instance.Backend.UpdateUserAccountDetail(Backend.UserAccountDetailTargetEnum.Username,
            _usernameField.value,
            res =>
            {
                _accompanyingText.style.display = DisplayStyle.Flex;
                _accompanyingText.text = res switch
                {
                    Backend.TransactionResult.Ok => "Username updated!",
                    Backend.TransactionResult.Unauthenticated =>
                        "You are not signed in. Please sign in to update your username.",
                    _ => "An error occurred updating the username. Please try again."
                };
            });
    }

    /// <summary>
    ///     function to handle the email update button click
    /// </summary>
    private void OnEmailUpdateButtonClick()
    {
        try
        {
            ValidateEmailField(_emailField);
        }
        catch (Exception e)
        {
            _accompanyingText.style.display = DisplayStyle.Flex;
            _accompanyingText.text = e.Message;
            return;
        }

        GameManager.Instance.Backend.UpdateUserAccountDetail(Backend.UserAccountDetailTargetEnum.Email,
            _emailField.value,
            callback =>
            {
                _accompanyingText.style.display = DisplayStyle.Flex;
                _accompanyingText.text = callback switch
                {
                    Backend.TransactionResult.Ok =>
                        $"Verification email sent to {_emailField.value}! You may want to sign in again to see the changes.",
                    Backend.TransactionResult.Unauthenticated =>
                        "You are not signed in. Please sign in to update your email.",
                    _ => "An error occurred updating the email. Please try again."
                };
            });
    }

    /// <summary>
    ///     function to handle the password update button click
    /// </summary>
    private void OnPasswordUpdateButtonClick()
    {
        try
        {
            ValidatePasswordField(_passwordField);
        }
        catch (Exception e)
        {
            _accompanyingText.style.display = DisplayStyle.Flex;
            _accompanyingText.text = e.Message;
            return;
        }

        GameManager.Instance.Backend.UpdateUserAccountDetail(Backend.UserAccountDetailTargetEnum.Password,
            _passwordField.value,
            callback =>
            {
                _accompanyingText.style.display = DisplayStyle.Flex;
                _accompanyingText.text = callback switch
                {
                    Backend.TransactionResult.Ok => "Password updated!",
                    Backend.TransactionResult.Unauthenticated =>
                        "You are not signed in. Please sign in to update your password.",
                    _ => "An error occurred updating the password. Please try again."
                };
            });
    }

    /// <summary>
    ///     function to handle the primary action button click
    /// </summary>
    private void OnPrimaryActionButtonClick()
    {
        switch (state)
        {
            // - continue button
            //   (attempt to figure out to log in or give the option to create an account)
            // - log in button
            case State.NotSignedIn:
            case State.AfterContinue:
                try
                {
                    ValidateFields(_emailField, _passwordField);
                }
                catch (Exception e)
                {
                    _accompanyingText.style.display = DisplayStyle.Flex;
                    _accompanyingText.text = e.Message;
                    return;
                }

                GameManager.Instance.Backend.AuthenticateUser(
                    _emailField.value,
                    _passwordField.value,
                    result =>
                    {
                        switch (result)
                        {
                            case Backend.AuthenticationResult.Ok:
                            case Backend.AuthenticationResult.AlreadyAuthenticated:
                                TransitionStateTo(State.SignedIn);
                                break;

                            case Backend.AuthenticationResult.NonExistentUser:
                                TransitionStateTo(State.AfterContinue);
                                break;

                            case Backend.AuthenticationResult.InvalidEmail:
                                _accompanyingText.style.display = DisplayStyle.Flex;
                                _accompanyingText.text = "Invalid email. Please try again.";
                                break;

                            case Backend.AuthenticationResult.InvalidCredentials:
                                _accompanyingText.style.display = DisplayStyle.Flex;
                                _accompanyingText.text = "Invalid credentials. Please try again.";
                                break;

                            case Backend.AuthenticationResult.AlreadyExistingUser:
                            case Backend.AuthenticationResult.GenericError:
                            default:
                                TransitionStateTo(State.AfterContinue);
                                _accompanyingText.style.display = DisplayStyle.Flex;
                                _accompanyingText.text =
                                    "There was an error. Either the account does not exist, or the credentials are invalid. Try again.";
                                break;
                        }
                    });
                break;

            // sign out button
            case State.SignedIn:
                GameManager.Instance.Backend.SignOutUser();
                TransitionStateTo(State.NotSignedIn);
                break;

            case State.UnassociatedState:
            default:
                Debug.LogError($"tertiary button clicked in illogical state {state} (unreachable?)");
                break;
        }
    }

    /// <summary>
    ///     function to handle the secondary action button click
    /// </summary>
    private void OnSecondaryActionButtonClick()
    {
        switch (state)
        {
            // forgot password button
            case State.NotSignedIn:
                try
                {
                    ValidateEmailField(_emailField);
                }
                catch (Exception e)
                {
                    _accompanyingText.style.display = DisplayStyle.Flex;
                    _accompanyingText.text = e.Message;
                    return;
                }

                GameManager.Instance.Backend.ResetUserPassword(_emailField.value, result =>
                {
                    _accompanyingText.style.display = DisplayStyle.Flex;
                    _accompanyingText.text =
                        result
                            ? "Password reset email sent. Check your inbox! :D"
                            : "An error occurred sending the email. Please try again.";
                });
                break;

            // create an account button
            case State.AfterContinue:
                try
                {
                    ValidateFields(_emailField, _passwordField, _usernameField);
                }
                catch (Exception e)
                {
                    _accompanyingText.style.display = DisplayStyle.Flex;
                    _accompanyingText.text = e.Message;
                    return;
                }

                // if the username field is hidden, show it and then immediately return
                if (_usernameField.style.display == DisplayStyle.None)
                {
                    _usernameField.style.display = DisplayStyle.Flex;
                    _accompanyingText.style.display = DisplayStyle.Flex;
                    _accompanyingText.text = "Set a username for your account.";
                    return;
                }

                GameManager.Instance.Backend.AuthenticateUser(
                    _emailField.value,
                    _passwordField.value,
                    result =>
                    {
                        switch (result)
                        {
                            case Backend.AuthenticationResult.Ok:
                            case Backend.AuthenticationResult.AlreadyAuthenticated:
                                TransitionStateTo(State.SignedIn);
                                break;

                            case Backend.AuthenticationResult.InvalidEmail:
                                _accompanyingText.style.display = DisplayStyle.Flex;
                                _accompanyingText.text = "Invalid email. Please try again.";
                                break;

                            case Backend.AuthenticationResult.InvalidCredentials:
                                _accompanyingText.style.display = DisplayStyle.Flex;
                                _accompanyingText.text = "Invalid credentials. Please try again.";
                                break;

                            case Backend.AuthenticationResult.UsernameAlreadyTaken:
                                _accompanyingText.style.display = DisplayStyle.Flex;
                                _accompanyingText.text = "Username already taken. Please try another.";
                                break;

                            case Backend.AuthenticationResult.NonExistentUser:
                            case Backend.AuthenticationResult.AlreadyExistingUser:
                            case Backend.AuthenticationResult.GenericError:
                            default:
                                _accompanyingText.style.display = DisplayStyle.Flex;
                                _accompanyingText.text = "An error occurred. Please try again.";
                                Debug.LogError(result);
                                break;
                        }
                    },
                    true,
                    _usernameField.value);
                break;

            case State.UnassociatedState:
            default:
                Debug.LogError($"tertiary button clicked in illogical state {state} (unreachable?)");
                break;
        }
    }

    private void OnTertiaryActionButtonClick()
    {
        switch (state)
        {
            // delete local data
            case State.NotSignedIn:
                PlayerPrefs.DeleteAll();
                _accompanyingText.style.display = DisplayStyle.Flex;
                _accompanyingText.text = "Local data deleted.";
                GameManager.Instance.Data.LoadFromTheWorld(GameManager.Instance.FireLocalPlayerDataChangeCallbacks);
                break;

            // delete user account
            case State.SignedIn:
                GameManager.Instance.Backend.DeleteUser(result =>
                {
                    _accompanyingText.style.display = DisplayStyle.Flex;
                    _accompanyingText.text = result switch
                    {
                        Backend.TransactionResult.Ok => "Account deleted.",
                        Backend.TransactionResult.Unauthenticated => "You are not signed in.",
                        _ => "An error occurred deleting the account. Please try again."
                    };
                });
                break;

            case State.AfterContinue:
            case State.UnassociatedState:
            default:
                Debug.LogError($"tertiary button clicked in illogical state {state} (unreachable?)");
                break;
        }
    }

    /// <summary>
    ///     populate the fields with the given username and email,
    ///     used as a callback to when local player data is changed
    /// </summary>
    private void OnLocalPlayerDataChangeCallback(LocalPlayerData data)
    {
        Debug.Log(
            $"updating AccountView ui with lkUsername={data.LastKnownUsername} and lkEmail={data.LastKnownEmail}");
        _usernameField.value = data.LastKnownUsername;
        _emailField.value = data.LastKnownEmail;
        if (state == State.SignedIn) _header.text = $"Signed in as {data.LastKnownUsername}";
    }

    /// <summary>
    ///     state of the account view
    /// </summary>
    private enum State
    {
        UnassociatedState, // (start)
        NotSignedIn, // initial
        AfterContinue, // after
        SignedIn // post
    }
}