using FoodsharingSiegen.Contracts;
using FoodsharingSiegen.Contracts.Entity;
using FoodsharingSiegen.Contracts.Enums;
using FoodsharingSiegen.Contracts.Model;
using FoodsharingSiegen.Server.Auth;
using Microsoft.EntityFrameworkCore;

namespace FoodsharingSiegen.Server.Data.Service
{
    /// <summary>
    /// The prospect service class (a. beging, 01.04.2022)
    /// </summary>
    /// <seealso cref="ServiceBase"/>
    public class ProspectService : ServiceBase
    {
        private AuditService AuditService { get; }

        #region Setup/Teardown

        /// <summary>
        /// Initializes a new instance of the <see cref="ProspectService"/> class
        /// </summary>
        /// <param name="context">The context</param>
        /// <param name="authService"></param>
        /// <param name="auditService"></param>
        public ProspectService(FsContext context, AuthService authService, AuditService auditService) : base(context, authService) => AuditService = auditService;

        #endregion

        #region Public Method AddInteraction

        /// <summary>
        /// Adds the interaction using the specified interaction (a. beging, 02.04.2022)
        /// </summary>
        /// <param name="interaction">The interaction</param>
        /// <returns>A task containing an operation result of interaction</returns>
        public async Task<OperationResult<Interaction>> AddInteraction(Interaction interaction)
        {
            try
            {
                var targetProspect = await Context.Prospects!.Include(x => x.Interactions).FirstOrDefaultAsync(x => x.Id == interaction.ProspectID);
                if (targetProspect == null) return new(new Exception("Invalid prospect id"));
                
                interaction.ProspectID = Guid.Empty;
                interaction.Created = DateTime.UtcNow;

                targetProspect.Interactions.Add(interaction);
                targetProspect.Modified = DateTime.UtcNow;
                
                await Context.SaveChangesAsync();
                
                await AuditService.Insert(AuditType.AddInteraction, targetProspect.Name, interaction.Type.ToString());
                
                // Detatch entities
                Context.Entry(targetProspect).State = EntityState.Detached;
                Context.Entry(interaction).State = EntityState.Detached;
                
                return new(interaction);
            }
            catch (Exception e)
            {
                return new(e);
            }
        }

        #endregion

        #region Public Method AddProspectAsync

        /// <summary>
        /// Adds the prospect using the specified prospect (a. beging, 01.04.2022)
        /// </summary>
        /// <param name="prospect">The prospect</param>
        /// <returns>A task containing an operation result of prospect</returns>
        public async Task<OperationResult<Prospect>> AddProspectAsync(Prospect? prospect)
        {
            try
            {
                if (prospect == null) return new(new Exception("Cannot be empty"));

                prospect.Created = DateTime.UtcNow;
                prospect.Modified = DateTime.UtcNow;
                prospect.Id = Guid.Empty;

                await Context.Prospects!.AddAsync(prospect);
                var saveR = await Context.SaveChangesAsync();

                if (saveR > 0)
                {
                    await AuditService.Insert(AuditType.CreateProspect, prospect.Name, prospect.FsId.ToString());
                    return new(prospect);
                }
            
                return new(new Exception("Couldn't add prospect"));
            }
            catch (Exception e)
            {
                return new(e);
            }
        }

        #endregion

        #region Public Method GetProspectsAsync

        /// <summary>
        /// Gets the users (a. beging, 01.04.2022)
        /// </summary>
        /// <returns>A task containing an operation result of list prospect</returns>
        public async Task<OperationResult<List<Prospect>>> GetProspectsAsync(GetProspectsParameter parameter)
        {
            try
            {
                var prospectsQuery = Context.Prospects!.AsNoTracking()
                    .Include(x => x.Images)
                    .Include(x => x.Interactions.OrderBy(i => i.Date)).ThenInclude(x => x.User)
                    .OrderBy(x => x.Name).AsQueryable();
                
                if(parameter.MustHaveInteractions != null && parameter.MustHaveInteractions.Any()) 
                    prospectsQuery = prospectsQuery.Where(x => x.Interactions.Any(i => parameter.MustHaveInteractions.Contains(i.Type)));
                
                if(parameter.CannotHaveInteractions != null && parameter.CannotHaveInteractions.Any())
                    prospectsQuery = prospectsQuery.Where(x => x.Interactions.All(i => !parameter.CannotHaveInteractions.Contains(i.Type)));

                if (!parameter.IncludeDeleted) 
                    prospectsQuery = prospectsQuery.Where(x => x.RecordState != RecordState.Archived);
                
                var prospects = await prospectsQuery.ToListAsync();
                
                return new(prospects);
            }
            catch (Exception e)
            {
                return new(e);
            }
        }

        #endregion

        #region Public Method RemoveInteraction

        /// <summary>
        /// Removes the interaction using the specified interaction id (a. beging, 11.04.2022)
        /// </summary>
        /// <param name="interactionId">The interaction id</param>
        /// <returns>A task containing the operation result</returns>
        public async Task<OperationResult> RemoveInteraction(Guid interactionId)
        {
            try
            {
                var interaction = await Context.Interactions!.AsNoTracking().FirstOrDefaultAsync(x => x.Id == interactionId);
                if(interaction == null) return new(new Exception("Interaction not found"));
                
                Context.Interactions!.Remove(new() { Id = interaction.Id });
                await Context.SaveChangesAsync();
                
                // Update prospect modified date
                var prospect = await Context.Prospects!.FirstOrDefaultAsync(x => x.Id == interaction.ProspectID);
                if (prospect != null)
                {
                    prospect.Modified = DateTime.UtcNow;
                    await Context.SaveChangesAsync();
                }

                await AuditService.Insert(AuditType.RemoveInteraction, "?");
                
                return new();
            }
            catch (Exception e)
            {
                return new(e);
            }
        }

        #endregion

        #region Public Method UpdateAsync

        /// <summary>
        /// Updates the prospect (a. beging, 11.04.2022)
        /// </summary>
        /// <param name="prospect">The prospect</param>
        /// <returns>A task containing the operation result</returns>
        public async Task<OperationResult> UpdateAsync(Prospect prospect)
        {
            try
            {
                var entityProspect = await Context.Prospects!.FirstOrDefaultAsync(x => x.Id == prospect.Id);
                if (entityProspect == null) return new(new Exception("Prospect not found"));

                entityProspect.Memo = prospect.Memo;
                entityProspect.Name = prospect.Name;
                entityProspect.FsId = prospect.FsId;
                entityProspect.Warning = prospect.Warning;
                entityProspect.RecordState = prospect.RecordState;
                entityProspect.Modified = DateTime.UtcNow;
            
                var saveR = await Context.SaveChangesAsync();
            
                if(saveR < 1) return new(new Exception("Fehler beim speichern"));
                
                await AuditService.Insert(AuditType.EditProspect, prospect.Name);
                return new();
            }
            catch (Exception e)
            {
                return new(e);
            }
        }

        #endregion

        #region Image Upload Features

        public async Task<OperationResult<Guid>> GenerateVerificationTokenAsync(Guid prospectId)
        {
            try
            {
                var prospect = await Context.Prospects!
                    .Include(x => x.Interactions)
                    .FirstOrDefaultAsync(x => x.Id == prospectId);
                
                if (prospect == null) return new(new Exception("Prospect not found"));

                if (prospect.Interactions.Any(x => x.Type == InteractionType.Verify))
                    return new(new Exception("Die Identitätsprüfung wurde bereits abgeschlossen. Es kann kein neuer Token generiert werden."));

                prospect.VerificationToken = Guid.NewGuid();
                prospect.Modified = DateTime.UtcNow;

                await Context.SaveChangesAsync();
                return new(prospect.VerificationToken.Value);
            }
            catch (Exception e)
            {
                return new(e);
            }
        }

        public async Task<OperationResult<Prospect>> GetProspectByVerificationTokenAsync(Guid token)
        {
            try
            {
                var prospect = await Context.Prospects!
                    .Include(x => x.Interactions)
                    .Include(x => x.Images)
                    .AsNoTracking()
                    .FirstOrDefaultAsync(x => x.VerificationToken == token);
                
                if (prospect == null) return new(new Exception("Ungültiger oder abgelaufener Token."));
                
                if (prospect.Interactions.Any(x => x.Type == InteractionType.Verify))
                    return new(new Exception("Die Identitätsprüfung wurde bereits abgeschlossen. Ein Hochladen weiterer Bilder ist nicht mehr möglich."));

                return new(prospect);
            }
            catch (Exception e)
            {
                return new(e);
            }
        }

        public async Task<OperationResult> AddVerificationImageAsync(Guid prospectId, byte[] imageData, string contentType)
        {
            try
            {
                var prospect = await Context.Prospects!
                    .Include(x => x.Interactions)
                    .AsNoTracking()
                    .FirstOrDefaultAsync(x => x.Id == prospectId);

                if (prospect == null) return new(new Exception("Foodsaver nicht gefunden."));
                
                if (prospect.Interactions.Any(x => x.Type == InteractionType.Verify))
                    return new(new Exception("Die Identitätsprüfung wurde bereits abgeschlossen. Ein Hochladen weiterer Bilder ist nicht mehr möglich."));

                // Verify max 5 images
                var imgCount = await Context.ProspectImages!.CountAsync(x => x.ProspectId == prospectId);
                if (imgCount >= 5) return new(new Exception("Maximum 5 images allowed"));

                var image = new ProspectImage
                {
                    Id = Guid.NewGuid(),
                    ProspectId = prospectId,
                    ImageData = imageData,
                    ContentType = contentType,
                    Created = DateTime.UtcNow
                };

                await Context.ProspectImages!.AddAsync(image);
                await Context.SaveChangesAsync();

                await AuditService.Insert(AuditType.UploadProspectImage, prospect.Name);

                return new();
            }
            catch (Exception e)
            {
                return new(e);
            }
        }

        public async Task<OperationResult<List<ProspectImage>>> GetVerificationImagesAsync(Guid prospectId)
        {
            try
            {
                var images = await Context.ProspectImages!
                    .AsNoTracking()
                    .Where(x => x.ProspectId == prospectId)
                    .OrderBy(x => x.Created)
                    .ToListAsync();

                var prospectName = await Context.Prospects!
                    .Where(x => x.Id == prospectId)
                    .Select(x => x.Name)
                    .FirstOrDefaultAsync();

                if (!string.IsNullOrEmpty(prospectName))
                {
                    await AuditService.Insert(AuditType.ViewProspectImages, prospectName);
                }

                return new(images);
            }
            catch (Exception e)
            {
                return new(e);
            }
        }

        public async Task<OperationResult> DeleteVerificationImagesAsync(Guid prospectId)
        {
            try
            {
                var images = await Context.ProspectImages!.Where(x => x.ProspectId == prospectId).ToListAsync();
                if (images.Any())
                {
                    Context.ProspectImages!.RemoveRange(images);
                    
                    var prospect = await Context.Prospects!.FirstOrDefaultAsync(x => x.Id == prospectId);
                    if (prospect != null)
                    {
                        prospect.VerificationToken = null; // Clear token when images are deleted
                        await AuditService.Insert(AuditType.DeleteProspectImages, prospect.Name);
                    }

                    await Context.SaveChangesAsync();
                }

                return new();
            }
            catch (Exception e)
            {
                return new(e);
            }
        }

        #endregion
    }
}