using Pilz.Data;
using Pilz.Extensions.Reflection;
using Pilz.Net.Extensions;
using System.Diagnostics;
using System.Net;

namespace Pilz.Net.Api.Server;

public abstract class BaseChildItemHandler<TEntity, TParent, TUpdateMsg>(IApiServer server)
    : BaseHandler<TEntity, TUpdateMsg>(server)
    where TEntity : class, IDataObject
    where TParent : class, IDataObject
    where TUpdateMsg : ApiMessage
{
    public class ChildEntityUpdate(TEntity entity, Func<TParent?> getParent, TUpdateMsg message, ApiRequestInfo reqest) : EntityUpdate(entity, message, reqest)
    {
        private readonly Func<TParent?> getParent = getParent;
        private TParent? parent;
        public TParent? Parent => parent ??= getParent();
    }

    /// <summary>
    /// Gets the base route (endpoint) for the most API calls.
    /// By default this defines the combo of <see cref="RouteParent"/> and <see cref="RouteChild"/>.
    /// </summary>
    public override string Route => RouteParent + RouteChild;
    /// <summary>
    /// Gets the base route of the parent entity.
    /// </summary>
    public abstract string RouteParent { get; }
    /// <summary>
    /// Gets the realtive base route of the child entity.
    /// </summary>
    public abstract string RouteChild { get; }
    protected virtual bool RegisterGetAll => true;
    protected virtual bool RegisterPost => true;

    public override void Initialize(IApiServer server)
    {
        base.Initialize(server);
        var t = GetType();
        if (RegisterGetAll)
            server.RegisterHandler(t.GetMethod(nameof(GetAll))!.CreateDelegate(this), new(RouteParent + "/{pid}" + RouteChild, "GET"), Debugger.IsAttached);
        if (RegisterPost)
            server.RegisterHandler(t.GetMethod(nameof(Post))!.CreateDelegate(this), new(RouteParent + "/{pid}" + RouteChild, "POST"), Debugger.IsAttached);
    }

    public virtual ApiResult GetAll(int pid, int offset, int amount)
    {
        IQueryable<TEntity> entities;

        if (pid != 0)
        {
            if (!server.Manager.Find(pid, out TParent? parentEntity))
                return ApiResult.NotFound();
            entities = GetChilds(parentEntity).AsQueryable();
        }
        else
            entities = server.Manager.Get<TEntity>();

        entities = SortEntities(entities);

        if (offset > 0)
            entities = entities.Skip(offset);
        if (amount > 0)
            entities = entities.Take(amount);

        return SortEntities(entities).ToList().Select(ToClient).ToItemsResult();
    }

    public virtual ApiResult Post(int pid, TUpdateMsg msg, ApiRequestInfo req)
    {
        if (!server.Manager.Find(pid, out TParent? parentEntity))
            return ApiResult.NotFound();
        var entity = CreateNewEntity(msg, parentEntity);
        var update = new ChildEntityUpdate(entity, () => parentEntity, msg, req);
        if (UpdateEntity(update) is ApiResult result)
            return result;
        GetChilds(parentEntity).Add(entity);
        if (OnSave(update) is ApiResult result2)
            return result2;
        return ToClient(entity).ToItemResult(HttpStatusCode.Created);
    }

    public override ApiResult Put(int id, TUpdateMsg msg, ApiRequestInfo req)
    {
        if (!server.Manager.Find(id, out TEntity? entity))
            return ApiResult.NotFound();
        var update = new ChildEntityUpdate(entity, () => server.Manager.Get<TParent>().AsEnumerable().FirstOrDefault(n => GetChilds(n).Any(n => n.Id == id)), msg, req);
        if (UpdateEntity(update) is ApiResult result)
            return result;
        if (OnSave(update) is ApiResult result2)
            return result2;
        return ToClient(entity).ToItemResult();
    }

    protected virtual TEntity CreateNewEntity(TUpdateMsg msg, TParent parent)
    {
        return CreateNewEntity(msg);
    }

    protected override ApiResult? OnSave(EntityUpdate update)
    {
        if (update.Entity.Id == 0 && update is ChildEntityUpdate { Parent: not null } updateEx)
        {
            server.Manager.Save(updateEx.Parent, true);
            return null;
        }
        else
            return base.OnSave(update);
    }

    public abstract IList<TEntity> GetChilds(TParent parent);

    protected virtual IQueryable<TEntity> SortEntities(IQueryable<TEntity> entities)
    {
        return entities;
    }
}
