/* 
 * Copyright (c)2009-2025 DemiVision, LLC. All Rights Reserved. The information 
 * herein is the CONFIDENTIAL and PROPRIETARY information of DemiVision, LLC. 
 */

using Plugin.Firebase.Firestore;

using DXLib.Data;
using DXLib.Data.Model;

using DXLib.Utils;

namespace iStatVball3;

/*
 * Represents a unique end-user of the app. Used for all authentication and permission filtering. Always 1:1 with a
 * Firebase Auth user instance.
 */
public class User : DXImageModel
{
	/* Constants */
	public const string CollectionKey = "Users";

	// Used to support backward compatibility
	public const byte DataVersion = 7;

	// Data version at which Team-Season billing started
	public const byte TeamSeasonVersion = 6;

	// Available permission levels (MUST match Permission.LevelType)
	public enum LevelType
	{
		None = -1,

		Media,
		Player,
		Fan,
		Statistician,
		Coach,
		Director
	};

    /* Properties */

    // Superuser
    [FirestoreProperty("IsAdmin")] public bool IsAdmin { get; set; }

    // Data version
    [FirestoreProperty("Version")] public int? Version { get; set; }

    // Profile
    [FirestoreProperty("Username")] public string Username { get; set; }
    [FirestoreProperty("FirstName")] public string FirstName { get; set; }
    [FirestoreProperty("LastName")] public string LastName { get; set; }

    // Requested permission level
    [FirestoreProperty("Role")] public string Role { get; set; }

    // Granted permission level
    [FirestoreProperty("LevelEnum")] public int LevelEnum { get; set; }

    // Session info
    [FirestoreProperty("LastLogin")] public DateTimeOffset? LastLogin { get; set; }

    // Firebase link
    [FirestoreProperty("AuthId")] public string AuthId { get; set; }

    // Review status
    [FirestoreProperty("Reviewed")] public bool? Reviewed { get; set; }
    [FirestoreProperty("ReviewRequested")] public DateTimeOffset? ReviewRequested { get; set; }

    // All team access permissions for this user (array of maps)
    [FirestoreProperty("Permissions")] public IList<Permission> Permissions { get; set; }

    // Settings follow user across devices (map)
    [FirestoreProperty("Settings")] public Settings Settings { get; set; }

    // Persisted help topic view status (array)
    [FirestoreProperty("Help")] public IList<string> Help { get; set; }

	// Custom analysis dashboards (array)
    [FirestoreProperty("Dashboards")] public IList<CustomDashboard> Dashboards { get; set; }

    // PURCHASE

    // Date free trial started
    [FirestoreProperty("TrialStarted")] public DateTimeOffset? TrialStarted { get; set; }

	// Purchases (array of maps)
    [FirestoreProperty("Purchases")] public IList<Purchase> Purchases { get; set; }

	/* Ignored */
	public string RoleName => DXString.GetLookupValue( "user.role", Role );
	public string FullName => $"{FirstName} {LastName}";

	public LevelType Level { get => (LevelType)LevelEnum; set => LevelEnum = (int)value; }

	public bool IsMedia => Level <= LevelType.Media;
	public bool IsFan => Level == LevelType.Fan;

	// New billing system?
    public bool IsTeamSeason => (Version >= TeamSeasonVersion);

    // Set currently being recorded by user, if any
	public Set RecordingSet { get; set; }
	public bool IsRecording => (RecordingSet != null);

	// Children (pseudo)
	public List<Organization> Organizations { get; private set; }

	/* Methods */
	public User()
	{
		BaseCollectionKey = CollectionKey;

		// Allocate containers
		Permissions = new List<Permission>();
		Help = new List<string>();
		Dashboards = new List<CustomDashboard>();

		// Defaults
		IsAdmin = false;
		Level = LevelType.None;

		// PURCHASE
		Purchases = new List<Purchase>();
	}

	// Tests equality based on unique username
	public override bool Equals( object obj )
	{
		return (obj is User user) && user.Username.Equals( Username );
	}

	// Generates unique hash code (required)
	public override int GetHashCode()
	{
		return UniqueId.GetHashCode();
	}

    // Returns display label for specified user access value
    public static string GetLevelLabel( LevelType level )
	{
		return DXString.GetLookupValue( "user.level", ((int)level).ToString() );
	}

    // Determines number of days since account was originally created
    public int DaysSinceCreated()
    {
        return (int) DateTimeOffset.Now.Subtract( Created ).TotalDays;
    }

    /* Login */

    // Executes all tasks to be performed following successful login
    public async Task Login()
	{
		IWriteBatch batch = DXData.StartBatch();

		// Might be missing Settings if created from web
		if ( Settings == null )
		{
			Settings = new Settings();
			Settings.SetDefaults();

			UpdateSettings( batch );
		}																												DXProfiler.Mark( "Settings" );
																														
		// Create session
		UpdateLogin( batch );
		Session.Create( batch, UniqueId );
																														DXProfiler.Mark( "Session" );
		// Persist
		await DXData.CommitBatch( batch );
																														DXProfiler.Mark( "Persist" );
        // Cache
        DXPreferences.Set( "launch.user", UniqueId );
																														DXProfiler.Mark( "Cache" );
        // Preload object tree root
        await Populate();
																														DXProfiler.Mark( "Populate" );
	}

	// Logs out current user, deletes cached login info
	public static void Logout()
	{
		Shell.CurrentUser = null;

		DXPreferences.Set( "launch.user", null );
		DXPreferences.Set( "launch.path", null );

		DXPreferences.Set( "scope.org", null );
	}

	/* Populate */

	// Populates all accessible child Organization objects
	public async Task Populate( bool deep = false )
	{
		// Sample/debug/owned/permitted
		Organizations = await ReadAllOrganizations();
																			
		// Populate tree down through Season
		foreach ( Organization org in Organizations.Where( org => org != null ) )
		{
			await org.Populate( this, deep );
		}
	}

	// Returns total number of organizations owned/permitted for this user
	public int GetOrganizationCount()
	{
		// Do NOT include sample/debug
		return Organizations.Count( org => org is { IsSample: false, IsDebug: false } );
	}

	// Returns pre-populated organization matching specified identifier
	public Organization GetOrganization( string uniqueId ) { return Organizations.Find( o => o.UniqueId == uniqueId ); }

	// Returns organization matching specified name
	public Organization GetOrganizationByName( string name )
	{
		// Convert to canonical format
		string canonical = DXUtils.ToCanonical( name );

		// Find match
		foreach ( Organization org in Organizations )
		{
			if ( org != null )
			{
				string canonical2 = DXUtils.ToCanonical( org.Name );

				if ( canonical.Equals( canonical2 ) )
				{
					return org;
				}
			}
		}

		// No match
		return null;
	}

	/* Permissions */

	// Returns unique ID of permitted player for analysis/video (null for all)
	public string GetPermission()
	{
		switch ( Level )
		{
			// Stat/Fan/Player have analyze/video access restricted to 1 player
			case LevelType.Statistician:
			case LevelType.Fan:
			case LevelType.Player:
			{
				foreach ( Permission permission in Permissions )
				{
					if ( permission.HasPlayer )
					{
						return permission.EntityId;
					}
				}

				break;
			}
			// NA
			case LevelType.None:
			case LevelType.Media:
			case LevelType.Coach:
			case LevelType.Director:
			default:
				break;
		}

		// Director/Coach can access all players
		return null;
	}

	// Determines if user has -any- Fan/Player permission
	public bool HasPermission()
	{
		return Permissions.Any( permission => permission.HasPlayer );
	}

	// Determines if user has Fan/Player permission within specified Season
	public bool HasPermission( Season season )
	{
		return Permissions.Any( permission => permission.HasPlayer && season.HasPlayer( permission.UniqueId ) );
	}

	// Determines if user has Fan/Player permission for specified Player
	public bool HasPermission( Player player )
	{
		return Permissions.Any( permission => permission.HasPlayer && player.EqualsId( permission.EntityId ) );
	}

	/* CRUD */

	// CREATE

	// Adds specified permission for this user
	public async Task CreatePermission( Permission permission, IWriteBatch batch = null )
	{
		Permissions.Add( permission );

		// Optionally batch update
		if ( batch != null )
		{
			Update( batch );
		}
		else
		{
			await Update( "Permissions", Permissions );
		}
	}

	// READ

	// Reads and returns User matching specified unique identifier
	public static async Task<User> Read( string uniqueId )
	{
		User user = Shell.CurrentUser;

		// IMPORTANT: If fetching current user, must use existing copy
		if ( (user != null) && user.UniqueId.Equals( uniqueId ) )
		{
			return user;
		}

		return await Read<User>( CollectionKey, uniqueId );
	}

	// Reads and returns User matching specified unique username
	public static async Task<User> ReadByUsername( string username )
	{
		User user = Shell.CurrentUser;

		// IMPORTANT: If fetching current user, must use existing copy
		if ( user != null )
		{
			if ( user.Username.Equals( username, StringComparison.CurrentCultureIgnoreCase ) )
			{
				return user;
			}
		}

		// Case-insensitive
		return await Read<User>( CollectionKey, "Username", username );
	}

	// Reads and returns User matching specified Firestore auth identifier
	public static async Task<User> ReadByAuth( string authId )
	{
		return await Read<User>( CollectionKey, "AuthId", authId );
	}

	// Returns list of all Organizations visible to this user (sample, owned, and via permission)
	private async Task<List<Organization>> ReadAllOrganizations()
	{
		List<Organization> list = [];

		// Special DEBUG mode (admin user only)
		if ( IsAdmin )
		{
			list.AddRange( await Organization.ReadDebug() );
		}																												DXProfiler.Mark( "Admin" );
		
		// Sample org(s)
		if ( Settings.GeneralSample )
		{
			list.AddRange( await Organization.ReadSample() );															DXProfiler.Mark( "Sample" );
		}
																				
		// Add all owned organizations
		if ( Level >= LevelType.Coach )
		{
			IEnumerable<Organization> owned = await Organization.ReadOwned( UniqueId );
																														DXProfiler.Mark( "Owned" );
			foreach ( Organization org in owned )
			{
				if ( (org != null) && !list.Contains( org ) )
				{
					list.Add( org );
																														DXProfiler.Mark( $"O:{org.DebugId}" );
				}
			}
		}

		// Add all accessible via permission grant
		await ReadOrganizations( list );

		return list;
	}

	// Returns list of all Organizations user has permission to access
	private async Task ReadOrganizations( List<Organization> list )
	{
		// Permissions are granted by definition
		foreach ( Permission permission in Permissions )
		{
			string orgId = permission.OrganizationId;

			// No need to read if already in list
			if ( !Organization.Contains( list, orgId ) )
			{
				Organization org = await Organization.Read( orgId );

				if ( (org != null) && !list.Contains( org ) )
				{
					list.Add( org );
																														DXProfiler.Mark( $"P:{org.DebugId}" );
				}
			}
		}
	}

	// UPDATE

	// Updates last login date/time for this user (batched)
	public void UpdateLogin( IWriteBatch batch )
	{
		LastLogin = DXUtils.Now();

		Update( batch, "LastLogin", LastLogin );
	}

	// Updates data version for this user
	public async Task UpdateVersion( byte version )
	{
		Version = version;

		await Update( "Version", Version );
	}

	// Updates access level for this user
	public async Task UpdateLevel( LevelType level )
	{
		LevelEnum = (int)level;

		await Update( "LevelEnum", LevelEnum );
	}

	// Updates role for this user
	public async Task UpdateRole( string role )
	{
		Role = role;

		await Update( "Role", Role );
	}

	// Starts free trial period if not already started
	public async Task UpdateTrial( DateTimeOffset started )
	{
		TrialStarted = started;

		await Update( "TrialStarted", TrialStarted );
	}

	// Adds a successfully completed purchase for this user
	public async Task UpdatePurchase( Purchase purchase )
	{
		if ( !Purchases.Contains( purchase ) )
		{
			Purchases.Add( purchase );
		}
	
		await Update( "Purchases", Purchases );
	}

	// Updates entire settings map for this user
	public async Task UpdateSettings()
	{
		await Update( "Settings", Settings );
	}

	// Updates entire settings map for this user (batched)
	public void UpdateSettings( IWriteBatch batch )
	{
		Update( batch, "Settings", Settings );
	}

	// Updates help video topic as having been viewed
	public async Task UpdateHelp( string key )
	{
		if ( !Help.Contains( key ) )
		{
			Help.Add( key );
		}

		await Update( "Help", Help );
	}

	// Marks user as having been prompted for review (only prompted once)
	public async Task UpdateReviewed()
	{
		Reviewed = true;

		await Update( "Reviewed", Reviewed );
	}

	// Persists timestamp of last time user was prompted for review
	public async Task UpdateReviewRequested()
	{
		ReviewRequested = DXUtils.Now();

		await Update( "ReviewRequested", ReviewRequested );
	}

	// Updates entire list of custom dashboard entries
	public async Task UpdateDashboards()
	{
		await Update( "Dashboards", Dashboards );
	}

	// DELETE

	// Deletes permission to specified entity from this user
	public async Task DeletePermission( string entityId, Permission.LevelType level )
	{
		foreach ( Permission permission in Permissions.ToList() )
		{
			if ( permission.EntityId == entityId )
			{
				// Can delete all levels or only specified
				if ( (level == Permission.LevelType.ALL) || (permission.Level == level) )
				{
					await DeleteElement( "Permissions", permission );

					Permissions.Remove( permission );
				}
			}
		}
	}

	// Deletes specified custom dashboard for this user
	public async Task DeleteDashboard( CustomDashboard dashboard )
	{
		Dashboards.Remove( dashboard );
	
		await UpdateDashboards();
	}

	// Fully deletes user account and all associated data
	public async Task DeleteAll()
    {
		// Delete ALL org/team/season data owned by this user
		IEnumerable<Organization> orgs = await Organization.ReadOwned( UniqueId );

		foreach ( Organization org in orgs )
        {
			await org.Populate( this, false );
			await org.Delete();
        }

		// Delete user itself (no children, all maps)
		await base.Delete();

		// Delete auth user/pswd
		await DXAuth.DeleteAccount();
	}
}

//
