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

using DXLib.UI.Control;
using DXLib.UI.Control.List;
using DXLib.UI.Control.List.Template;

using DXLib.Data;
using DXLib.Utils;

namespace iStatVball3;

/*
 * Provides functionality for restoring a match, set, or stats from local the MonkeyBarrel cache. The data could either
 * have been deleted or never correctly persisted to Firestore.
 */
public class SeasonRestorer
{
    /* Constants */

    // All possible restore types
    private const string MatchKey = "match";
    private const string SetKey = "set";
    private const string StatsKey = "stats";
    private const string PartialKey = "partial";

    // Type colors
    private static readonly Dictionary<string,Color> Colors = new(){ { MatchKey, DXColors.Negative },
																	 { SetKey, DXColors.Warn },
																	 { StatsKey, DXColors.Accent5 },
																	 { PartialKey, DXColors.Accent4 } };
	/* Fields */

    // UI
    private DXPopup popup;

	// External refs
	private readonly SeasonForm form;
	private readonly Season season;

	// List data
    private List<DXListMenuItem> items;

    /* Methods */
    public SeasonRestorer( SeasonForm form, Season season )
	{
		this.form = form;
		this.season = season;
	}

	// Prompts user to start restore process
	public void Start()
	{
		DXAlert.ShowOkCancel( "restore.title", "restore.prompt", OnStartConfirmed, OnDone );
	}

    /* List */
     
    // Create list of restorable matches
    private async Task BuildList()
	{
        items = [];

        // Get list of all cached objects
        var keys = DXData.GetCacheKeys();
		
		// Scan each object
		foreach ( string key in keys )
		{
			Match cachedMatch = GetMatch( key );

			// Cached match
			if ( cachedMatch != null )
			{
				// Do NOT populate sets here
                cachedMatch.Populate( season, false );

				// Db lookup
                Match match = season.GetMatch( cachedMatch.UniqueId );

                // Match missing
                if ( match == null )
				{
					AddItem( cachedMatch, MatchKey );
                }
				else
				{
                    match.Populate( season, true );

                    // At least 1 set missing
                    if ( match.SetCount != cachedMatch.SetCount )
					{
						AddItem( cachedMatch, SetKey );
					}
					else
					{
						// Check sets
						foreach ( Set set in cachedMatch.Sets )
						{
							set.Populate( cachedMatch );

							string setId = set.UniqueId;

							// Read cloud stats (ignore exceptions for file not found)
							SetData stats = await SetData.ReadCloud( setId, false );

							// Set missing ALL stats
							if ( (stats == null) || stats.IsEmpty )
							{
								AddItem( cachedMatch, StatsKey );
								break;
							}

                            // Read local stats
                            SetData cachedStats = SetData.ReadLocal( setId );

                            // Set missing some stats
                            if ( (cachedStats != null) && (cachedStats.Count > stats.Count) )
							{
                                AddItem( cachedMatch, PartialKey );
                                break;
                            }
                        }
					}
				}
            }
		}

		// Sort chronologically
		items = items.OrderByDescending( i => i.Date ).ToList();
	}

    // Returns cached match matching specified key
    private static Match GetMatch( string key )
    {
	    return key.StartsWith( MatchKey ) ? DXData.ReadCache<Match>( key ) : null;
    }

	// Adds list item for specified cached match
    private void AddItem( Match match, string type )
    {
		DateTimeOffset date = match.MatchTime;

		string dateStr = DXUtils.LabelFromDate( date );
		string timeStr = DXUtils.TimeFromDate( date );

		// 'mm/dd/yy hh:mm'
		string value = $"{dateStr} {timeStr}";

		// ' - Foo opponent'
		if ( !match.IsAnonOpponent )
		{
            string opponentStr = match.GetOpponentName();

            value += $" - {opponentStr}";
		}

		// Create item
        DXListMenuItem item = new()
        {
            Key = match.UniqueId,
			Date = match.MatchTime,
            Value = value,
            SubValue = type,
            IconColor = Colors[ type ],
            Data = match
        };

        items.Add( item );
    }

	// Creates and displays list of recoverable matches in non-modal popup
	private void ShowList()
	{
		// List view
        DXListView listView = new()
        {
            ItemsSource = items,
            ItemSpacing = 1,

            RowHeight = 40,
            Mode = DXListView.SelectMode.Single,

            ItemTemplate = GetTemplate( false ),
            SelectedItemTemplate = GetTemplate( true ),

            ItemSelected = OnMatchSelected,
        };

		// Non-modal popup
        popup = new DXPopup
        {
            IsModal = false,
			Title = DXString.Get( "restore.title" ),

			ViewWidth = 350,
			ViewHeight = 400,

			PopupClosed = OnDone
		};

		// Show
		popup.SetContent( listView );
		popup.ShowFromView( form.CustomFooterBtn );
	}

    // Returns (un)selected list template
    private static DataTemplate GetTemplate( bool selected )
    {
		return new DataTemplate( () => new DXListIconTemplate( "match", selected ) );
    }

    /* Restore */

    // Restores specified match from local cache
    private async Task RestoreMatch( Match cachedMatch, string type )
    {
        DXSpinner.Start();

        switch ( type )
        {
            // Missing entire match, create new first
            case MatchKey:
            {
                await CreateMatch( cachedMatch );
                break;
            }
            // Missing entire set(s), delete everything, then create new
            case SetKey:
            {
                Match match = season.GetMatch( cachedMatch.UniqueId );
                await match.Delete( false );

                await CreateMatch( cachedMatch );
                break;
            }
        }

        // Restore stats for each set
        await RestoreStats( cachedMatch );

        OnDone();

        // Success!
        DXAlert.ShowOk( "restore.title", "restore.success" );
    }

    // Creates new match restored from cached data
    private async Task CreateMatch( Match cachedMatch )
    {
        // Do NOT populate sets here
        cachedMatch.Populate( season, false );

        // Populate each underlying set
        foreach ( Set set in cachedMatch.Sets )
        {
            set.Populate( cachedMatch );
        }

        IWriteBatch batch = DXData.StartBatch();

        // Create Match in Firestore
        cachedMatch.Create( batch );

        // Create Set(s) in Firestore
        foreach ( Set set in cachedMatch.Sets )
        {
            set.Create( batch );
        }

        // Add to parent(s)
        cachedMatch.Tournament?.Matches.Add( cachedMatch );
        season.Matches.Add( cachedMatch );

        // Persist
        await DXData.CommitBatch( batch );

        // Update win/loss records (internally batched)
        await cachedMatch.UpdateAllResults();

        // Refresh season data
        await season.Resync();
    }

    // Uses local cache to restore any stats missing from cloud
    private static async Task RestoreStats( Match cachedMatch )
    {
        // Each set independently eligible for restore
        foreach ( Set set in cachedMatch.Sets )
        {
            // MUST populate here
            set.Populate( cachedMatch );

            string setId = set.UniqueId;

            // Read local cache
            SetData cachedStats = SetData.ReadLocal( setId );

            // Only applicable with cached stats
            if ( cachedStats is { IsEmpty: false } )
            {
                // Read cloud stats (ignore exceptions for file not found)
                SetData stats = await SetData.ReadCloud( setId, false );

                // Only applicable if more stats in cache than cloud
                if ( (stats == null) || (cachedStats.Count > stats.Count) )
                {
                    // Replace with cached
                    set.StatCache = cachedStats.Stats.ToList();

                    // Re-cache everything
                    await set.WriteCaches( true );
                }
            }
        }
    }

    /* Event Callbacks */

    // User confirmed start of restore process
    private async void OnStartConfirmed()
	{
		DXSpinner.Start();

		// MUST refresh data first
		await season.Resync();

		// Build list of restorable matches
		await BuildList();

        // Only proceed if at least 1 match eligible for restore
        if ( items.Count > 0 )
		{
			ShowList();
		}
		else
		{
            DXAlert.ShowNeutral( "restore.title", "restore.empty", OnDone );
        }
    }

	// User selected match to restore
	private void OnMatchSelected( object obj )
	{
		if ( obj is DXListMenuItem item )
		{
			popup.Hide();
			
			// Extract data
			Match match = item.Data as Match;
			string type = item.SubValue;
			
			DXAlert.ShowOkCancel( "restore.title", "restore.create", item.Value, Restore, OnDone );
			
			// User confirmed restore
			async void Restore()
			{
				await RestoreMatch( match, type );
			}
		}
	}

    // Performs cleanup after completion or cancellation of restore
    private void OnDone()
	{
		form.CustomFooterBtn.Reset();

		DXSpinner.Stop();
	}
}

//
