/* Copyright (C) Itseez3D, Inc. - All Rights Reserved
* You may not use this file except in compliance with an authorized license
* Unauthorized copying of this file, via any medium is strictly prohibited
* Proprietary and confidential
* UNLESS REQUIRED BY APPLICABLE LAW OR AGREED BY ITSEEZ3D, INC. IN WRITING, SOFTWARE DISTRIBUTED UNDER THE LICENSE IS DISTRIBUTED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED
* See the License for the specific language governing permissions and limitations under the License.
* Written by Itseez3D, Inc. <support@avatarsdk.com>, August 2026
*/

using System;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;

namespace AvatarSDKMove.Editor
{
	[InitializeOnLoad]
	public class AccountWindow : EditorWindow
	{
		private static int numUpdatesToWait = 4;

		private readonly Connection connection = new Connection();

		private UserSession session;
		private string playerUid = string.Empty;
		private bool isGenerationAllowed = true;

		private string email = string.Empty;
		private string code = string.Empty;

		private bool isCodeRequested = false;
		private bool isBusy = false;
		private string statusMessage = string.Empty;
		private MessageType statusMessageType = MessageType.Info;

		static AccountWindow()
		{
			EditorApplication.update += InitializeOnce;
		}

		private static void InitializeOnce()
		{
			if (--numUpdatesToWait > 0)
				return;

			EditorApplication.update -= InitializeOnce;

			if (AuthUtils.HasSession())
				return;

			Debug.Log("Avatar SDK Move account is not connected. Opening the account window...");
			ShowWindow();
		}

		[MenuItem("Avatar SDK Move/Account", priority = 2)]
		public static void ShowWindow()
		{
			AccountWindow window = GetWindow<AccountWindow>("Account");
			window.minSize = new Vector2(420, 320);
			window.Show();
		}

		private void OnEnable()
		{
			session = AuthUtils.LoadSession();
			if (session == null)
				return;

			email = session.email;
			playerUid = PlayerUidStorage.Load(session.email);
			RefreshAccount();
		}

		private void OnGUI()
		{
			GUILayout.Label("Avatar SDK Move Account", EditorStyles.boldLabel);
			GUILayout.Space(5);

			if (!string.IsNullOrEmpty(statusMessage))
			{
				EditorGUILayout.HelpBox(statusMessage, statusMessageType);
				GUILayout.Space(10);
			}

			if (session != null)
				DrawAccountGUI();
			else
				DrawSignInGUI();

			GUILayout.FlexibleSpace();
			EditorGUILayout.LabelField("Avatar SDK Move " + AvatarSDKMoveSettings.Version, EditorStyles.miniLabel);
		}

		private void DrawAccountGUI()
		{
			EditorGUILayout.LabelField("Signed in as", session.email, EditorStyles.boldLabel);

			if (!string.IsNullOrEmpty(playerUid))
				EditorGUILayout.LabelField("Player UID", playerUid);

			GUILayout.Space(10);

			if (!isGenerationAllowed)
			{
				EditorGUILayout.HelpBox(
					"Animation generation is not enabled for this account. Contact " + AvatarSDKMoveSettings.SupportEmail + ".",
					MessageType.Warning);
				GUILayout.Space(10);
			}

			EditorGUILayout.HelpBox(AvatarSDKMoveSettings.BetaNotice, MessageType.Info);

			GUILayout.Space(15);
			DrawNextStepsGUI();
			GUILayout.Space(15);

			GUI.enabled = !isBusy;
			if (GUILayout.Button("Log Out", GUILayout.Height(24)))
			{
				LogOut();
			}
			GUI.enabled = true;
		}

		private void DrawNextStepsGUI()
		{
			GUILayout.Label("Next steps", EditorStyles.boldLabel);

			if (GUILayout.Button("Open Getting Started Sample", GUILayout.Height(24)))
				OpenSampleScene(AvatarSDKMoveSettings.GettingStartedSceneGuid, "Getting Started");

			EditorGUILayout.LabelField("Generate an animation in the Editor.", EditorStyles.miniLabel);

			GUILayout.Space(5);

			if (GUILayout.Button("Open Runtime Sample", GUILayout.Height(24)))
				OpenSampleScene(AvatarSDKMoveSettings.RuntimeSampleSceneGuid, "Runtime");

			EditorGUILayout.LabelField("Generate an animation at runtime from a UI.", EditorStyles.miniLabel);
		}

		private static void OpenSampleScene(string guid, string description)
		{
			string path = AssetDatabase.GUIDToAssetPath(guid);
			if (string.IsNullOrEmpty(path))
			{
				Debug.LogWarningFormat("Could not find the Avatar SDK Move {0} scene in this project.", description);
				return;
			}

			if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
				return;

			EditorSceneManager.OpenScene(path);
		}

		private void DrawSignInGUI()
		{
			EditorGUILayout.LabelField(
				"Enter your email address. We will send you a one-time code to confirm it.",
				EditorStyles.wordWrappedLabel);

			GUILayout.Space(10);

			using (new EditorGUI.DisabledScope(isBusy))
			{
				email = EditorGUILayout.TextField("Email", email);
			}

			GUI.enabled = !isBusy && !string.IsNullOrEmpty(email);
			if (GUILayout.Button(isCodeRequested ? "Send Code Again" : "Send Code", GUILayout.Height(24)))
			{
				RequestCode();
			}
			GUI.enabled = true;

			if (!isCodeRequested)
				return;

			GUILayout.Space(10);

			using (new EditorGUI.DisabledScope(isBusy))
			{
				code = EditorGUILayout.TextField("Code", code);
			}

			GUI.enabled = !isBusy && !string.IsNullOrEmpty(code);
			if (GUILayout.Button("Authenticate", GUILayout.Height(30)))
			{
				Authenticate();
			}
			GUI.enabled = true;
		}

		private async void RefreshAccount()
		{
			isBusy = true;
			SetStatus("Updating account info...", MessageType.Info);

			try
			{
				AuthResult result = await connection.RefreshSession(session.refreshToken);
				if (!result.success)
				{
					AuthUtils.ClearSession();
					session = null;
					playerUid = string.Empty;
					isCodeRequested = false;
					SetStatus("Your session has expired. Please sign in again.", MessageType.Warning);
					return;
				}

				session = new UserSession(session.email, result.token, result.refresh_token);
				AuthUtils.StoreSession(session);

				UserInfoData userInfo = await connection.GetUserInfo(session.token);
				isGenerationAllowed = userInfo.move_bundle_creation_allowed;

				playerUid = userInfo.player_uid;
				PlayerUidStorage.Store(session.email, playerUid);

				SetStatus(string.Empty, MessageType.Info);
			}
			catch (Exception ex)
			{
				Debug.LogException(ex);
				SetStatus("Could not update the account info: " + ex.Message, MessageType.Error);
			}
			finally
			{
				isBusy = false;
				Repaint();
			}
		}

		private void LogOut()
		{
			AuthUtils.ClearSession();

			session = null;
			playerUid = string.Empty;
			code = string.Empty;
			isCodeRequested = false;

			SetStatus("You have been logged out.", MessageType.Info);
		}

		private async void RequestCode()
		{
			isBusy = true;
			SetStatus("Sending the code...", MessageType.Info);

			try
			{
				await connection.AuthenticateClient();

				RequestCodeResult result = await connection.RequestAuthenticationCode(email);
				if (!result.success)
				{
					SetStatus(FormatRequestCodeError(result), MessageType.Error);
					return;
				}

				isCodeRequested = true;
				SetStatus("A code has been sent to " + email + ".", MessageType.Info);
			}
			catch (Exception ex)
			{
				Debug.LogException(ex);
				SetStatus("Could not send the code: " + ex.Message, MessageType.Error);
			}
			finally
			{
				isBusy = false;
				Repaint();
			}
		}

		private async void Authenticate()
		{
			isBusy = true;
			SetStatus("Authenticating...", MessageType.Info);

			try
			{
				AuthResult result = await connection.SubmitAuthenticationCode(email, code);
				if (!result.success)
				{
					SetStatus(string.IsNullOrEmpty(result.error) ? "The code is invalid or expired." : result.error, MessageType.Error);
					return;
				}

				var newSession = new UserSession(email, result.token, result.refresh_token);
				AuthUtils.StoreSession(newSession);

				UserSession storedSession = AuthUtils.LoadSession();
				if (storedSession == null || storedSession.email != newSession.email || storedSession.token != newSession.token)
				{
					SetStatus("Authentication succeeded, but the session could not be saved. See the console for details.", MessageType.Error);
					return;
				}

				playerUid = result.player_uid;
				PlayerUidStorage.Store(email, playerUid);

				session = newSession;
				code = string.Empty;
				isCodeRequested = false;

				ShowNotification(new GUIContent("Successfully authenticated"));
				SetStatus(string.Empty, MessageType.Info);
			}
			catch (Exception ex)
			{
				Debug.LogException(ex);
				SetStatus("Authentication failed: " + ex.Message, MessageType.Error);
			}
			finally
			{
				isBusy = false;
				Repaint();
			}
		}

		private static string FormatRequestCodeError(RequestCodeResult result)
		{
			if (result.retry_after > RequestCodeResult.RetryAfterNotProvided)
				return string.Format("Too many requests. Try again in {0:0} seconds.", result.retry_after);

			return string.IsNullOrEmpty(result.error) ? "Could not send the code." : result.error;
		}

		private void SetStatus(string message, MessageType messageType)
		{
			statusMessage = message;
			statusMessageType = messageType;
			Repaint();
		}
	}
}
