using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Diagnostics;
using System.Linq;
using System.Linq.Dynamic;
using DatabaseEditor.BL.Enums;
using DatabaseEditor.BL.Models;
using DatabaseEditor.DAL;
using DatabaseEditor.DAL.Entities;

namespace DatabaseEditor.BL.ItemsProviders
{
    public class AuthorsProvider : MySQLItemsProvider<AuthorListModel, AuthorDetialModel>
    {
        private readonly Mapper _mapper;
        private readonly Measurer _measurer;

        public AuthorsProvider(string connectionString)
            : base(connectionString)
        {
            _measurer = new Measurer();
            _mapper = new Mapper();
        }

        public override int FetchCount()
        {
            using (var dbContext = new LibraryDbContext(ConnectionString))
            {
                return dbContext.Authors.AsNoTracking().Where(Filter.WherePredicate).Count();
            }
        }

        public override IList<AuthorListModel> FetchRange(int startIndex, int count)
        {
            using (var dbContext = new LibraryDbContext(ConnectionString))
            {
                IQueryable<Author> query = dbContext.Authors;
                switch (SortDirection)
                {
                    case SortDirection.Ascending:
                        query = query.OrderBy(SortedColumnName);
                        break;
                    case SortDirection.Descending:
                        query = query.OrderBy($"{SortedColumnName} desc");
                        break;
                    case SortDirection.NotSorted:
                        query = query.OrderBy(book => book.AuthorId);
                        break;
                }
                return query
                    .AsNoTracking()
                    .Where(Filter.WherePredicate)
                    .Skip(startIndex).Take(count)
                    .Select(_mapper.MapEntityToListModel)
                    .ToList();
            }
        }

        public override bool Contains(AuthorListModel author)
        {
            using (var dbContext = new LibraryDbContext(ConnectionString))
            {
                return dbContext.Authors.FirstOrDefault(a => a.AuthorId == author.Id) != null;
            }
        }

        public override AuthorDetialModel GetItemById(Guid id)
        {
            using (var dbContext = new LibraryDbContext(ConnectionString))
            {
                var sw = Stopwatch.StartNew();
                var entity = dbContext.Authors
                    .AsNoTracking()
                    .Include(a => a.Books)
                    .FirstOrDefault(a => a.AuthorId == id);
                sw.Stop();
                _measurer.AddTime(sw.Elapsed);
                return _mapper.MapEntityToDetailModel(entity);
            }
        }

        public override void UpdateOrInsert(AuthorDetialModel author)
        {
            using (var dbContext = new LibraryDbContext(ConnectionString))
            {
                var newAuthor = EntityFromDetailModel(author);

                var authorInDb = dbContext.Authors
                    .Include(a => a.Books)
                    .FirstOrDefault(a => a.AuthorId == newAuthor.AuthorId);

                if (authorInDb == null)
                {
                    // add new author
                    foreach (var newAuthorBook in newAuthor.Books)
                    {
                        dbContext.Books.Attach(newAuthorBook);
                    }
                    dbContext.Authors.Add(newAuthor);
                }
                else
                {
                    // update scalar values
                    dbContext.Entry(authorInDb).CurrentValues.SetValues(newAuthor);

                    // remove all books that the new author shouldn't have
                    foreach (var authorInDbBook in authorInDb.Books.ToArray())
                    {
                        Book book = newAuthor.Books.FirstOrDefault(b => b.BookId == authorInDbBook.BookId);
                        if (book == null)
                        {
                            authorInDb.Books.Remove(authorInDbBook);
                        }
                    }

                    // add all new books that the existing author doesn't have
                    foreach (var newAuthorBook in newAuthor.Books.ToArray())
                    {
                        Book book = authorInDb.Books.FirstOrDefault(b => b.BookId == newAuthorBook.BookId);
                        if (book == null)
                        {
                            dbContext.Books.Attach(newAuthorBook);
                            authorInDb.Books.Add(newAuthorBook);
                        }
                    }
                }
                dbContext.SaveChanges();
            }
        }

        public override bool DeleteItem(Guid id)
        {
            using (var dbContext = new LibraryDbContext(ConnectionString))
            {
                var author = dbContext.Authors.Find(id);
                if (author == null) return false;

                dbContext.Authors.Remove(author);
                dbContext.SaveChanges();
            }
            return true;
        }

        public override bool SourceChanged => TableChanged(typeof(Author));

        private List<Book> GetAuthorsBooks(AuthorDetialModel author)
        {
            if (author == null) return null;

            using (var dbContext = new LibraryDbContext(ConnectionString))
            {
                var books = new List<Book>();
                foreach (var bookListModel in author.Books)
                {
                    books.Add(dbContext.Books.FirstOrDefault(book => book.BookId == bookListModel.Id));
                }
                return books;
            }
        }

        private Author EntityFromDetailModel(AuthorDetialModel detialModel)
        {
            return new Author()
            {
                AuthorId = detialModel.Id,
                Books = GetAuthorsBooks(detialModel).ToArray(),
                Name = detialModel.Name,
                Surname = detialModel.Surname
            };
        }
    }
}