using Coscine.Database.DataModel;
using Coscine.Database.ReturnObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Coscine.Database.Models
{
    // ------------------------------------------------------------------------------------
    // This Class makes use of "GetAllWhere(...Id)" method rather than "GetById(...Id)" 
    // to make it future proof in case on a later stage more than a single entry 
    // is allowed inside the ContactChange table (e.g. history of contact change requests).
    // To achieve that, remove usage of "DeleteAllDbEntries(...Id)" method.
    // The line in quesiton is marked with an appropiate comment.
    // ------------------------------------------------------------------------------------

    public class ContactChangeModel : DatabaseModel<ContactChange>
    {
        public bool UserHasEmailsToConfirm(Guid userId)
        {
            // Perform deletion of all expired entries (older than 24 Hours).
            DeleteExpiredDbEntries(userId);
            IEnumerable<ContactChange> emailData = GetAllWhere((contactChange) => contactChange.UserId == userId);
            // Return True if entries for a user exist inside the database table ContactChange. Else return False.
            if (emailData.Count() > 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public ContactChangeObject NewEmailChangeRequest(Guid userId, string email)
        {
            if (UserHasEmailsToConfirm(userId))
            {
                DeleteAllDbEntries(userId); // <--- REMOVE THIS LINE IF YOU WANT TO KEEP HISTORY OF EMAIL CHANGES.
            }
            ContactChangeObject contactChangeObject = AddDbEntry(userId, email);
            // Sending a confirmation email after an addition to the database is handled by the Controller.
            return contactChangeObject;
        }

        public List<ContactChangeObject> GetEmailsForConfirmation(Guid userId)
        {
            List<ContactChangeObject> contactChangeObjects = new List<ContactChangeObject>();
            IEnumerable<ContactChange> emailData = GetAllWhere((contactChange) => contactChange.UserId == userId);
            foreach (var entry in emailData)
            {
                contactChangeObjects.Add(ToObject(entry));
            }
            return contactChangeObjects;
        }

        public UserObject ExecuteConfirmation(Guid token)
        {
            ContactChange emailData = GetWhere((contactChange) => contactChange.ConfirmationToken == token);
            if (emailData != null)
            {
                if (emailData.EditDate != null)
                {
                    // Add 23 Hours, 59 Minutes and 59 Seconds to EditDate, see when the token has to expire and compare with Now.
                    DateTime expirationDateTime = emailData.EditDate.Value.AddHours(23).AddMinutes(59).AddSeconds(59);
                    var compareDateTime = DateTime.Compare(expirationDateTime, DateTime.Now);
                    // Token EXPIRED when expirationDateTime = -1 (Expiration is BEFORE Now)
                    // Token VALID when expirationDateTime = 0  OR  = 1 (Expiration IS Now or AFTER Now)
                    if (compareDateTime >= 0)
                    {
                        // VALID
                        UserModel userModel = new UserModel();
                        User user = userModel.GetById(emailData.UserId);
                        user.EmailAddress = emailData.NewEmail; // Overwrite old Email with New.
                        userModel.Update(user); // Update Database (User Table).
                        Delete(emailData); // Delete Entry from Database (ContactChange Table).
                        UserObject userObject = userModel.CreateReturnObjectFromDatabaseObject(userModel.GetWhere((usr) => usr.Id == emailData.UserId));
                        return userObject;
                    }
                    else
                    {
                        throw new Exception("EXPIRED: Token " + token.ToString() + " has expired.");
                    }
                }
                else
                {
                    throw new ArgumentNullException("INVALID: Value EditDate is NULL for Token " + token.ToString() + ".");
                }

            }
            else
            {
                throw new MissingFieldException("INVALID: The Token " + token.ToString() + " is not valid. No entry inside the Database.");
            }
        }
        
        private void DeleteExpiredDbEntries(Guid userId)
        {
            IEnumerable<ContactChange> emailData = GetAllWhere((contactChange) => contactChange.UserId == userId);
            foreach (var entry in emailData)
            {
                // Add 23 Hours, 59 Minutes and 59 Seconds to EditDate, see when the token has to expire and compare with Now.
                DateTime expirationDateTime = entry.EditDate.Value.AddHours(23).AddMinutes(59).AddSeconds(59);
                var compareDateTime = DateTime.Compare(expirationDateTime, DateTime.Now);
                // Token EXPIRED when expirationDateTime = -1 (Expiration is BEFORE Now)
                // Token VALID when expirationDateTime = 0  OR  = 1 (Expiration IS Now or AFTER Now)
                if (compareDateTime < 0)
                {
                    Delete(entry);
                }
            }
        }

        private void DeleteAllDbEntries(Guid userId)
        {
            IEnumerable<ContactChange> emailData = GetAllWhere((contactChange) => contactChange.UserId == userId);
            foreach (var entry in emailData)
            {
                Delete(entry);
            }
        }

        private ContactChangeObject AddDbEntry(Guid userId, string email)
        {
            // Create new entry inside the Database for an Email Change Request for the specific User with an Id.
            ContactChange contactChange = new ContactChange()
            {
                RelationId = Guid.NewGuid(),
                UserId = userId,
                NewEmail = email,
                EditDate = DateTime.Now,
                ConfirmationToken = Guid.NewGuid()
            };
            Insert(contactChange);
            return ToObject(contactChange);
        }

        public static ContactChangeObject ToObject(ContactChange contactChange)
        {
            return new ContactChangeObject(
                contactChange.RelationId,
                contactChange.UserId,
                contactChange.NewEmail,
                contactChange.EditDate,
                contactChange.ConfirmationToken
                );
        }

        public static ContactChange FromObject(ContactChangeObject contactChangeObject)
        {
            return new ContactChange() 
            {
                RelationId = contactChangeObject.RelationId,
                UserId = contactChangeObject.UserId,
                NewEmail = contactChangeObject.NewEmail,
                EditDate = contactChangeObject.EditDate,
                ConfirmationToken = contactChangeObject.ConfirmationToken
            };
        }

        public override Expression<Func<ContactChange, Guid>> GetIdFromObject()
        {
            return (contactChange) => contactChange.RelationId;
        }

        public override Microsoft.EntityFrameworkCore.DbSet<ContactChange> GetITableFromDatabase(CoscineDB db)
        {
            return db.ContactChanges;
        }

        public override void SetObjectId(ContactChange databaseObject, Guid id)
        {
            databaseObject.RelationId = id;
        }
    }
}