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

namespace DatabaseEditor.BL.ItemsProviders
{
    public class BooksProvider : MySQLItemsProvider<BookListModel, BookDetailModel>
    {
        private readonly Mapper _mapper;
        private readonly Measurer _measurer = new Measurer();

        public BooksProvider(string connectionString)
            : base(connectionString)
        {
            _mapper = new Mapper();
        }

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

        public override IList<BookListModel> FetchRange(int startIndex, int count)
        {
            using (var dbContext = new LibraryDbContext(ConnectionString))
            {
                dbContext.Database.Log = s => Trace.WriteLine(s);
                IQueryable<Book> query = dbContext.Books.AsNoTracking();
                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.BookId);
                        break;
                }
                return query
                    .Where(Filter.WherePredicate)
                    .Skip(startIndex).Take(count)
                    .Select(_mapper.MapEntityToListModel)
                    .ToList();
            }
        }

        public override bool Contains(BookListModel item)
        {
            using (var dbContext = new LibraryDbContext(ConnectionString))
            {
                return dbContext.Books.FirstOrDefault(b => b.BookId == item.Id) != null;
            }
        }

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

        public override void UpdateOrInsert(BookDetailModel book)
        {
            using (var dbContext = new LibraryDbContext(ConnectionString))
            {
                var newBook = EntityFromDetailModel(book);
                var bookInDb = dbContext.Books
                    .Include(b => b.Authors)
                    .FirstOrDefault(b => b.BookId == newBook.BookId);

                if (bookInDb == null)
                {
                    // add new book
                    foreach (var newBookAuthor in newBook.Authors)
                    {
                        dbContext.Authors.Attach(newBookAuthor);
                    }
                    dbContext.Books.Add(newBook);
                }
                else
                {
                    // update scalar values
                    dbContext.Entry(bookInDb).CurrentValues.SetValues(newBook);

                    // remove all authors that the new book shouldn't have
                    foreach (var bookInDbAuthor in bookInDb.Authors.ToArray())
                    {
                        Author author = newBook.Authors.FirstOrDefault(a => a.AuthorId == bookInDbAuthor.AuthorId);
                        if (author == null)
                        {
                            bookInDb.Authors.Remove(bookInDbAuthor);
                        }
                    }

                    // add all new books that the existing author doesn't have
                    foreach (var newBookAuthor in newBook.Authors.ToArray())
                    {
                        Author author = bookInDb.Authors.FirstOrDefault(a => a.AuthorId == newBookAuthor.AuthorId);
                        if (author == null)
                        {
                            dbContext.Authors.Attach(newBookAuthor);
                            bookInDb.Authors.Add(newBookAuthor);
                        }
                    }
                }
                dbContext.SaveChanges();
            }
        }

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

                dbContext.Books.Remove(book);
                dbContext.SaveChanges();
            }
            return true;
        }

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

        private List<Author> GetBooksAuthor(BookDetailModel book)
        {
            if (book == null) return null;

            using (var dbContext = new LibraryDbContext(ConnectionString))
            {
                var authors = new List<Author>();
                foreach (var authorListModel in book.Authors)
                {
                    authors.Add(dbContext.Authors.FirstOrDefault(author => author.AuthorId == authorListModel.Id));
                }
                return authors;
            }
        }

        private Book EntityFromDetailModel(BookDetailModel detialModel)
        {
            return new Book()
            {
                BookId = detialModel.Id,
                Name = detialModel.Name,
                Authors = GetBooksAuthor(detialModel).ToArray(),
                ISBN = detialModel.ISBN,
                Pages = detialModel.Pages,
                Year = detialModel.Year
            };
        }
    }
}