using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using ContactManagementSystem.Models;

namespace ContactManagementSystem.Data.Repositories
{
    /// <summary>
    /// Implementation of IContactRepository using Entity Framework Core
    /// Demonstrates: Repository Pattern, LINQ queries, Async/Await
    /// </summary>
    public class ContactRepository : IContactRepository
    {
        private readonly ApplicationDbContext _context;
        private bool _disposed = false;

        #region Constructor

        /// <summary>
        /// Constructor - creates a new DbContext
        /// </summary>
        public ContactRepository()
        {
            _context = new ApplicationDbContext();
        }

        /// <summary>
        /// Constructor with dependency injection (recommended for production)
        /// </summary>
        /// <param name="context">Injected DbContext</param>
        public ContactRepository(ApplicationDbContext context)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
        }

        #endregion

        #region Synchronous CRUD Operations

        /// <summary>
        /// Gets all contacts from the database
        /// </summary>
        public List<Contact> GetAll()
        {
            return _context.Contacts
                .OrderBy(c => c.LastName)
                .ThenBy(c => c.FirstName)
                .ToList();
        }

        /// <summary>
        /// Gets a contact by its ID
        /// </summary>
        public Contact GetById(int id)
        {
            return _context.Contacts.Find(id);
        }

        /// <summary>
        /// Adds a new contact to the database
        /// </summary>
        public Contact Add(Contact contact)
        {
            if (contact == null)
                throw new ArgumentNullException(nameof(contact));

            // Validate contact
            contact.Validate();

            // Check for duplicate email
            if (EmailExists(contact.Email))
                throw new InvalidOperationException($"A contact with email '{contact.Email}' already exists.");

            // Set timestamps
            contact.DateCreated = DateTime.Now;
            contact.DateModified = DateTime.Now;

            // Add to context
            _context.Contacts.Add(contact);
            _context.SaveChanges();

            return contact;
        }

        /// <summary>
        /// Updates an existing contact in the database
        /// </summary>
        public bool Update(Contact contact)
        {
            if (contact == null)
                throw new ArgumentNullException(nameof(contact));

            // Validate contact
            contact.Validate();

            // Check if contact exists
            var existingContact = _context.Contacts.Find(contact.Id);
            if (existingContact == null)
                return false;

            // Check for duplicate email (excluding current contact)
            if (_context.Contacts.Any(c => c.Email == contact.Email && c.Id != contact.Id))
                throw new InvalidOperationException($"A contact with email '{contact.Email}' already exists.");

            // Update timestamp
            contact.DateModified = DateTime.Now;

            // Update entity
            _context.Entry(existingContact).CurrentValues.SetValues(contact);
            _context.SaveChanges();

            return true;
        }

        /// <summary>
        /// Deletes a contact from the database
        /// </summary>
        public bool Delete(int id)
        {
            var contact = _context.Contacts.Find(id);
            if (contact == null)
                return false;

            _context.Contacts.Remove(contact);
            _context.SaveChanges();

            return true;
        }

        #endregion

        #region Asynchronous CRUD Operations

        /// <summary>
        /// Gets all contacts from the database asynchronously
        /// </summary>
        public async Task<List<Contact>> GetAllAsync()
        {
            return await _context.Contacts
                .OrderBy(c => c.LastName)
                .ThenBy(c => c.FirstName)
                .ToListAsync();
        }

        /// <summary>
        /// Gets a contact by its ID asynchronously
        /// </summary>
        public async Task<Contact> GetByIdAsync(int id)
        {
            return await _context.Contacts.FindAsync(id);
        }

        /// <summary>
        /// Adds a new contact to the database asynchronously
        /// </summary>
        public async Task<Contact> AddAsync(Contact contact)
        {
            if (contact == null)
                throw new ArgumentNullException(nameof(contact));

            // Validate contact
            contact.Validate();

            // Check for duplicate email
            if (await EmailExistsAsync(contact.Email))
                throw new InvalidOperationException($"A contact with email '{contact.Email}' already exists.");

            // Set timestamps
            contact.DateCreated = DateTime.Now;
            contact.DateModified = DateTime.Now;

            // Add to context
            await _context.Contacts.AddAsync(contact);
            await _context.SaveChangesAsync();

            return contact;
        }

        /// <summary>
        /// Updates an existing contact in the database asynchronously
        /// </summary>
        public async Task<bool> UpdateAsync(Contact contact)
        {
            if (contact == null)
                throw new ArgumentNullException(nameof(contact));

            // Validate contact
            contact.Validate();

            // Check if contact exists
            var existingContact = await _context.Contacts.FindAsync(contact.Id);
            if (existingContact == null)
                return false;

            // Check for duplicate email (excluding current contact)
            if (await _context.Contacts.AnyAsync(c => c.Email == contact.Email && c.Id != contact.Id))
                throw new InvalidOperationException($"A contact with email '{contact.Email}' already exists.");

            // Update timestamp
            contact.DateModified = DateTime.Now;

            // Update entity
            _context.Entry(existingContact).CurrentValues.SetValues(contact);
            await _context.SaveChangesAsync();

            return true;
        }

        /// <summary>
        /// Deletes a contact from the database asynchronously
        /// </summary>
        public async Task<bool> DeleteAsync(int id)
        {
            var contact = await _context.Contacts.FindAsync(id);
            if (contact == null)
                return false;

            _context.Contacts.Remove(contact);
            await _context.SaveChangesAsync();

            return true;
        }

        #endregion

        #region Query Operations

        /// <summary>
        /// Searches for contacts matching the search term
        /// </summary>
        public List<Contact> Search(string searchTerm)
        {
            if (string.IsNullOrWhiteSpace(searchTerm))
                return new List<Contact>();

            searchTerm = searchTerm.ToLower();

            return _context.Contacts
                .Where(c =>
                    c.FirstName.ToLower().Contains(searchTerm) ||
                    c.LastName.ToLower().Contains(searchTerm) ||
                    c.Email.ToLower().Contains(searchTerm) ||
                    c.PhoneNumber.Contains(searchTerm) ||
                    (c.Company != null && c.Company.ToLower().Contains(searchTerm)))
                .OrderBy(c => c.LastName)
                .ToList();
        }

        /// <summary>
        /// Searches for contacts matching the search term asynchronously
        /// </summary>
        public async Task<List<Contact>> SearchAsync(string searchTerm)
        {
            if (string.IsNullOrWhiteSpace(searchTerm))
                return new List<Contact>();

            searchTerm = searchTerm.ToLower();

            return await _context.Contacts
                .Where(c =>
                    c.FirstName.ToLower().Contains(searchTerm) ||
                    c.LastName.ToLower().Contains(searchTerm) ||
                    c.Email.ToLower().Contains(searchTerm) ||
                    c.PhoneNumber.Contains(searchTerm) ||
                    (c.Company != null && c.Company.ToLower().Contains(searchTerm)))
                .OrderBy(c => c.LastName)
                .ToListAsync();
        }

        /// <summary>
        /// Gets contacts by company name
        /// </summary>
        public List<Contact> GetByCompany(string company)
        {
            if (string.IsNullOrWhiteSpace(company))
                return new List<Contact>();

            return _context.Contacts
                .Where(c => c.Company != null && c.Company.ToLower() == company.ToLower())
                .OrderBy(c => c.LastName)
                .ToList();
        }

        /// <summary>
        /// Gets contacts by company name asynchronously
        /// </summary>
        public async Task<List<Contact>> GetByCompanyAsync(string company)
        {
            if (string.IsNullOrWhiteSpace(company))
                return new List<Contact>();

            return await _context.Contacts
                .Where(c => c.Company != null && c.Company.ToLower() == company.ToLower())
                .OrderBy(c => c.LastName)
                .ToListAsync();
        }

        /// <summary>
        /// Gets all favorite contacts
        /// </summary>
        public List<Contact> GetFavorites()
        {
            return _context.Contacts
                .Where(c => c.IsFavorite)
                .OrderBy(c => c.LastName)
                .ToList();
        }

        /// <summary>
        /// Gets all favorite contacts asynchronously
        /// </summary>
        public async Task<List<Contact>> GetFavoritesAsync()
        {
            return await _context.Contacts
                .Where(c => c.IsFavorite)
                .OrderBy(c => c.LastName)
                .ToListAsync();
        }

        /// <summary>
        /// Gets contacts with pagination
        /// </summary>
        public List<Contact> GetPaged(int pageNumber, int pageSize)
        {
            if (pageNumber < 1)
                pageNumber = 1;

            if (pageSize < 1)
                pageSize = 10;

            return _context.Contacts
                .OrderBy(c => c.LastName)
                .ThenBy(c => c.FirstName)
                .Skip((pageNumber - 1) * pageSize)
                .Take(pageSize)
                .ToList();
        }

        /// <summary>
        /// Gets contacts with pagination asynchronously
        /// </summary>
        public async Task<List<Contact>> GetPagedAsync(int pageNumber, int pageSize)
        {
            if (pageNumber < 1)
                pageNumber = 1;

            if (pageSize < 1)
                pageSize = 10;

            return await _context.Contacts
                .OrderBy(c => c.LastName)
                .ThenBy(c => c.FirstName)
                .Skip((pageNumber - 1) * pageSize)
                .Take(pageSize)
                .ToListAsync();
        }

        #endregion

        #region Utility Operations

        /// <summary>
        /// Gets the total count of contacts
        /// </summary>
        public int GetCount()
        {
            return _context.Contacts.Count();
        }

        /// <summary>
        /// Gets the total count of contacts asynchronously
        /// </summary>
        public async Task<int> GetCountAsync()
        {
            return await _context.Contacts.CountAsync();
        }

        /// <summary>
        /// Checks if a contact with the given email exists
        /// </summary>
        public bool EmailExists(string email)
        {
            if (string.IsNullOrWhiteSpace(email))
                return false;

            return _context.Contacts.Any(c => c.Email.ToLower() == email.ToLower());
        }

        /// <summary>
        /// Checks if a contact with the given email exists asynchronously
        /// </summary>
        public async Task<bool> EmailExistsAsync(string email)
        {
            if (string.IsNullOrWhiteSpace(email))
                return false;

            return await _context.Contacts.AnyAsync(c => c.Email.ToLower() == email.ToLower());
        }

        #endregion

        #region IDisposable Implementation

        /// <summary>
        /// Disposes the DbContext
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Protected dispose method
        /// </summary>
        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    _context?.Dispose();
                }
                _disposed = true;
            }
        }

        #endregion
    }
}

/* INSTRUCTOR NOTES:
 * 
 * LINQ Query Methods Used:
 * - Where(): Filters data based on condition
 * - OrderBy()/ThenBy(): Sorts data
 * - ToList()/ToListAsync(): Executes query and returns List
 * - Find()/FindAsync(): Gets entity by primary key (checks cache first)
 * - Any()/AnyAsync(): Checks if any element matches condition
 * - Count()/CountAsync(): Counts elements
 * - Skip()/Take(): Used for pagination
 * - Contains(): String search (case-sensitive)
 * 
 * Performance Tips:
 * - Use AsNoTracking() for read-only queries
 * - Avoid Select() for all columns (use projection)
 * - Use pagination for large datasets
 * - Index frequently queried columns (Email, LastName, Company)
 * - Use async for I/O-bound operations
 * 
 * Common LINQ Mistakes:
 * - Forgetting ToList() - query won't execute
 * - Using .Result or .Wait() on async methods - causes deadlocks
 * - Not checking for null before accessing navigation properties
 * - Using client-side evaluation for complex logic
 * 
 * Dispose Pattern:
 * - Implements IDisposable for proper resource cleanup
 * - DbContext holds database connections
 * - Use 'using' statement or call Dispose() explicitly
 * - GC.SuppressFinalize prevents finalizer from running
 */
