/* 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 const double FocusRefreshIntervalSeconds = 30.0;

		private static int numUpdatesToWait = 4;

		private readonly Connection connection = new Connection();

		private UserSession session;
		private string playerUid = string.Empty;
		private UserInfoData userInfo;
		private double lastFocusRefreshTime = 0;
		private string serverName = AvatarSDKMoveServer.Name;

		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()
		{
			AuthUtils.SessionChanged += OnSessionChanged;

			session = AuthUtils.LoadSession();
			if (session == null)
				return;

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

		private void OnDisable()
		{
			AuthUtils.SessionChanged -= OnSessionChanged;
		}

		private void OnSessionChanged()
		{
			session = AuthUtils.LoadSession();
			playerUid = session == null ? string.Empty : session.playerUid;

			bool isServerChanged = serverName != AvatarSDKMoveServer.Name;
			serverName = AvatarSDKMoveServer.Name;
			if (session == null || isServerChanged)
				userInfo = null;
			if (session != null && isServerChanged)
				RefreshAccountQuietly();

			Repaint();
		}

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

			if (AvatarSDKMoveServer.IsOverridden)
			{
				EditorGUILayout.HelpBox(string.Format("Development server: {0} ({1})", AvatarSDKMoveServer.Name, AvatarSDKMoveServer.ApiUrl), MessageType.Warning);
				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);

			if (userInfo != null)
			{
				using (new EditorGUILayout.HorizontalScope())
				{
					EditorGUILayout.PrefixLabel("Balance");
					CoinIcon.LayoutAmount(userInfo.balance, EditorStyles.label, EditorGUIUtility.singleLineHeight);
				}
				if (userInfo.free_move_animations > 0)
					EditorGUILayout.LabelField("Free animations", userInfo.free_move_animations.ToString());
				if (userInfo.have_asdk_subscription)
					EditorGUILayout.LabelField("Subscription", "Active");
			}

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

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

		private void DrawCoinsGUI()
		{
			EditorGUILayout.HelpBox(
				string.Format("Animation generation costs {0} coins per second of animation. Coins are purchased and topped up in MetaPerson Creator.",
					AvatarSDKMoveSettings.CoinsPerAnimationSecond),
				MessageType.Info);

			if (GUILayout.Button("Top Up Coins in MetaPerson Creator", GUILayout.Height(24)))
				MetaPersonCreatorLink.OpenShop();
		}

		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 void OnFocus()
		{
			if (session == null || isBusy)
				return;
			if (EditorApplication.timeSinceStartup - lastFocusRefreshTime < FocusRefreshIntervalSeconds)
				return;

			RefreshAccountQuietly();
		}

		private async void RefreshAccountQuietly()
		{
			lastFocusRefreshTime = EditorApplication.timeSinceStartup;
			try
			{
				userInfo = await connection.GetUserInfo();
				playerUid = userInfo.player_uid;
			}
			catch (SignInRequiredException ex)
			{
				isCodeRequested = false;
				SetStatus(ex.Message, MessageType.Warning);
			}
			catch (Exception ex)
			{
				Debug.LogWarningFormat("Could not update the Avatar SDK Move account info: {0}", ex.Message);
			}
			Repaint();
		}

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

			try
			{
				userInfo = await connection.GetUserInfo();
				playerUid = userInfo.player_uid;

				SetStatus(string.Empty, MessageType.Info);
			}
			catch (SignInRequiredException ex)
			{
				isCodeRequested = false;
				SetStatus(ex.Message, MessageType.Warning);
			}
			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();

			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
			{
				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);

			bool isSignedIn = false;
			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;
				}

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

				session = storedSession;
				playerUid = storedSession.playerUid;
				code = string.Empty;
				isCodeRequested = false;
				isSignedIn = true;

				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();
			}

			if (isSignedIn)
				RefreshAccount();
		}

		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();
		}
	}
}
