using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Kyoo.Models;
using Kyoo.Models.Exceptions;
namespace Kyoo.Controllers
{
	/// 
	/// Information about the pagination. How many items should be displayed and where to start.
	/// 
	public readonly struct Pagination
	{
		/// 
		/// The count of items to return.
		/// 
		public int Count { get; }
		/// 
		/// Where to start? Using the given sort
		/// 
		public int AfterID { get; }
		/// 
		/// Create a new  instance.
		/// 
		/// Set the  value
		/// Set the  value. If not specified, it will start from the start
		public Pagination(int count, int afterID = 0)
		{
			Count = count;
			AfterID = afterID;
		}
		
		/// 
		/// Implicitly create a new pagination from a limit number.
		/// 
		/// Set the  value
		/// A new  instance
		public static implicit operator Pagination(int limit) => new(limit);
	}
	/// 
	/// Information about how a query should be sorted. What factor should decide the sort and in which order.
	/// 
	/// For witch type this sort applies
	public readonly struct Sort
	{
		/// 
		/// The sort key. This member will be used to sort the results.
		/// 
		public Expression> Key { get; }
		/// 
		/// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendant order.
		/// 
		public bool Descendant { get; }
		
		/// 
		/// Create a new  instance.
		/// 
		/// The sort key given. It is assigned to .
		/// Should this be in descendant order? The default is false.
		/// If the given key is not a member.
		public Sort(Expression> key, bool descendant = false)
		{
			Key = key;
			Descendant = descendant;
			
			if (!Utility.IsPropertyExpression(Key))
				throw new ArgumentException("The given sort key is not valid.");
		}
		/// 
		/// Create a new  instance from a key's name (case insensitive).
		/// 
		/// A key name with an optional order specifier. Format: "key:asc", "key:desc" or "key".
		/// An invalid key or sort specifier as been given.
		public Sort(string sortBy)
		{
			if (string.IsNullOrEmpty(sortBy))
			{
				Key = null;
				Descendant = false;
				return;
			}
			
			string key = sortBy.Contains(':') ? sortBy[..sortBy.IndexOf(':')] : sortBy;
			string order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null;
			ParameterExpression param = Expression.Parameter(typeof(T), "x");
			MemberExpression property = Expression.Property(param, key);
			Key = property.Type.IsValueType
				? Expression.Lambda>(Expression.Convert(property, typeof(object)), param)
				: Expression.Lambda>(property, param);
			Descendant = order switch
			{
				"desc" => true,
				"asc" => false,
				null => false,
				_ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.")
			};
		}
	}
	/// 
	/// A base class for repositories. Every service implementing this will be handled by the .
	/// 
	public interface IBaseRepository
	{
		/// 
		/// The type for witch this repository is responsible or null if non applicable.
		/// 
		Type RepositoryType { get; }
	}
	
	/// 
	/// A common repository for every resources.
	/// 
	/// The resource's type that this repository manage.
	public interface IRepository : IBaseRepository where T : class, IResource
	{
		/// 
		/// Get a resource from it's ID.
		/// 
		/// The id of the resource
		/// If the item could not be found.
		/// The resource found
		Task Get(int id);
		/// 
		/// Get a resource from it's slug.
		/// 
		/// The slug of the resource
		/// If the item could not be found.
		/// The resource found
		Task Get(string slug);
		/// 
		/// Get the first resource that match the predicate.
		/// 
		/// A predicate to filter the resource.
		/// If the item could not be found.
		/// The resource found
		Task Get(Expression> where);
		
		/// 
		/// Get a resource from it's ID or null if it is not found.
		/// 
		/// The id of the resource
		/// The resource found
		Task GetOrDefault(int id);
		/// 
		/// Get a resource from it's slug or null if it is not found.
		/// 
		/// The slug of the resource
		/// The resource found
		Task GetOrDefault(string slug);
		/// 
		/// Get the first resource that match the predicate or null if it is not found.
		/// 
		/// A predicate to filter the resource.
		/// The resource found
		Task GetOrDefault(Expression> where);
		
		/// 
		/// Search for resources.
		/// 
		/// The query string.
		/// A list of resources found
		Task> Search(string query);
		
		/// 
		/// Get every resources that match all filters
		/// 
		/// A filter predicate
		/// Sort information about the query (sort by, sort order)
		/// How pagination should be done (where to start and how many to return)
		/// A list of resources that match every filters
		Task> GetAll(Expression> where = null, 
			Sort sort = default,
			Pagination limit = default);
		/// 
		/// Get every resources that match all filters
		/// 
		/// A filter predicate
		/// A sort by predicate. The order is ascending.
		/// How pagination should be done (where to start and how many to return)
		/// A list of resources that match every filters
		Task> GetAll([Optional] Expression> where,
			Expression> sort,
			Pagination limit = default
		) => GetAll(where, new Sort(sort), limit);
		/// 
		/// Get the number of resources that match the filter's predicate.
		/// 
		/// A filter predicate
		/// How many resources matched that filter
		Task GetCount(Expression> where = null);
		
		
		/// 
		/// Create a new resource.
		/// 
		/// The item to register
		/// The resource registers and completed by database's information (related items & so on)
		Task Create([NotNull] T obj);
		
		/// 
		/// Create a new resource if it does not exist already. If it does, the existing value is returned instead.
		/// 
		/// The object to create
		/// The newly created item or the existing value if it existed.
		Task CreateIfNotExists([NotNull] T obj);
		
		/// 
		/// Edit a resource
		/// 
		/// The resource to edit, it's ID can't change.
		/// Should old properties of the resource be discarded or should null values considered as not changed?
		/// If the item is not found
		/// The resource edited and completed by database's information (related items & so on)
		Task Edit([NotNull] T edited, bool resetOld);
		
		/// 
		/// Delete a resource by it's ID
		/// 
		/// The ID of the resource
		/// If the item is not found
		Task Delete(int id);
		/// 
		/// Delete a resource by it's slug
		/// 
		/// The slug of the resource
		/// If the item is not found
		Task Delete(string slug);
		/// 
		/// Delete a resource
		/// 
		/// The resource to delete
		/// If the item is not found
		Task Delete([NotNull] T obj);
		
		/// 
		/// Delete all resources that match the predicate.
		/// 
		/// A predicate to filter resources to delete. Every resource that match this will be deleted.
		/// If the item is not found
		Task DeleteAll([NotNull] Expression> where);
	}
	/// 
	/// A repository to handle shows.
	/// 
	public interface IShowRepository : IRepository
	{
		/// 
		/// Link a show to a collection and/or a library. The given show is now part of those containers.
		/// If both a library and a collection are given, the collection is added to the library too.
		/// 
		/// The ID of the show
		/// The ID of the library (optional)
		/// The ID of the collection (optional)
		Task AddShowLink(int showID, int? libraryID, int? collectionID);
		/// 
		/// Get a show's slug from it's ID.
		/// 
		/// The ID of the show
		/// If a show with the given ID is not found.
		/// The show's slug
		Task GetSlug(int showID);
	}
	/// 
	/// A repository to handle seasons.
	/// 
	public interface ISeasonRepository : IRepository
	{
		/// 
		/// Get a season from it's showID and it's seasonNumber
		/// 
		/// The id of the show
		/// The season's number
		/// If the item is not found
		/// The season found
		Task Get(int showID, int seasonNumber);
		
		/// 
		/// Get a season from it's show slug and it's seasonNumber
		/// 
		/// The slug of the show
		/// The season's number
		/// If the item is not found
		/// The season found
		Task Get(string showSlug, int seasonNumber);
		
		/// 
        /// Get a season from it's showID and it's seasonNumber or null if it is not found.
        /// 
        /// The id of the show
        /// The season's number
        /// The season found
        Task GetOrDefault(int showID, int seasonNumber);
        
        /// 
        /// Get a season from it's show slug and it's seasonNumber or null if it is not found.
        /// 
        /// The slug of the show
        /// The season's number
        /// The season found
        Task GetOrDefault(string showSlug, int seasonNumber);
	}
	
	/// 
	/// The repository to handle episodes
	/// 
	public interface IEpisodeRepository : IRepository
	{
		/// 
		/// Get a episode from it's showID, it's seasonNumber and it's episode number.
		/// 
		/// The id of the show
		/// The season's number
		/// The episode's number
		/// If the item is not found
		/// The episode found
		Task Get(int showID, int seasonNumber, int episodeNumber);
		/// 
		/// Get a episode from it's show slug, it's seasonNumber and it's episode number.
		/// 
		/// The slug of the show
		/// The season's number
		/// The episode's number
		/// If the item is not found
		/// The episode found
		Task Get(string showSlug, int seasonNumber, int episodeNumber);
		/// 
		/// Get a episode from it's showID, it's seasonNumber and it's episode number or null if it is not found.
		/// 
		/// The id of the show
		/// The season's number
		/// The episode's number
		/// The episode found
		Task GetOrDefault(int showID, int seasonNumber, int episodeNumber);
		/// 
		/// Get a episode from it's show slug, it's seasonNumber and it's episode number or null if it is not found.
		/// 
		/// The slug of the show
		/// The season's number
		/// The episode's number
		/// The episode found
		Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber);
		
		/// 
		/// Get a episode from it's showID and it's absolute number.
		/// 
		/// The id of the show
		/// The episode's absolute number (The episode number does not reset to 1 after the end of a season.
		/// If the item is not found
		/// The episode found
		Task GetAbsolute(int showID, int absoluteNumber);
		/// 
		/// Get a episode from it's showID and it's absolute number.
		/// 
		/// The slug of the show
		/// The episode's absolute number (The episode number does not reset to 1 after the end of a season.
		/// If the item is not found
		/// The episode found
		Task GetAbsolute(string showSlug, int absoluteNumber);
	}
	/// 
	/// A repository to handle tracks
	/// 
	public interface ITrackRepository : IRepository