using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using AutoMapper;
using EFCore.BulkExtensions;
using Microsoft.Extensions.Options;
using OnlineAssessment.Common;
using OnlineAssessment.Domain.Models;
using OnlineAssessment.Domain.ViewModels;

namespace OnlineAssessment.Data.EFCore
{
    public class EFCorePracticeRepository : EfCoreRepository<Practices, OnlineAssessmentContext>
    {
        public readonly OnlineAssessmentContext _context;
        public readonly EfCoreCommonRepository _common;
        private readonly IMapper _mapper;
        public EFCorePracticeRepository(OnlineAssessmentContext context, IMapper mapper, IOptionsSnapshot<ResponseMessage> responseMessage) : base(context, responseMessage)
        {
            _context = context;
            _mapper = mapper;
            _common = new EfCoreCommonRepository(_context, mapper);
        }

        #region Practices

        public int GetLanguageIdByCode(string language_code)
        {
            Language language = _common.GetLanguageByCode(language_code);

            if (language == null) return -1;
            int language_id = (int)language.Id;

            return language_id;
        }

        //Add new Practice
        public PracticeViewModel AddNewPractice(int institute_id, int language_id, int class_id, int user_id, PracticeAddModel newPractice, out string return_message)
        {
            return_message = string.Empty;

            Classes cls = _context.Classes.Where(c => c.Id == class_id && c.InstituteId == institute_id && c.IsActive == true).FirstOrDefault();
            if (cls == null) return null;

            int? module_id = 0;

            if (newPractice.module == Constant.Subject.ToString())
            {

                module_id = (from s in _context.Subjects
                             join c in _context.Classes on s.ClassId equals c.Id

                             where s.Id == newPractice.module_id && s.IsActive == true
                             && c.InstituteId == institute_id && c.IsActive == true
                             select s).FirstOrDefault().Id;

            } else if (newPractice.module == Constant.Category.ToString())
            {
                module_id = (from cat in _context.Categories
                             join s in _context.Subjects on cat.SubjectId equals s.Id
                             join c in _context.Classes on s.ClassId equals c.Id

                             where cat.Id == newPractice.module_id && cat.IsActive == true
                             && s.IsActive == true
                             && c.InstituteId == institute_id && c.IsActive == true
                             select cat).FirstOrDefault().Id;
            }


            if (module_id == null || module_id <= 0) return null;

            try
            {
                //Step-1: Begin a transaction
                _context.Database.BeginTransaction();

                //Step-2: Add Practice details
                Practices practices = new Practices
                {
                    InstituteId = institute_id,
                    ClassId = class_id,
                    Module = newPractice.module,
                    ModuleId = (int)module_id,
                    ModuleStatus = StatusCode.DRAFT.ToString(),
                    Status = StatusCode.DRAFT.ToString(),
                    LanguageId = language_id,
                    Name = newPractice.name,
                    CreatedBy = user_id,
                    UpdatedBy = user_id,
                    CreatedOn = _common.NowIndianTime(),
                    UpdatedOn = _common.NowIndianTime(),
                    IsActive = true
                };
                _context.Practices.Add(practices);
                _context.SaveChanges();
                int newPracticeId = practices.Id;

                //Step-3: Commit the transaction
                _context.Database.CommitTransaction();

                //Step-4: Get the new Exam infomration and return as response
                PracticeViewModel addedPractice = _common.GetPracticeById(institute_id, newPracticeId);

                return addedPractice;

            }
            catch (Exception ex)
            {
                return_message = ex.InnerException.ToString();
                _context.Database.RollbackTransaction();
                return null;
            }
        }

        public bool isValidBatch(int institute_id, int batch_id, int user_id)
        {
            return _common.isValidBatch(institute_id, batch_id, user_id);
        }


        //Getting a specific exam
        public PracticeViewModel GetPracticeById(int institute_id, int user_id, int practice_id)
        {
            PracticeViewModel practice = _common.GetPracticeById(institute_id, practice_id);

            if (practice == null) return null;

            if (user_id > 0 && practice.author_id != user_id) return null;

            return practice;
        }

        //Getting upcoming practices of an institute
        public List<PracticeViewAllModel> GetUpcomingPractices(int institute_id, int class_id, int user_id, string module, string sortBy, string sortOrder)
        {
            List<Practices> practices;
            List<PracticeViewAllModel> practiceList = new List<PracticeViewAllModel>();

            practices = (from p in _context.Practices

                         where p.InstituteId == institute_id && p.ClassId == class_id && p.Status == StatusCode.PUBLISHED.ToString()
                                && p.Module == module && p.OpenDatetime > _common.NowIndianTime() && p.IsActive == true

                         select p
                     ).ToList();

            if (practices == null || practices.Count == 0) return practiceList;

            if (user_id > 0)
            {
                practices = practices.Where(p => p.CreatedBy == user_id).ToList();
                if (practices == null || practices.Count() == 0) return practiceList;
            }
                
            List<int> practiceIdList = new List<int>();
            foreach (Practices p in practices)
            {
                practiceIdList.Add(p.Id);
            }

            practiceList = _common.GetPracticeListByIds(institute_id, practiceIdList);

            if ((sortOrder != null && sortOrder.Length > 0) || (sortBy != null && sortBy.Length > 0))
            {
                if (sortOrder != null && sortOrder.Equals("D"))
                {
                    switch (sortBy.ToUpper())
                    {
                        case "ID": practiceList = practiceList.OrderByDescending(a => a.id).ToList(); break;
                        case "AUTHOR": practiceList = practiceList.OrderByDescending(a => a.author_name).ToList(); break;
                    }
                }
                else
                {
                    switch (sortBy.ToUpper())
                    {
                        case "ID": practiceList = practiceList.OrderBy(a => a.id).ToList(); break;
                        case "AUTHOR": practiceList = practiceList.OrderBy(a => a.author_name).ToList(); break;
                    }
                }
            }

            return practiceList;
        }


        //Getting live practices of an institute
        public List<PracticeViewAllModel> GetLivePractices(int institute_id, int class_id, int user_id, string module, string sortBy, string sortOrder)
        {
            List<Practices> practices;
            List<PracticeViewAllModel> practiceList = new List<PracticeViewAllModel>();

            practices = (from p in _context.Practices

                         where p.InstituteId == institute_id && p.ClassId == class_id && p.Status == StatusCode.PUBLISHED.ToString()
                                && p.Module == module && p.OpenDatetime <= _common.NowIndianTime() && p.IsActive == true

                         select p
                     ).ToList();

            if (practices == null || practices.Count == 0) return practiceList;

            if (user_id > 0)
            {
                practices = practices.Where(p => p.CreatedBy == user_id).ToList();
                if (practices == null || practices.Count() == 0) return practiceList;
            }

            List<int> practiceIdList = new List<int>();
            foreach (Practices p in practices)
            {
                practiceIdList.Add(p.Id);
            }

            practiceList = _common.GetPracticeListByIds(institute_id, practiceIdList);

            if ((sortOrder != null && sortOrder.Length > 0) || (sortBy != null && sortBy.Length > 0))
            {
                if (sortOrder != null && sortOrder.Equals("D"))
                {
                    switch (sortBy.ToUpper())
                    {
                        case "ID": practiceList = practiceList.OrderByDescending(a => a.id).ToList(); break;
                        case "AUTHOR": practiceList = practiceList.OrderByDescending(a => a.author_name).ToList(); break;
                    }
                }
                else
                {
                    switch (sortBy.ToUpper())
                    {
                        case "ID": practiceList = practiceList.OrderBy(a => a.id).ToList(); break;
                        case "AUTHOR": practiceList = practiceList.OrderBy(a => a.author_name).ToList(); break;
                    }
                }
            }

            return practiceList;
        }




        //Getting draft practices of an institute
        public List<PracticeViewAllModel> GetDraftPractices(int institute_id, int class_id, int user_id, string module, string sortBy, string sortOrder)
        {
            List<Practices> practices;
            List<PracticeViewAllModel> practiceList = new List<PracticeViewAllModel>();

            practices = (from p in _context.Practices

                         where p.InstituteId == institute_id && p.ClassId == class_id && p.Status == StatusCode.DRAFT.ToString()
                                && p.Module == module && p.IsActive == true

                         select p
                     ).ToList();

            if (practices == null || practices.Count == 0) return practiceList;

            if (user_id > 0)
            {
                practices = practices.Where(p => p.CreatedBy == user_id).ToList();
                if (practices == null || practices.Count() == 0) return practiceList;
            }

            List<int> practiceIdList = new List<int>();
            foreach (Practices p in practices)
            {
                practiceIdList.Add(p.Id);
            }

            practiceList = _common.GetPracticeListByIds(institute_id, practiceIdList);

            if ((sortOrder != null && sortOrder.Length > 0) || (sortBy != null && sortBy.Length > 0))
            {
                if (sortOrder != null && sortOrder.Equals("D"))
                {
                    switch (sortBy.ToUpper())
                    {
                        case "ID": practiceList = practiceList.OrderByDescending(a => a.id).ToList(); break;
                        case "AUTHOR": practiceList = practiceList.OrderByDescending(a => a.author_name).ToList(); break;
                    }
                }
                else
                {
                    switch (sortBy.ToUpper())
                    {
                        case "ID": practiceList = practiceList.OrderBy(a => a.id).ToList(); break;
                        case "AUTHOR": practiceList = practiceList.OrderBy(a => a.author_name).ToList(); break;
                    }
                }
            }

            return practiceList;
        }

        //This endpoint allows you to associate questions to Praactices
        public int AttachQuestionsToPractice(int institute_id, int user_id, int practice_id, QuestionsList questionIdList, out string return_message)
        {
            int return_value = 0;

            Practices practices = _context.Practices.Where(p => p.Id == practice_id && p.InstituteId == institute_id
                            && p.Status == StatusCode.DRAFT.ToString() && p.IsActive == true).FirstOrDefault();


            if (practices == null)
            {
                return_message = string.Format("{0} questions attached the Practice", return_value);
                return (int)Message.NotAllowedToResource;
            }


            try
            {
                List<PracticeQuestions> practiceQuestions = new List<PracticeQuestions>();
                foreach (int question_id in questionIdList.idList)
                {
                    Questions qns = _context.Questions.Where(q => q.Id == question_id && q.InstituteId == institute_id && q.IsActive == true).FirstOrDefault();
                    if (qns == null)
                        continue;
                    if (user_id > 0 && qns.CreatedBy != user_id)
                        continue;
                    PracticeQuestions pQns = _context.PracticeQuestions.Where(pq => pq.PracticeId == practice_id && pq.QuestionId == question_id && pq.IsActive == true).FirstOrDefault();
                    if (pQns != null)
                        continue;
                    else
                    {
                        practiceQuestions.Add(
                            new PracticeQuestions()
                            {
                                PracticeId = practice_id,
                                QuestionId = question_id,
                                CreatedOn = _common.NowIndianTime(),
                                UpdatedOn = _common.NowIndianTime(),
                                IsActive = true
                            });
                    }
                    ++return_value;
                }

                if (return_value > 0)
                {
                    _context.BulkInsert(practiceQuestions);
                    practices.UpdatedOn = _common.NowIndianTime();
                    _context.Practices.Update(practices);
                    _context.SaveChanges();
                }

                return_message = string.Format("{0} questions attached the Exam Section", return_value);
            }
            catch (Exception ex)
            {
                return_value = -1;
                return_message = ex.InnerException.ToString();
            }
            return return_value;
        }

        // Attach & detach only possible in draft state, if submitted state then it will convert to draft again. if published then it cant 
        public int DetachQuestionsFromPractice(int institute_id, int practice_id, QuestionsList questionIdList, out string return_message)
        {
            int return_value = 0;

            Practices practices = _context.Practices.Where(p => p.Id == practice_id && p.InstituteId == institute_id
                            && p.Status == StatusCode.DRAFT.ToString() && p.IsActive == true).FirstOrDefault();


            if (practices == null)
            {
                return_message = string.Format("{0} questions detached from Practice", return_value);
                return (int)Message.NotAllowedToResource;
            }

            try
            {
                List<PracticeQuestions> practiceQuestions = new List<PracticeQuestions>();
                foreach (int question_id in questionIdList.idList)
                {
                    PracticeQuestions pQns = _context.PracticeQuestions.Where(pq => pq.PracticeId == practice_id && pq.QuestionId == question_id && pq.IsActive == true).FirstOrDefault();

                    if (pQns == null)
                        continue;
                    else
                        practiceQuestions.Add(pQns);

                    ++return_value;
                }

                if (return_value > 0)
                {
                    _context.BulkDelete(practiceQuestions);
                    practices.UpdatedOn = _common.NowIndianTime();
                    _context.Practices.Update(practices);
                    _context.SaveChanges();
                }

                return_message = string.Format("{0} questions detached to the exam section.", return_value);
            }
            catch (Exception ex)
            {
                return_value = -1;
                return_message = ex.InnerException.ToString();
            }
            return return_value;
        }


        public List<PracticeQuestionDetails> GetQuestionsOfThePractice(int institute_id, int user_id, int practice_id, string sortBy, string sortOrder)
        {
            List<PracticeQuestionDetails> lqvam = new List<PracticeQuestionDetails>();

            try
            {
                Practices practice = _context.Practices.Where(p => p.Id == practice_id && p.InstituteId == institute_id && p.IsActive == true).FirstOrDefault();

                if (practice == null) return lqvam;

                List<PracticeQuestions> questions = _context.PracticeQuestions.
                                                            Where(p => p.PracticeId == practice_id).
                                                            Where(p => p.IsActive == true).ToList();


                if (questions == null) return lqvam;

                Dictionary<int, PracticeQuestions> d = new Dictionary<int, PracticeQuestions>();
                List<int> idList = new List<int>();
                for (int i = 0; i < questions.Count; i++)
                {
                    d.Add(questions[i].QuestionId, questions[i]);
                    idList.Add(questions[i].QuestionId);
                }

                
                lqvam = (from q in _context.Questions
                         join ql in _context.QuestionLanguges on q.Id equals ql.QuestionId
                         join qt in _context.QuestionTypes on q.TypeId equals qt.Id
                         join qc in _context.QuestionCategories on q.Id equals qc.QuestionId
                         join c in _context.Categories on qc.CategoryId equals c.Id
                         join sub in _context.Subjects on c.SubjectId equals sub.Id
                         join cls in _context.Classes on sub.ClassId equals cls.Id

                         where idList.Contains(q.Id)
                                    && q.InstituteId == institute_id
                                    && ql.LanguageId == practice.LanguageId && ql.IsActive == true
                                    && qt.IsActive == true


                         select new PracticeQuestionDetails
                         {
                             id = q.Id,
                             title = ql.Question,
                             author = q.AuthorId,
                             complexity_code = q.ComplexityCode,
                             source = q.Source,
                             type = qt.Code,
                             type_name = qt.Name,
                             status = q.StatusCode,
                             isActive = ql.IsActive,
                             topic_id = c.Id,
                             topic_name = c.Name,
                             sequence = d[q.Id].QuestionSequence,
                             isBookmarked =
                                      _context.BookmarkedQuestions.
                                      Where(b => b.UserId == user_id).
                                      Where(b => b.QuestionId == q.Id).FirstOrDefault().IsActive,
                             duration_seconds = d[q.Id].DurationSeconds
                         }
                            ).ToList();

                return lqvam;
            }
            catch (Exception ex)
            {
                _context.Database.RollbackTransaction();
                return null;
            }
        }

        //exam section & question from user's intitute, questlist has all questions attached to this exam section
        public PracticeViewModel AssignDurationToPracticeQuestions(int institute_id, int practice_id, QuestionDurationList qList)
        {
            //Check the exam status is not published or its not started
            int return_value = 0;
            try
            {
                Practices practice = (from p in _context.Practices

                                      where p.Id == practice_id && p.InstituteId == institute_id && p.IsActive == true
                                             && p.Status == StatusCode.DRAFT.ToString()

                                      select p).FirstOrDefault();



                if (practice == null) return null;

                _context.Database.BeginTransaction();

                short nSeq = 0;
                List<PracticeQuestions> practiceQuestions = new List<PracticeQuestions>();
                foreach (QuestionDuration newQuestion in qList.qnsMarkList)
                {
                    PracticeQuestions pq = _context.PracticeQuestions.Where(p => p.PracticeId == practice_id && p.QuestionId == newQuestion.id && p.IsActive == true).FirstOrDefault();

                    nSeq++;

                    if (pq == null)
                    {
                        _context.Database.RollbackTransaction();
                        return null;
                    }

                    else
                    {
                        pq.DurationSeconds = newQuestion.duration_seconds;
                        pq.QuestionSequence = nSeq;
                        practiceQuestions.Add(pq);
                    }

                    ++return_value;
                }


                if (return_value > 0)
                {
                    _context.BulkUpdate(practiceQuestions);
                    practice.UpdatedOn = _common.NowIndianTime();
                    practice.ModuleStatus = StatusCode.PUBLISHED.ToString();
                    _context.Practices.Update(practice);
                    _context.SaveChanges();
                }

                _context.Database.CommitTransaction();

                return _common.GetPracticeById(institute_id, practice_id);
            }
            catch (Exception ex)
            {
                string return_message = ex.InnerException.ToString();
                _context.Database.RollbackTransaction();
                return null;
            }
        }

        //(exam state == draft, section >= 1, all section == submited, start datetime & end datetime gt now, section duration lt totaltime || totaltime == -1, count of batch gt 1)
        public PracticeViewModel PublishPractice(int institute_id, int user_id, int practice_id, PracticePublishModel schedulePractice)
        {
            try
            {
                //Valid Start & end date
                if (DateTime.Compare(_common.NowIndianTime(), (DateTime)schedulePractice.start_date) > 0)
                {
                    schedulePractice.start_date = _common.NowIndianTime();
                }

                Practices practices = _context.Practices.Where(p => p.Id == practice_id && p.InstituteId == institute_id && p.IsActive == true).FirstOrDefault();
                PracticeViewModel practice = _common.GetPracticeById(institute_id, practice_id);

                //State shouldbe draft, atleast one section
                if (practice == null || practice.status == StatusCode.PUBLISHED.ToString())
                {
                    return null;
                }

                int complexity = 0;
                complexity = _common._calculateComplexity(schedulePractice.complexity);

                _context.Database.BeginTransaction();
                //TBD: Check the examsection status is submitted\
                practices.Complexity = (short)complexity;
                practices.OpenDatetime = schedulePractice.start_date;
                practices.UpdatedOn = _common.NowIndianTime();
                practices.UpdatedBy = user_id;
                practices.Status = StatusCode.PUBLISHED.ToString();

                _context.Practices.Update(practices);
                _context.SaveChanges();

                _context.Database.CommitTransaction();

                //AttachUserGroups(user_id, exam.id, scheduleExam.batch_list);

                return _common.GetPracticeById(institute_id, practices.Id);
            }
            catch
            {
                _context.Database.RollbackTransaction();
                return null;
            }
        }

        //This endpoint allows you to attach usergroups to a practice
        public int AttachUserGroups(int institute_id, int user_id, int practice_id, UserGroupsList userGroupsList)
        {
            int return_value = 0;

            try
            {
                Practices practices = _context.Practices.Where(e => e.Id == practice_id && e.InstituteId == institute_id && e.Status == StatusCode.PUBLISHED.ToString() && e.IsActive == true).FirstOrDefault();
                if (practices == null)
                {
                    return (int)Message.InvalidInput;
                }

                List<UserGroupPractices> userGroupPractices = new List<UserGroupPractices>();
                foreach (int userGroup_id in userGroupsList.idList)
                {
                    UserGroups ug = _context.UserGroups.Where(u => u.Id == userGroup_id && u.IsActive == true).FirstOrDefault();
                    if (ug == null || ug.ClassId != practices.ClassId)
                        continue;

                    UserGroupPractices ugp = _common.GetUserGroupPractices(institute_id, practice_id, userGroup_id);
                    if (ugp != null)
                        continue;

                    else
                    {
                        userGroupPractices.Add(
                            new UserGroupPractices()
                            {
                                UserGroupId = userGroup_id,
                                PracticeId = practice_id,
                                CreatedOn = _common.NowIndianTime(),
                                CreatedBy = user_id,
                                UpdatedOn = _common.NowIndianTime(),
                                UpdatedBy = user_id,
                                IsActive = true
                            });
                    }
                    ++return_value;
                }

                if (return_value > 0)
                {
                    _context.BulkInsert(userGroupPractices);
                }
            }
            catch (Exception ex)
            {
                return (int)Message.Failure;
            }

            return return_value;
        }

        //This endpoint allows you to attach usergroups to a practice
        public int DetachUserGroups(int institute_id, int user_id, int practice_id, UserGroupsList userGroupsList)
        {
            int return_value = 0;

            try
            {
                Practices practices = _context.Practices.Where(e => e.Id == practice_id && e.InstituteId == institute_id && e.Status == StatusCode.PUBLISHED.ToString() && e.IsActive == true).FirstOrDefault();
                if (practices == null)
                {
                    return (int)Message.InvalidInput;
                }

                List<UserGroupPractices> userGroupPractices = new List<UserGroupPractices>();
                foreach (int userGroup_id in userGroupsList.idList)
                {
                    UserGroups ug = _context.UserGroups.Where(u => u.Id == userGroup_id && u.IsActive == true).FirstOrDefault();
                    if (ug == null || ug.ClassId != practices.ClassId)
                        continue;

                    UserGroupPractices ugp = _common.GetUserGroupPractices(institute_id, practice_id, userGroup_id);
                    if (ugp == null)
                        continue;

                    else
                    {
                        userGroupPractices.Add(ugp);
                    }
                    ++return_value;
                }

                if (return_value > 0)
                {
                    _context.BulkDelete(userGroupPractices);
                }
            }
            catch (Exception ex)
            {
                return (int)Message.Failure;
            }

            return return_value;
        }

        //This endpoint returns list of usergroups to a practice
        public UserGroupsList GetBatchListsOfThePractice(int institute_id, int user_id, int practice_id)
        {
            Practices practice = _context.Practices.Where(p => p.Id == practice_id && p.Status == StatusCode.PUBLISHED.ToString() && p.IsActive == true).FirstOrDefault();
            if (practice == null)
            {
                return null;
            }

            try
            {
                UserGroupsList ugl = new UserGroupsList();

                ugl.idList = (from u in _context.UserGroupPractices
                              where u.PracticeId == practice_id && u.IsActive == true
                              select u.UserGroupId).ToList();

                return ugl;

            }
            catch (Exception ex)
            {
                return null;
            }
        }

        public int DeletePractice(int institute_id, int user_id, int practice_id)
        {
            int return_value = -1;
            try
            {
                Practices practice = _context.Practices.Where(p => p.InstituteId == institute_id && p.Id == practice_id && p.IsActive == true).FirstOrDefault();

                //if exam is in published mode then delete not possible. 
                if (practice == null || practice.Status == StatusCode.PUBLISHED.ToString())
                {
                    return -1;
                }

                practice.IsActive = false;
                practice.UpdatedBy = user_id;
                practice.UpdatedOn = _common.NowIndianTime();

                _context.Practices.Update(practice);
                _context.SaveChanges();

                return_value = practice_id;
            }
            catch
            {
                return_value = -1;
            }
            return return_value;
        }

        #endregion
    }
}
