using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using NUnit.Framework;
using Moq;
using ContactManagementSystem.Models;
using ContactManagementSystem.Data.Repositories;
using ContactManagementSystem.Services;

namespace ContactManagementSystem.Tests
{
    [TestFixture]
    public class ContactServiceTests
    {
        private Mock<IContactRepository> _mockRepository;
        private ContactService _service;
        private List<Contact> _testContacts;

        #region Setup and Teardown

        [SetUp]
        public void SetUp()
        {
            // Create fresh mock repository before each test
            _mockRepository = new Mock<IContactRepository>();

            // Create service with mocked repository
            _service = new ContactService(_mockRepository.Object);

            // Setup test data
            _testContacts = new List<Contact>
            {
                new Contact
                {
                    Id = 1,
                    FirstName = "John",
                    LastName = "Doe",
                    Email = "john@test.com",
                    PhoneNumber = "555-555-0001",
                    IsFavorite = true
                },
                new Contact
                {
                    Id = 2,
                    FirstName = "Jane",
                    LastName = "Smith",
                    Email = "jane@test.com",
                    PhoneNumber = "555-555-0001",
                    IsFavorite = false
                },
                new Contact
                {
                    Id = 3,
                    FirstName = "Bob",
                    LastName = "Johnson",
                    Email = "bob@test.com",
                    PhoneNumber = "555-555-0001",
                    Company = "Tech Corp",
                    IsFavorite = true
                } //  new
            }; // list
        } // setup()

        [TearDown]
        public void TearDown()
        {
            // Clean up resources after each test
            _service?.Dispose();
            _mockRepository = null;
            _testContacts = null;
        }

        #endregion

        #region GetAllContactsAsync Tests

        [Test]
        public async Task GetAllContactsAsync_ReturnsAllContacts()
        {
            // Arrange
            _mockRepository.Setup(r => r.GetAllAsync())
                .ReturnsAsync(_testContacts);

            // Act
            var result = await _service.GetAllContactsAsync();

            // Assert
            Assert.IsNotNull(result);
            Assert.AreEqual(3, result.Count);
            _mockRepository.Verify(r => r.GetAllAsync(), Times.Once);
        }

        [Test]
        public async Task GetAllContactsAsync_WhenNoContacts_ReturnsEmptyList()
        {
            // Arrange
            _mockRepository.Setup(r => r.GetAllAsync())
                .ReturnsAsync(new List<Contact>());

            // Act
            var result = await _service.GetAllContactsAsync();

            // Assert
            Assert.IsNotNull(result);
            Assert.AreEqual(0, result.Count);
        }

        #endregion

        #region GetContactByIdAsync Tests

        [Test]
        public async Task GetContactByIdAsync_ValidId_ReturnsContact()
        {
            // Arrange
            var expectedContact = _testContacts[0];
            _mockRepository.Setup(r => r.GetByIdAsync(1))
                .ReturnsAsync(expectedContact);

            // Act
            var result = await _service.GetContactByIdAsync(1);

            // Assert
            Assert.IsNotNull(result);
            Assert.AreEqual("John", result.FirstName);
            Assert.AreEqual("Doe", result.LastName);
        }

        [Test]
        public async Task GetContactByIdAsync_InvalidId_ReturnsNull()
        {
            // Arrange
            _mockRepository.Setup(r => r.GetByIdAsync(999))
                .ReturnsAsync((Contact)null);

            // Act
            var result = await _service.GetContactByIdAsync(999);

            // Assert
            Assert.IsNull(result);
        }

        [Test]
        public void GetContactByIdAsync_ZeroId_ThrowsArgumentException()
        {
            // Act & Assert
            var ex = Assert.ThrowsAsync<ArgumentException>(
                async () => await _service.GetContactByIdAsync(0));

            Assert.That(ex.ParamName, Is.EqualTo("id"));
        }

        [Test]
        public void GetContactByIdAsync_NegativeId_ThrowsArgumentException()
        {
            // Act & Assert
            var ex = Assert.ThrowsAsync<ArgumentException>(
                async () => await _service.GetContactByIdAsync(-1));

            Assert.That(ex.ParamName, Is.EqualTo("id"));
        }

        #endregion

        #region CreateContactAsync Tests

        [Test]
        public async Task CreateContactAsync_ValidContact_ReturnsCreatedContact()
        {
            // Arrange
            var newContact = new Contact
            {
                FirstName = "Alice",
                LastName = "Williams",
                Email = "alice@test.com",
                PhoneNumber = "555-555-0001"
            };

            _mockRepository.Setup(r => r.EmailExistsAsync(newContact.Email))
                .ReturnsAsync(false);

            _mockRepository.Setup(r => r.AddAsync(It.IsAny<Contact>()))
                .ReturnsAsync((Contact c) => { c.Id = 4; return c; });

            // Act
            var result = await _service.CreateContactAsync(newContact);

            // Assert
            Assert.IsNotNull(result);
            Assert.AreEqual(4, result.Id);
            Assert.AreEqual("Alice", result.FirstName);
            _mockRepository.Verify(r => r.AddAsync(It.IsAny<Contact>()), Times.Once);
        }

        [Test]
        public void CreateContactAsync_NullContact_ThrowsArgumentNullException()
        {
            // Act & Assert
            Assert.ThrowsAsync<ArgumentNullException>(
                async () => await _service.CreateContactAsync(null));
        }

        [Test]
        public void CreateContactAsync_EmptyFirstName_ThrowsArgumentException()
        {
            // Arrange
            var contact = new Contact
            {
                FirstName = "",
                LastName = "Doe",
                Email = "email@test.com",
                PhoneNumber = "555-555-0001"
            };

            // Act & Assert
            var ex = Assert.ThrowsAsync<ArgumentException>(
                async () => await _service.CreateContactAsync(contact));

            Assert.That(ex.Message, Does.Contain("FirstName"));
        }

        [Test]
        public void CreateContactAsync_NullFirstName_ThrowsArgumentException()
        {
            // Arrange
            var contact = new Contact
            {
                FirstName = null,
                LastName = "Doe",
                Email = "email@test.com",
                PhoneNumber = "555-555-0001"
            };

            // Act & Assert
            var ex = Assert.ThrowsAsync<ArgumentException>(
                async () => await _service.CreateContactAsync(contact));

            Assert.That(ex.Message, Does.Contain("FirstName"));
        }

        [Test]
        public void CreateContactAsync_EmptyLastName_ThrowsArgumentException()
        {
            // Arrange
            var contact = new Contact
            {
                FirstName = "John",
                LastName = "",
                Email = "email@test.com",
                PhoneNumber = "555-555-0001"
            };

            // Act & Assert
            var ex = Assert.ThrowsAsync<ArgumentException>(
                async () => await _service.CreateContactAsync(contact));

            Assert.That(ex.Message, Does.Contain("LastName"));
        }

        [Test]
        public void CreateContactAsync_NullLastName_ThrowsArgumentException()
        {
            // Arrange
            var contact = new Contact
            {
                FirstName = "John",
                LastName = null,
                Email = "email@test.com",
                PhoneNumber = "555-555-0001"
            };

            // Act & Assert
            var ex = Assert.ThrowsAsync<ArgumentException>(
                async () => await _service.CreateContactAsync(contact));

            Assert.That(ex.Message, Does.Contain("LastName"));
        }

        [Test]
        public void CreateContactAsync_EmptyEmail_ThrowsArgumentException()
        {
            // Arrange
            var contact = new Contact
            {
                FirstName = "John",
                LastName = "Doe",
                Email = "",
                PhoneNumber = "555-555-0001"
            };

            // Act & Assert
            var ex = Assert.ThrowsAsync<ArgumentException>(
                async () => await _service.CreateContactAsync(contact));

            Assert.That(ex.Message, Does.Contain("Email"));
        }

        [Test]
        public void CreateContactAsync_NullEmail_ThrowsArgumentException()
        {
            // Arrange
            var contact = new Contact
            {
                FirstName = "John",
                LastName = "Doe",
                Email = null,
                PhoneNumber = "555-555-0001"
            };

            // Act & Assert
            var ex = Assert.ThrowsAsync<ArgumentException>(
                async () => await _service.CreateContactAsync(contact));

            Assert.That(ex.Message, Does.Contain("Email"));
        }

        [Test]
        public void CreateContactAsync_EmptyPhoneNumber_ThrowsArgumentException()
        {
            // Arrange
            var contact = new Contact
            {
                FirstName = "John",
                LastName = "Doe",
                Email = "email@test.com",
                PhoneNumber = "555-555-0001"
            };

            // Act & Assert
            var ex = Assert.ThrowsAsync<ArgumentException>(
                async () => await _service.CreateContactAsync(contact));

            Assert.That(ex.Message, Does.Contain("PhoneNumber"));
        }

        [Test]
        public void CreateContactAsync_NullPhoneNumber_ThrowsArgumentException()
        {
            // Arrange
            var contact = new Contact
            {
                FirstName = "John",
                LastName = "Doe",
                Email = "email@test.com",
                PhoneNumber = "555-555-0001"
            };

            // Act & Assert
            var ex = Assert.ThrowsAsync<ArgumentException>(
                async () => await _service.CreateContactAsync(contact));

            Assert.That(ex.Message, Does.Contain("PhoneNumber"));
        }

        [Test]
        public void CreateContactAsync_DuplicateEmail_ThrowsInvalidOperationException()
        {
            // Arrange
            var newContact = new Contact
            {
                FirstName = "Test",
                LastName = "User",
                Email = "john@test.com",  // Duplicate email
                PhoneNumber = "555-555-0001"
            };

            _mockRepository.Setup(r => r.EmailExistsAsync("john@test.com"))
                .ReturnsAsync(true);

            // Act & Assert
            var ex = Assert.ThrowsAsync<InvalidOperationException>(
                async () => await _service.CreateContactAsync(newContact));

            Assert.That(ex.Message, Does.Contain("already exists"));
        }

        #endregion

        #region UpdateContactAsync Tests

        [Test]
        public async Task UpdateContactAsync_ValidContact_ReturnsTrue()
        {
            // Arrange
            var contactToUpdate = _testContacts[0];
            contactToUpdate.FirstName = "Updated";

            _mockRepository.Setup(r => r.GetByIdAsync(1))
                .ReturnsAsync(_testContacts[0]);

            _mockRepository.Setup(r => r.UpdateAsync(It.IsAny<Contact>()))
                .ReturnsAsync(true);

            // Act
            var result = await _service.UpdateContactAsync(contactToUpdate);

            // Assert
            Assert.IsTrue(result);
            _mockRepository.Verify(r => r.UpdateAsync(It.IsAny<Contact>()), Times.Once);
        }

        [Test]
        public void UpdateContactAsync_ContactNotFound_ThrowsInvalidOperationException()
        {
            // Arrange
            var contact = new Contact { Id = 999, FirstName = "Test", LastName = "User" };

            _mockRepository.Setup(r => r.GetByIdAsync(999))
                .ReturnsAsync((Contact)null);

            // Act & Assert
            var ex = Assert.ThrowsAsync<InvalidOperationException>(
                async () => await _service.UpdateContactAsync(contact));

            Assert.That(ex.Message, Does.Contain("not found"));
        }

        #endregion

        #region DeleteContactAsync Tests

        [Test]
        public async Task DeleteContactAsync_ExistingContact_ReturnsTrue()
        {
            // Arrange
            _mockRepository.Setup(r => r.GetByIdAsync(1))
                .ReturnsAsync(_testContacts[0]);

            _mockRepository.Setup(r => r.DeleteAsync(1))
                .ReturnsAsync(true);

            // Act
            var result = await _service.DeleteContactAsync(1);

            // Assert
            Assert.IsTrue(result);
            _mockRepository.Verify(r => r.DeleteAsync(1), Times.Once);
        }

        [Test]
        public async Task DeleteContactAsync_NonExistingContact_ReturnsFalse()
        {
            // Arrange
            _mockRepository.Setup(r => r.GetByIdAsync(999))
                .ReturnsAsync((Contact)null);

            // Act
            var result = await _service.DeleteContactAsync(999);

            // Assert
            Assert.IsFalse(result);
        }

        #endregion

        #region SearchContactsAsync Tests

        [Test]
        public async Task SearchContactsAsync_ValidTerm_ReturnsMatchingContacts()
        {
            // Arrange
            var searchResults = new List<Contact> { _testContacts[0] };

            _mockRepository.Setup(r => r.SearchAsync("John"))
                .ReturnsAsync(searchResults);

            // Act
            var result = await _service.SearchContactsAsync("John");

            // Assert
            Assert.IsNotNull(result);
            Assert.AreEqual(1, result.Count);
            Assert.AreEqual("John", result[0].FirstName);
        }

        [Test]
        public async Task SearchContactsAsync_EmptyTerm_ReturnsEmptyList()
        {
            // Act
            var result = await _service.SearchContactsAsync("");

            // Assert
            Assert.IsNotNull(result);
            Assert.AreEqual(0, result.Count);
        }

        [Test]
        public async Task SearchContactsAsync_NullTerm_ReturnsEmptyList()
        {
            // Act
            var result = await _service.SearchContactsAsync(null);

            // Assert
            Assert.IsNotNull(result);
            Assert.AreEqual(0, result.Count);
        }

        [Test]
        public async Task SearchContactsAsync_WhitespaceTerm_ReturnsEmptyList()
        {
            // Act
            var result = await _service.SearchContactsAsync("   ");

            // Assert
            Assert.IsNotNull(result);
            Assert.AreEqual(0, result.Count);
        }

        #endregion

        #region GetFavoriteContactsAsync Tests

        [Test]
        public async Task GetFavoriteContactsAsync_ReturnsFavorites()
        {
            // Arrange
            var favorites = new List<Contact> { _testContacts[0], _testContacts[2] };

            _mockRepository.Setup(r => r.GetFavoritesAsync())
                .ReturnsAsync(favorites);

            // Act
            var result = await _service.GetFavoriteContactsAsync();

            // Assert
            Assert.IsNotNull(result);
            Assert.AreEqual(2, result.Count);
            Assert.IsTrue(result[0].IsFavorite);
            Assert.IsTrue(result[1].IsFavorite);
        }

        #endregion

        #region ToggleFavoriteAsync Tests

        [Test]
        public async Task ToggleFavoriteAsync_TogglesFromFalseToTrue()
        {
            // Arrange
            var contact = _testContacts[1];  // IsFavorite = false

            _mockRepository.Setup(r => r.GetByIdAsync(2))
                .ReturnsAsync(contact);

            _mockRepository.Setup(r => r.UpdateAsync(It.Is<Contact>(c => c.IsFavorite == true)))
                .ReturnsAsync(true);

            // Act
            var result = await _service.ToggleFavoriteAsync(2);

            // Assert
            Assert.IsTrue(result);
            Assert.IsTrue(contact.IsFavorite);
            _mockRepository.Verify(r => r.UpdateAsync(It.IsAny<Contact>()), Times.Once);
        }

        [Test]
        public async Task ToggleFavoriteAsync_TogglesFromTrueToFalse()
        {
            // Arrange
            var contact = _testContacts[0];  // IsFavorite = true

            _mockRepository.Setup(r => r.GetByIdAsync(1))
                .ReturnsAsync(contact);

            _mockRepository.Setup(r => r.UpdateAsync(It.Is<Contact>(c => c.IsFavorite == false)))
                .ReturnsAsync(true);

            // Act
            var result = await _service.ToggleFavoriteAsync(1);

            // Assert
            Assert.IsTrue(result);
            Assert.IsFalse(contact.IsFavorite);
        }

        #endregion

        #region ValidateContact Tests

        [Test]
        public void ValidateContact_ValidContact_ReturnsTrue()
        {
            // Arrange
            var contact = _testContacts[0];

            // Act
            var result = _service.ValidateContact(contact);

            // Assert
            Assert.IsTrue(result);
        }

        [Test]
        public void ValidateContact_NullContact_ReturnsFalse()
        {
            // Act
            var result = _service.ValidateContact(null);

            // Assert
            Assert.IsFalse(result);
        }

        #endregion

        #region GetContactCountAsync Tests

        [Test]
        public async Task GetContactCountAsync_ReturnsCorrectCount()
        {
            // Arrange
            _mockRepository.Setup(r => r.GetCountAsync())
                .ReturnsAsync(3);

            // Act
            var result = await _service.GetContactCountAsync();

            // Assert
            Assert.AreEqual(3, result);
        }

        #endregion
    }
}