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

using System.Text.Json.Serialization;
using Plugin.Firebase.Firestore;

using DXLib.Utils;

namespace DXLib.Data.Model;

/*
 * Base class for all Firestore persisted documents. Includes properties and functionality common across all data models.
 */
public abstract class DXModel : IFirestoreObject
{
	/* Constants */
	protected const string ModifiedKey = "Modified";

	/* Properties */

	// Used as Firestore document ID
	[FirestoreDocumentId][FirestoreProperty("UniqueId")] public string UniqueId { get; set; }

	// Timestamps
	[FirestoreProperty("Created")] public DateTimeOffset Created { get; set; }
    [FirestoreProperty("Modified")] public DateTimeOffset Modified { get; set; }
    [FirestoreProperty("Deleted")] public DateTimeOffset? Deleted { get; set; }

	// Ignored
	[JsonIgnore] public string BaseCollectionKey { get; protected init; }
	[JsonIgnore] public virtual string ObjectName => UniqueId;

	/* Methods */
	protected DXModel()
	{
		UniqueId = DXData.Guid();

		DateTimeOffset now = DXUtils.Now();

		Created = now;
		Modified = now;
	}

	/* Firestore */

	// Use current source configuration (default, server, cache)
	public static Source GetSource()
	{
		return Source.Default;
	}

	// Gets Firestore collection for specified key
	public static ICollectionReference GetCollection( string collectionKey )
	{
		return DXData.Firestore.GetCollection( collectionKey );
	}

	// Gets Firestore document for this object
	protected IDocumentReference GetDocument()
	{
		return GetDocument( BaseCollectionKey, UniqueId );
	}

	// Gets Firestore document matching specified unique identifier
	protected static IDocumentReference GetDocument( string collectionKey, string uniqueId )
	{
		ICollectionReference collection = GetCollection( collectionKey );

		if ( collection == null )
		{
			Console.WriteLine( "NULL CONNECTION key:{0} id:{1}", collectionKey, uniqueId );
		}

		return GetCollection( collectionKey ).GetDocument( uniqueId );
	}

	// Gets Firestore sub-collection within this object
	protected ICollectionReference GetSubCollection( string subCollectionKey )
	{
		return GetDocument().GetCollection( subCollectionKey );
	}

	// Gets Firestore document within sub-collection for this object
	protected IDocumentReference GetSubDocument( string collectionKey, string uniqueId )
	{
		return GetDocument( collectionKey, uniqueId ).GetCollection( BaseCollectionKey ).GetDocument( UniqueId );
	}

	// Returns data structure used for writing individual document fields
	protected static Dictionary<object,object> GetData( string field, object value )
	{
		return new Dictionary<object,object> { { field, value } };
	}

	/* Create */

	// Creates a new document for this object
	public virtual async Task Create()
	{
		try
		{
			IDocumentReference doc = GetDocument();

			if ( doc == null )
			{
				Console.WriteLine( "NULL DOC" );
			}

			await GetDocument().SetDataAsync( this );
		}
		catch ( Exception ex )
		{
			DXData.HandleError( "data.create", ex );
		}
	}

	// Creates an individual element in specified array field
	protected async Task CreateElement( string field, object value )
	{
		try
		{
			await GetDocument().UpdateDataAsync( GetData( field, value ) );
		}
		catch ( Exception ex )
		{
			DXData.HandleError( "data.create.element", ex );
		}
	}

	// Creates a new document for this object (BATCHED)
	public virtual void Create( IWriteBatch batch )
	{
		try
		{
			batch.SetData( GetDocument(), this );
		}
		catch ( Exception ex )
		{
			DXData.HandleError( "data.create.batch", ex );
		}
	}

	/* Read */

	// Reads and returns document matching specified unique identifier
	protected static async Task<T> Read<T>( string collectionKey, string uniqueId )
	{
		try
		{
			IDocumentSnapshot<T> snapshot = await GetDocument( collectionKey, uniqueId ).GetDocumentSnapshotAsync<T>( GetSource() );

			// Convert to object
			return snapshot.Data;
		}
        catch ( Exception ex )
        {
            DXData.HandleError( "data.read", ex );
			return default;
        }
    }

    // Reads and returns single document matching specified value for given field
    protected static async Task<T> Read<T>( string collectionKey, string field, object value )
	{
		IEnumerable<T> objects = await ReadList<T>( collectionKey, field, value );

		// Should only be 1 match
		return objects.FirstOrDefault();
    }

	// Reads and returns list of documents matching specified value for field
	protected static async Task<List<T>> ReadList<T>( string collectionKey, string field, object value )
	{
		try
		{
			// Query
			IQuerySnapshot<T> snapshot = await GetCollection( collectionKey ).WhereEqualsTo( field, value ).GetDocumentsAsync<T>( GetSource() );

			return DXData.BuildList( snapshot );
		}
        catch ( Exception ex )
        {
            DXData.HandleError( "data.read.list", ex );
			return null;
        }
    }

    // Reads and returns list of all documents of specified child type
    public async Task<List<T>> ReadChildren<T>( string collectionKey, string field, string orderBy = null )
	{
		try
		{
			IQuery query = GetCollection( collectionKey ).WhereEqualsTo( field, UniqueId );

			// Optional sorting (ascending)
			if ( !string.IsNullOrEmpty( orderBy ) )
			{
				query = query.OrderBy( orderBy, false );
			}

			// Blocking read
			IQuerySnapshot<T> snapshot = await query.GetDocumentsAsync<T>( GetSource() );

			// Support multiple matches
			return DXData.BuildList( snapshot );
        }
        catch ( Exception ex )
        {
            DXData.HandleError( "data.read.children", ex );
			return null;
        }
    }

    // Reads all documents in specified sub-collection within this object
    public async Task<List<T>> ReadSubChildren<T>( string subCollectionKey, string field, string orderBy = null )
	{
		try
		{
			IQuery query = GetDocument().GetCollection( subCollectionKey ).WhereEqualsTo( field, UniqueId );

			// Optional sorting (ascending)
			if ( !string.IsNullOrEmpty( orderBy ) )
			{
				query = query.OrderBy( orderBy, false );
			}

			// Blocking read
			IQuerySnapshot<T> snapshot = await query.GetDocumentsAsync<T>( GetSource() );

			// Support multiple matches
			return DXData.BuildList( snapshot );
        }
        catch ( Exception ex )
		{
			DXData.HandleError( "data.read.subchildren", ex );
			return null;
		}
	}

	/* Update */

	// Updates ALL fields of corresponding document for this object
	public virtual async Task Update()
	{
		try
		{
			Modified = DXUtils.Now();
	
			// Overwrite entire document
			await GetDocument().SetDataAsync( this );
		}
		catch ( Exception ex )
		{
			DXData.HandleError( "data.update", ex );
		}
	}

	// Updates document for this object with new data for specified field
	public virtual async Task Update( string field, object value )
	{
		await Update( GetData( field, value ) );
	}

	// Updates document for this object with new data for specified fields
	public virtual async Task Update( Dictionary<object,object> data )
	{
		try
		{
			// Update modified timestamp
			Modified = DXUtils.Now();
			data.Add( ModifiedKey, Modified );
	
			// Update specified fields
			await GetDocument().UpdateDataAsync( data );
		}
		catch ( Exception ex )
		{
			DXData.HandleError( "data.update.dict", ex );
		}
	}

	// Updates an individual element (deletes then adds) in specified array field
	protected async Task UpdateElement( string field, object value )
	{
		await DeleteElement( field, value );
		await CreateElement( field, value );
	}

	// BATCHED: Updates ALL data for this object
	public virtual void Update( IWriteBatch batch )
	{
		try
		{
			Modified = DXUtils.Now();
	
			// Query document
			IDocumentReference document = GetDocument();
	
			// Overwrite entire document
			batch.SetData( document, this );
		}
		catch ( Exception ex )
		{
			DXData.HandleError( "data.update.batch", ex );
		}
	}

	// BATCHED: Updates specified document field for this object
	public void Update( IWriteBatch batch, string field, object value )
	{
		try
		{
			Dictionary<object,object> data = GetData( field, value );

			// Update modified timestamp
			Modified = DXUtils.Now();
			data.Add( ModifiedKey, Modified );

			// Query document
			IDocumentReference document = GetDocument();

			// Update specified field
			batch.UpdateData( document, data );
		}
		catch ( Exception ex )
		{
			DXData.HandleError( "data.update.batch.field", ex );
		}
	}

	/* Delete */

	// Deletes document for this object (remove from parent?)
	public virtual async Task Delete( bool remove = true )
	{
		try
		{
			await GetDocument().DeleteDocumentAsync();
		}
		catch ( Exception ex )
		{
			DXData.HandleError( "data.delete", ex );
		}
	}

	// Deletes specified sub-document from this object
	protected static async Task Delete( IDocumentReference subdoc )
	{
		try
		{
			await subdoc.DeleteDocumentAsync();
		}
		catch ( Exception ex )
		{
			DXData.HandleError( "data.delete.subdoc", ex );
		}
	}

	// Deletes an individual element from specified array field
	protected async Task DeleteElement( string field, object value )
	{
		try
		{
			IDocumentReference doc = GetDocument();
	
			FieldValue array = FieldValue.ArrayRemove( value );
	
			// Only update array element
			await doc.UpdateDataAsync( GetData( field, array ) );
		}
		catch ( Exception ex )
		{
			DXData.HandleError( "data.delete.field", ex );
		}
	}

	// BATCHED: Deletes document for this object
	public void Delete( IWriteBatch batch )
	{
		try
		{
			batch.DeleteDocument( GetDocument() );
		}
		catch ( Exception ex )
		{
			DXData.HandleError( "data.delete.batch", ex );
		}
	}
}

//
