/*
 * 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.UI;
using DXLib.UI.Layout;
using DXLib.UI.Container;

using DXLib.UI.Control;
using DXLib.UI.Control.Button;

using DXLib.UI.Form;
using DXLib.UI.Form.Control;

using DXLib.Data;
using DXLib.Utils;

namespace iStatVball3;

/*
 * Implements the internal analytics interface used to display business and usage metrics for the app.
 */
public class AdminAnalytics : DXScroll
{
	/* Constants */
	private const double LabelSize = 14;
	private const double TitleSize = (LabelSize + 1);

	private const double LayoutPad = 20;
	private const double RowHt = (LabelSize + 2);

	// Query keys
	private const string UserKey = "user";
	private const string SessionKey = "session";
	private const string OrgKey = "org";
	private const string SeasonKey = "season";
	private const string SetKey = "set";

	// Query timeframes (days)
	private static readonly Dictionary<string,int> QueryTimes = new()
	{
		{ UserKey, 365 },
		{ SessionKey, 180 },
		{ OrgKey, 365 },
		{ SeasonKey, 0 },
		{ SetKey, 90 }
	};

	/* Fields */
	private readonly DXVerticalLayout dataLayout;

	// Query controls
	private DXSelectorField queryData;
	private DXKeypadField queryTime;
	private DXButton queryBtn;

	// Query data sets
	private List<User> users;
	private List<Session> sessions;
	private List<Organization> orgs;
	private List<Season> seasons;
	private List<Set> sets;

	// Global totals
	private int totalUsersNew;
	private int totalTeams;

	/* Methods */
	public AdminAnalytics()
	{
		BackgroundColor = DXColors.Light4;
		Padding = 22;
		Orientation = ScrollOrientation.Vertical;

		// Main layout
		DXVerticalLayout scrollLayout = new()
		{
			Padding = 0,
			Spacing = 0
		};

		// Query selector/button
		scrollLayout.Add( CreateControls() );

		// Data set specific controls
		dataLayout = new DXVerticalLayout
		{
			Padding = 0,
			Spacing = 0
		};

		scrollLayout.Add( dataLayout );

		Content = scrollLayout;
	}

	// Adds controls for configuring query
	private DXGridLayout CreateControls()
	{
		// Layout
		DXGridLayout layout = new()
		{
			Padding = 0,
			RowSpacing = 0,
			ColumnSpacing = 15
		};

		// Data
		queryData = new DXSelectorField
		{
			Key = "data",
			Title = "analytics.data",
			Items = "analytics.data",
			SelectedItem = UserKey,
			HideClear = true,
			Help = null,

			ControlChanged = OnDataChanged
		};

		queryData.Init();
		queryData.SetState( DXFormControl.ControlState.Normal );

		// Timespan
		queryTime = new DXKeypadField
		{
			Key = "time",
			Title = "analytics.time",
			Number = QueryTimes[ UserKey ],
			MinValue = 1,
			MaxValue = (365 * 10),
			Help = null
		};

		queryTime.Init();
		queryTime.SetState( DXFormControl.ControlState.Normal );

		// Button
		queryBtn = new DXButton
		{
			Text = "QUERY",
			Type = DXButton.ButtonType.Neutral,

			Horizontal = LayoutOptions.Start,
			Vertical = LayoutOptions.Center,

			IsDisabled = false,
			IsSticky = true,

			ButtonWd = 80,
			ButtonTapped = OnQueryTapped
		};

		// 3 columns
		layout.AddStarColumn( 50 );     // 0: data
		layout.AddStarColumn( 20 );     // 1: time
		layout.AddStarColumn( 30 );		// 1: button

		// Add components
		layout.Add( queryData, 0, 0 );
		layout.Add( queryTime, 1, 0 );
		layout.Add( queryBtn, 2, 0 );

		return layout;
	}

	// Returns timestamp for currently configured query timespan
	private DateTimeOffset GetTimespan()
	{
		int days = (int)queryTime.Number!;

		return DateTimeOffset.Now.Subtract( TimeSpan.FromDays( days ) );
	}

	// Clears all controls from data layout
	private void Clear()
	{
		dataLayout.Clear();
	}

	// Resets query button for new query
	private void Reset()
	{
		queryBtn.Reset();
	}

	/* User */

	// Queries and returns all users active within timespan
	private async Task QueryUser()
	{
		// Only include logins within last year
		IQuerySnapshot<User> snapshot = await DXData.Firestore.GetCollection( User.CollectionKey )
													.WhereGreaterThan( "LastLogin", GetTimespan() )
													.GetDocumentsAsync<User>();
		var userList = DXData.BuildList( snapshot );
		users = [];

		// Exclude those with no team access
		foreach ( User user in userList )
		{
			if ( user.Permissions is { Count: > 0 } )
			{
				users.Add( user );
			}
		}
	}

	// Shows all analytics categories derived from user data
	private void ShowUser()
	{
		ShowBilling();
		ShowTeam();
		ShowLevel();
		ShowPurchase();
	}

	// Displays breakdown of users by new/old billing (data version)
	private void ShowBilling()
	{
		List<User> newUsers = [];
		List<User> oldUsers = [];

		// Count new/old
		foreach ( User user in users )
		{
			if ( user.IsTeamSeason || PurchaseEngine.HasPurchased( user ) )
			{
				newUsers.Add( user );
			}
			else
			{
				oldUsers.Add( user );
			}
		}

		totalUsersNew = newUsers.Count;

		DXGridLayout table = CreateTable( "VERSION" );

		int newCount = newUsers.Count;
		int oldCount = oldUsers.Count;

		int total = (newCount + oldCount);

		// Category, Count, %
		AddPercentRow( table, "New", newCount, total );
		AddPercentRow( table, "Old", oldCount, total );

		// Total
		AddPercentRow( table, "Total", total, total, true );
	}

	// Shows breakdown of users by number of teams
	private void ShowTeam()
	{
		int maxTeams = 5;
		int count = (maxTeams + 1);

		int[] counts = new int[ count ];

		totalTeams = 0;

		// Count teams from each user
		foreach ( User user in users )
		{
			int teams = user.Permissions.Count;
			int index = Math.Min( teams, maxTeams );

			counts[ index ]++;

			// Accumulate total
			totalTeams += teams;
		}

		DXGridLayout table = CreateTable( "TEAM" );

		int totalUsers = users.Count;

		// Teams, Count, Percent
		for ( int i = 1; i < count; i++ )
		{
			string header = (i == maxTeams) ? $"{maxTeams}+" : i.ToString();

			AddPercentRow( table, header, counts[i], totalUsers );
		}

		// Total
		AddPercentRow( table, "Total", totalUsers, totalUsers, true );

		float teamsPerUser = (totalTeams / (float)totalUsers);

		// Total Teams, Teams/User
		AddSpacerRow( table );
		AddValueRow( table, "Teams", totalTeams );
		AddValueRow( table, "Per User", teamsPerUser );
	}

	// Shows breakdown of users by access level
	private void ShowLevel()
	{
		int maxLevel = (int)User.LevelType.Director;
		int count = (maxLevel + 1);

		int[] counts = new int[ count ];
		int total = 0;

		// Count users at each level
		foreach ( User user in users )
		{
			int index = Math.Max( user.LevelEnum, 0 );

			counts[ index ]++;
			total++;
		}

		DXGridLayout table = CreateTable( "LEVEL" );

		List<DXItem> items = DXString.GetLookupList( "user.role" );

		// Level, Count, Percent
		for ( int i = 0; i <= maxLevel; i++ )
		{
			string header = items[i].Value;
			int index = (maxLevel - i);

			AddPercentRow( table, header, counts[ index ], total );
		}

		// Total
		AddPercentRow( table, "Total", total, total, true );
	}

	// Shows breakdown of users by Team-Seasons purchased
	private void ShowPurchase()
	{
		int max = 5;
		int[] counts = new int[ max + 1 ];

		// Count Team-Seasons from each user
		foreach ( User user in users )
		{
			// New billing only
			if ( user.IsTeamSeason )
			{
				int purchases = PurchaseEngine.GetPurchased( user );
				int index = Math.Min( purchases, max );
				
				// Accumulate totals
				counts[ index ]++;
			}
		}

		DXGridLayout table = CreateTable( "TEAM-SEASON" );

		int total = totalUsersNew;

		// Purchases, Count, Percent
		for ( int i = 0; i < (max + 1); i++ )
		{
			string header = (i == max) ? $"{max}+" : i.ToString();

			AddPercentRow( table, header, counts[i], total );
		}

		// Total
		AddPercentRow( table, "Total", total, total, true );
	}

	/* Session */

	// Queries all data required to display session related analytics
	private async Task QuerySession()
	{
		// Only include sessions within timespan
		IQuerySnapshot<Session> snapshot = await DXData.Firestore.GetCollection( Session.CollectionKey )
																 .WhereGreaterThan( "Created", GetTimespan() )
																 .GetDocumentsAsync<Session>();
		sessions = DXData.BuildList( snapshot );
	}

	// Shows all data tables derived from session query
	private void ShowSession()
	{
		ShowPlatform();
		ShowIdiom();
		ShowVersion();
	}

	// Shows breakdown of sessions by platform (iOS/Android)
	private void ShowPlatform()
	{
		int iosCount = 0;
		int droidCount = 0;
		int otherCount = 0;

		int total = 0;

		// Group by platform
		foreach ( Session session in sessions )
		{
			switch ( session.Platform )
			{
				case Session.Apple: iosCount++; break;
				case Session.Android: droidCount++; break;
				default: otherCount++; break;
			}

			// Accumulate total
			total++;
		}

		DXGridLayout table = CreateTable( "PLATFORM" );

		// Platform, Count, Percent
		AddPercentRow( table, "iOS", iosCount, total );
		AddPercentRow( table, "Android", droidCount, total );
		AddPercentRow( table, "Other", otherCount, total );

		// Total
		AddPercentRow( table, "Total", total, total, true );
	}

	// Shows breakdown of sessions by idiom (tablet/phone)
	private void ShowIdiom()
	{
		int tabletCount = 0;
		int phoneCount = 0;
		int otherCount = 0;

		int total = 0;

		// Group by idiom
		foreach ( Session session in sessions )
		{
			switch ( session.Idiom )
			{
				case Session.Tablet: tabletCount++; break;
				case Session.Phone: phoneCount++; break;
				default: otherCount++; break;
			}

			// Accumulate total
			total++;
		}

		DXGridLayout table = CreateTable( "IDIOM" );

		// Idiom, Count, Percent
		AddPercentRow( table, "Tablet", tabletCount, total );
		AddPercentRow( table, "Phone", phoneCount, total );
		AddPercentRow( table, "Other", otherCount, total );

		// Total
		AddPercentRow( table, "Total", total, total, true );
	}

	// Shows breakdown of sessions by app version
	private void ShowVersion()
	{
		// Some sessions have no version
		SortedDictionary<int,int> versionTable = new() { { 0, 0 } };

		int total = 0;

		// Groups sessions by version
		foreach ( Session session in sessions )
		{
			TimeSpan timeSpan = DateTimeOffset.Now.Subtract( session.Created );

			// Only look at most recent month
			if ( timeSpan.Days < 30 )
			{
				string versionStr = session.AppVersion;

				// Empty version
				if ( string.IsNullOrEmpty( versionStr ) )
				{
					versionTable[ 0 ]++;
				}
				else
				{
					int version = DXUtils.VersionToInt( versionStr );

					// Create table entry
					versionTable.TryAdd( version, 0 );
					versionTable[ version ]++;
				}

				total++;
			}
		}

		DXGridLayout table = CreateTable( "VERSION" );

		// Create row for each version (descending order)
		foreach ( int key in versionTable.Keys.Reverse() )
		{
			string header = DXUtils.IntToVersion( key );
			int count = versionTable[ key ];

			AddPercentRow( table, header, count, total );
		}

		// Total
		AddPercentRow( table, "Total", total, total, true );
	}

	/* Organization */

	// Queries all data required to display organization related analytics
	private async Task QueryOrg()
	{
		IQuerySnapshot<Organization> snapshot = await DXData.Firestore.GetCollection( Organization.CollectionKey )
																	  .GetDocumentsAsync<Organization>();
		orgs = DXData.BuildList( snapshot );
	}

	// Shows all data tables derived from organization query
	private void ShowOrg()
	{
		ShowType();
	}

	// Shows breakdown of organizations by type
	private void ShowType()
	{
		List<DXItem> items = DXString.GetLookupList( "organization.type" );
		Dictionary<string,int> counts = new();

		int total = 0;

		// Group by type
		foreach ( Organization org in orgs )
		{
			string key = org.Type;

			if ( key != null )
			{
				counts.TryAdd( key, 0 );
				counts[ key ]++;
			}

			total++;
		}

		DXGridLayout table = CreateTable( "TYPE" );

		// Add row for each type
		foreach ( DXItem item in items )
		{
			int count = counts[ item.Key ];

			AddPercentRow( table, item.Value, count, total );
		}

		// Total
		AddPercentRow( table, "Total", total, total, true );
	}

	/* Season */

	// Queries all data required to display season related analytics
	private async Task QuerySeason()
	{
		IQuerySnapshot<Season> snapshot = await DXData.Firestore.GetCollection( Season.CollectionKey )
																.GetDocumentsAsync<Season>();
		seasons = DXData.BuildList( snapshot );
	}

	// Shows all data tables derived from season query
	private void ShowSeason()
	{
		ShowYear();
		ShowMatch();
	}

	// Shows breakdown of seasons by year started
	private void ShowYear()
	{
		SortedDictionary<int,int> yearTable = new();

		int total = 0;

		// Groups seasons by year
		foreach ( Season season in seasons )
		{
			int year = season.StartDate.Year;

			// Create table entry
			yearTable.TryAdd( year, 0 );
			
			// Accumulate totals
			yearTable[ year ]++;
			total++;
		}

		DXGridLayout table = CreateTable( "SEASON" );

		// Create row for each year (descending)
		foreach ( int key in yearTable.Keys.Reverse() )
		{
			int count = yearTable[ key ];

			AddPercentRow( table, key.ToString(), count, total );	
		}

		// Total
		AddPercentRow( table, "Total", total, total, true );

		// Seasons/Team, Seasons/User
		if ( users != null )
		{
			float seasonsPerTeam = (total / (float)totalTeams);
			float seasonsPerUser = (total / (float)users.Count);

			AddSpacerRow( table );
			AddValueRow( table, "Per Team", seasonsPerTeam );
			AddValueRow( table, "Per User", seasonsPerUser );
		}
	}

	// Shows breakdown of matches by result
	private void ShowMatch()
	{
		// Layout
		DXGridLayout table = CreateTable( "MATCH", 4 );

		int won = 0;
		int lost = 0;
		int tied = 0;

		int total = 0;

		// Accumulate result totals
		foreach ( Season season in seasons )
		{
			won += season.Won;
			lost += season.Lost;
			tied += season.Tied;

			total += season.MatchCount;
		}

		table.AddFixedRow( RowHt );

		// Add headers
		AddLabel( table, "Won", 0, true, true );
		AddLabel( table, "Lost", 1, true, true );
		AddLabel( table, "Tied", 2, true, true );
		AddLabel( table, "Total", 3, true, true );

		table.AddFixedRow( RowHt );

		// Add counts
		AddLabel( table, GetInt( won ), 0, false, true );
		AddLabel( table, GetInt( lost ), 1, false, true );
		AddLabel( table, GetInt( tied ), 2, false, true );
		AddLabel( table, GetInt( total ), 3, true, true );
	}

	/* Set */

	// Queries all data required for set based analytics
	private async Task QuerySet()
	{
		// Only include within timespan (restrict to first set only for performance)
		IQuerySnapshot<Set> snapshot = await DXData.Firestore.GetCollection( Set.CollectionKey )
															 .WhereGreaterThan( "Created", GetTimespan() )
															 .GetDocumentsAsync<Set>();
		sets = DXData.BuildList( snapshot );
	}

	// Shows all data tables derived from set query
	private void ShowSet()
	{
		ShowEngine();
		ShowRally();
		ShowConnection();
		ShowMisc();
	}

	// Shows breakdown of sets by record engine
	private void ShowEngine()
	{
		int rallyCount = 0;
		int legacyCount = 0;

		int total = 0;

		// Group by engine
		foreach ( Set set in sets )
		{
			if ( set.Legacy )
			{
				legacyCount++;
			}
			else
			{
				rallyCount++;
			}

			// Accumulate total
			total++;
		}

		DXGridLayout table = CreateTable( "ENGINE" );

		// RallyFlow, Legacy
		AddPercentRow( table, "RallyFlow", rallyCount, total );
		AddPercentRow( table, "Legacy", legacyCount, total );

		// Total
		AddPercentRow( table, "Total", total, total, true );
	}

	// Shows breakdown of sets by RallyFlow level
	private void ShowRally()
	{
		string[] keys = [ "low", "med", "high", "max", "custom" ];

		Dictionary<string,int> counts = new();

		// Build LUT
		foreach ( string key in keys )
		{
			counts.Add( key, 0 );
		}

		int total = 0;

		// Count sets at each level
		foreach ( Set set in sets )
		{
			if ( !set.Legacy )
			{
				string level = set.RallyLevel;

				counts[ level ]++;
				total++;
			}
		}

		DXGridLayout table = CreateTable( "LEVEL" );

		// Level, Count, Percent
		foreach ( string key in keys )
		{
			AddPercentRow( table, counts, key, total );
		}

		// Total
		AddPercentRow( table, "Total", total, total, true );
	}

	// Shows breakdown of sets by connection status
	private void ShowConnection()
	{
		int connected = 0;
		int notConnected = 0;
		int unknown = 0;

		int total = 0;

		// Group by connection status
		foreach ( Set set in sets )
		{
			if ( set.Connected == null )
			{
				unknown++;
			}
			else if ( set.Connected == true )
			{
				connected++;
			}
			else
			{
				notConnected++;
			}

			// Accumulate total
			total++;
		}

		DXGridLayout table = CreateTable( "CONNECTION" );

		// Connected, Not Connected, Unknown
		AddPercentRow( table, "Connected", connected, total );
		AddPercentRow( table, "Not Connected", notConnected, total );
		AddPercentRow( table, "Unknown", unknown, total );

		// Total
		AddPercentRow( table, "Total", total, total, true );
	}

	// Shows breakdown of imported and video synced sets
	private void ShowMisc()
	{
		int importCount = 0;
		int syncCount = 0;
		int archiveCount = 0;

		int total = 0;

		// Accumulate misc totals
		foreach ( Set set in sets )
		{
			// Import
			if ( set.Imported )
			{
				importCount++;
			}

			// Sync
			if ( set.Synced )
			{
				syncCount++;
			}

			// Archived
			if ( set.Deleted != null )
			{
				archiveCount++;
			}

			total++;
		}

		DXGridLayout table = CreateTable( "MISC" );

		// Import, Sync
		AddPercentRow( table, "Imported", importCount, total, false );
		AddPercentRow( table, "Synced", syncCount, total, false );
		AddPercentRow( table, "Archived", archiveCount, total, false );
	}

	/* Utils */

	// Returns specified value as comma delimited integer string
	private static string GetInt( int value )
	{
		return value.ToString( "N0" );
	}

	// Returns specified value as floating point string (x.x)
	private static string GetFloat( float value )
	{
		return value.ToString( "N1" );
	}

	// Returns specified value as percent string (x.x%)
	private static string GetPercent( float value )
	{
		return value.ToString( "P1" );
	}

	// Adds a vertical spacer to specified table
	private static void AddSpacerRow( DXGridLayout layout )
	{
		layout.AddFixedRow( RowHt );
	}

	// Adds a bold title row to specified table
	private static void AddTitleRow( DXGridLayout layout, string title )
	{
		layout.AddFixedRow( RowHt );

		DXLabel label = new()
		{
			Text = title,
			Font = DXFonts.RobotoBold,
			FontSize = TitleSize,
			HAlign = TextAlignment.Start
		};

		// Full width
		layout.Add( label, 0, layout.RowIndex, layout.ColumnCount, 1 );
	}

	// Adds integer value row to specified table
	private static void AddValueRow( DXGridLayout layout, string header, int value, bool bold = true )
	{
		layout.AddFixedRow( RowHt );

		// Add components
		AddLabel( layout, header, 0, bold );
		AddLabel( layout, GetInt( value ), 1, bold, true );
	}

	// Adds floating point value row to specified table
	private static void AddValueRow( DXGridLayout layout, string header, float value, bool bold = true )
	{
		layout.AddFixedRow( RowHt );

		// Add components
		AddLabel( layout, header, 0, bold );
		AddLabel( layout, GetFloat( value ), 1, bold, true );
	}

	// Adds row to specified table showing count and percent of total
	private static void AddPercentRow( DXGridLayout layout, string header, int value, int total, bool bold = false )
	{
		layout.AddFixedRow( RowHt );

		float percent = (value / (float)total);

		// Add components
		AddLabel( layout, header, 0, bold );
		AddLabel( layout, GetInt( value ), 1, bold, true );
		AddLabel( layout, GetPercent( percent ), 2, bold, true );
	}

	// Adds row to specified table showing count and percent of total
	private static void AddPercentRow( DXGridLayout layout, Dictionary<string,int> counts, string key, int total )
	{
		string header = DXUtils.ToFirstUpper( key );
		int count = counts[ key ];

		AddPercentRow( layout, header, count, total );
	}

	// Adds label for individual cell to specified grid layout
	private static void AddLabel( DXGridLayout grid, string text, int column, bool bold, bool center = false )
	{
		DXLabel label = new()
		{
			Text = text,
			Font = bold ? DXFonts.RobotoBold : DXFonts.Roboto,
			FontSize = LabelSize,
			HAlign = center ? TextAlignment.Center : TextAlignment.Start
		};

		grid.Add( label, column, grid.RowIndex );
	}

	// Creates base table and columns for analytics display
	private DXGridLayout CreateTable( string title, int colCount = 3 )
	{
		// Layout
		DXGridLayout table = new()
		{
			Padding = DXUtils.Top( LayoutPad ),
			RowSpacing = 8,
			ColumnSpacing = 0
		};

		switch ( colCount )
		{
			// 2 columns
			case 2:
			{
				table.AddStarColumn( 40 );       // 0: header
				table.AddStarColumn( 60 );       // 1: value
				break;
			}
			// 3 columns
			case 3:
			{
				table.AddStarColumn( 40 );       // 0: header
				table.AddStarColumn( 30 );       // 1: value
				table.AddStarColumn( 30 );       // 2: value2
				break;
			}
			// 4 columns
			case 4:
			{
				table.AddStarColumn( 25 );       // 0: value1
				table.AddStarColumn( 25 );       // 1: value2
				table.AddStarColumn( 25 );       // 2: value3
				table.AddStarColumn( 25 );       // 3: value4
				break;
			}
		}

		// Title
		if ( title != null )
		{
			AddTitleRow( table, title );
		}

		dataLayout.Add( table );

		return table;
	}

	/* Event Callbacks */

	// User changed query data set
	private void OnDataChanged()
	{
		string key = queryData.GetString();

		// Set default timespan based on data set
		queryTime.Number = QueryTimes[ key ];
		queryTime.IsDisabled = (key == SeasonKey);
	}

	// User tapped button to begin analytics query
	private async void OnQueryTapped( object sender )
	{
		DXSpinner.Start();

		Clear();

		string data = queryData.GetString();

		switch ( data )
		{
			// User
			case UserKey:
			{
				await QueryUser();
				ShowUser();
				break;
			}
			// Session
			case SessionKey:
			{
				await QuerySession();
				ShowSession();
				break;
			}
			// Organization
			case OrgKey:
			{
				await QueryOrg();
				ShowOrg();
				break;
			}
			// Season
			case SeasonKey:
			{
				await QuerySeason();
				ShowSeason();
				break;
			}
			// Set
			case SetKey:
			{
				await QuerySet();
				ShowSet();
				break;
			}
		}

		Reset();

		DXSpinner.Stop();
	}
}

//
