/*
 * 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.Utils;

namespace iStatVball3;

/*
 * Represents a collection of matches in a volleyball tournament. Although matches can be grouped into a tournament, they
 * are NOT children of the tournament.
 */
public class Tournament : RootModel
{
	/* Constants */
	public const string CollectionKey = "Tournaments";

	/* Properties */

	// Required
	[FirestoreProperty("Name")] public string Name { get; set; }
	[FirestoreProperty("Description")] public string Description { get; set; }

	// Optional
	[FirestoreProperty("Division")] public string Division { get; set; }
	[FirestoreProperty("Seed")] public int? Seed { get; set; }
	[FirestoreProperty("Result")] public string Result { get; set; }
	[FirestoreProperty("Notes")] public string Notes { get; set; }

	// Record
	[FirestoreProperty("Won")] public int Won { get; set; }
	[FirestoreProperty("Lost")] public int Lost { get; set; }
	[FirestoreProperty("Tied")] public int Tied { get; set; }

	// References (FK)
	[FirestoreProperty("VenueId")] public string VenueId { get; set; }

	// Parent (FK)
	[FirestoreProperty("SeasonId")] public string SeasonId { get; set; }

	/* Ignored */
	public override string ObjectName => Name;

	// Children (pseudo)
	public List<Match> Matches { get; private set; }

	// References
	public Venue Venue { get; set; }

	// Parent
	public Season Season { get; set; }

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

		// Allocate containers
		Matches = [];
	}

	// Tests equality based on unique ID
	public override bool Equals( object obj )
	{
		return (obj is Tournament tournament) && tournament.UniqueId.Equals( UniqueId );
	}

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

	// Returns tournament date suitable for display
	public DateTimeOffset GetDate()
	{
		return (Matches.Count > 0) ? Matches[0].MatchTime : Modified;
	}

	// Determines if matches of this tournament span multiple days
	public bool IsMultiDay()
	{
		return (GroupByDay().Count > 1);
	}

	// Returns string for date range of this tournament
	public string GetDateRange()
	{
		// Group by day
		List<TournamentDay> days = GetTournamentDays();

		int count = days.Count;

		if ( count > 0 )
		{
			DateTimeOffset startDay = days[0].Date;

			// Single day
			if ( count == 1 )
			{
				return DXUtils.MonthFromDate( startDay, true );
			}
			
			DateTimeOffset endDay = days[ count - 1 ].Date;

			// Multi-day show start-end range
			return DXUtils.DateRange( startDay, endDay );
		}

		return null;
	}

	// Returns list of matches for specified day number of tournament
	public List<Match> MatchesForDay( int number )
	{
		List<TournamentDay> days = GetTournamentDays();

		// Return list for specified day
		foreach ( TournamentDay day in days )
		{
			if ( day.Number == number )
			{
				return day.Matches;
			}
		}

		return null;
	}

	// Returns list of tournament days, with matches grouped by date
	public List<TournamentDay> GetTournamentDays()
	{
		var groups = GroupByDay();
		int number = 1;

		List<TournamentDay> days = [];

		// Create each tournament day
		foreach ( var day in groups )
		{
			TournamentDay tournamentDay = new()
			{
				Number = number++,
				Date = day.Key,
				Matches = day.Value,

				Tournament = this
			};

			days.Add( tournamentDay );
		}

		return days;
	}

	// Returns tournament matches grouped by date
	private SortedDictionary<DateTimeOffset,List<Match>> GroupByDay()
	{
		var groups = new SortedDictionary<DateTimeOffset,List<Match>>();

		// Group all matches by date
		foreach ( Match match in Matches )
		{
			// MUST use local time here
			DateTimeOffset key = match.MatchTime.LocalDateTime.Date;

			// New day found
			if ( !groups.TryGetValue( key, out List<Match> value ) )
			{
                value = [];
                groups.Add( key, value);
			}

            value.Add( match );
		}

		return groups;
	}

	/* Populate */

	// Populates all pseudo child Match objects (no db)
	public void Populate()
	{
		Matches = Season.GetMatches( UniqueId );

		// Populate parent
		foreach ( Match match in Matches )
		{
			match.Tournament = this;
		}
	}

	/* Permissions */

	// Determines if user has permission to create Tournaments
	public static bool CanCreate( Season season,User user )
	{
		return user.Level switch
		{
			// Director/coach/stat always can
			User.LevelType.Director or 
			User.LevelType.Coach or 
			User.LevelType.Statistician => !season.IsSample || user.IsAdmin,
			
			// No one else can
			_ => false
		};
	}

	// Determines if user has permission to edit Tournaments
	public static bool CanEdit( User user )
	{
		return user.Level switch
		{
			// Director/coach/stat always can
			User.LevelType.Director or
			User.LevelType.Coach or 
			User.LevelType.Statistician => true,
			
			// No one else can
			_ => false
		};
	}

	// Determines if user has permission to analyze Tournament stats
	public bool CanAnalyze( User user )
	{
		return user.Level switch
		{
			// Media cannot
			User.LevelType.Media => false,
			
			// Statistician only if linked to player
			User.LevelType.Statistician => user.HasPermission( Season ),
			
			// Everyone else can
			_ => true
		};
	}

    /* Analyze */

    // Returns list of all matches in this tournament
    public List<Match> GetMatches()
    {
        List<Match> matches = [];
        List<Match> matchList = Season.GetMatches( Season.UniqueId );

        // Only include matches for this tournament
        foreach ( Match match in matchList )
        {
            if ( (match.Tournament != null) && match.Tournament.Equals( this ) )
            {
                matches.Add( match );
            }
        }

        return matches;
    }

    // Aggregates all data for analyzing scope of this Tournament
    public async Task<DataStats> Aggregate()
	{
		DataStats stats = new();
		List<Match> matches = GetMatches();
	
		// Append stats from all matches within tournament
		foreach ( Match match in matches )
		{
			stats.Add( await match.Aggregate() );
		}
	
		return stats;
	}

	// Aggregates all raw summary data for scope of this Tournament
	public StatSummary AggregateRaw()
	{
		StatSummary summary = new();
		List<Match> matches = GetMatches();
	
		// Aggregate summary data for all matches within Tournament
		foreach ( Match match in matches )
		{
			summary.Add( match.AggregateRaw() );
		}
	
		return summary;
	}

	/* CRUD */

	// Updates won/lost/tied fields for this object (batched)
	public void UpdateRecord( IWriteBatch batch )
	{
		Won = 0; 
		Lost = 0; 
		Tied = 0;

		// Recount all matches
		foreach ( Match match in Season.Matches )
		{
			if ( (match.Tournament != null) && match.Tournament.Equals( this ) )
			{
				if ( match.Won )
				{
					Won++;
				}
				else if ( match.Lost )
				{
					Lost++;
				}
				else if ( match.Tied )
				{
					Tied++;
				}
			}
		}

		// Persist
		Update( batch, "Won", Won );
		Update( batch, "Lost", Lost );
		Update( batch, "Tied", Tied );
	}

	// Performs cascading delete on this Tournament
	public override async Task Delete( bool remove = true )
	{
		// Remove from season first
		foreach ( Match match in Matches )
		{
			Season.Matches.Remove( match );
		}
		
		// Delete (MUST use for loop to avoid enumeration exception)
		for ( int i = (Matches.Count - 1); i >= 0; i-- )
		{
			await Matches[i].Delete();
		}

		// Remove from parent
		if ( remove )
		{
			Season.Tournaments.Remove( this );
		}

		// Delete self
		await base.Delete( remove );
	}

	/* Import */

	// Creates new Tournament in specified season from existing object (batched)
	public static void Import( IWriteBatch batch, Season season, Tournament tournament )
	{
		Tournament newTournament = new()
		{
			// Copy fields from existing object
			Name = tournament.Name,
			Description = tournament.Description,

			Notes = tournament.Notes,

			// Start with existing image
			ImageUrl = tournament.ImageUrl,

			// Connect to this season
			SeasonId = season.UniqueId,
			Season = season
		};

		// Add to parent
		season.Tournaments.Add( newTournament );

		// Connect roots
		newTournament.Connect( batch, tournament );

		// Persist
		newTournament.Create( batch );
	}
}

//
