{{!-- SmartStack Service Test Template --}}
{{!-- Generates unit tests for application services following SmartStack conventions --}}

using FluentAssertions;
using Moq;
using Xunit;
using {{namespace}}.Application.Services;
using {{namespace}}.Application.Interfaces;
using {{namespace}}.Domain.Entities;
using {{namespace}}.Domain.Interfaces;

namespace {{namespace}}.Tests.Unit.Services;

/// <summary>
/// Unit tests for <see cref="{{name}}Service"/>.
/// Follows SmartStack testing conventions: {Method}_When{Condition}_Should{Result}
/// </summary>
public class {{name}}ServiceTests
{
    private readonly Mock<I{{name}}Repository> _mockRepository;
    private readonly Mock<IUnitOfWork> _mockUnitOfWork;
    {{#unless isSystemEntity}}
    private readonly Mock<ITenantContext> _mockTenantContext;
    {{/unless}}
    private readonly {{name}}Service _sut;

    public {{name}}ServiceTests()
    {
        _mockRepository = new Mock<I{{name}}Repository>();
        _mockUnitOfWork = new Mock<IUnitOfWork>();
        {{#unless isSystemEntity}}
        _mockTenantContext = new Mock<ITenantContext>();
        _mockTenantContext.Setup(x => x.TenantId).Returns(Guid.NewGuid());
        {{/unless}}

        _sut = new {{name}}Service(
            _mockRepository.Object,
            _mockUnitOfWork.Object{{#unless isSystemEntity}},
            _mockTenantContext.Object{{/unless}}
        );
    }

    #region GetById Tests

    [Fact]
    public async Task GetByIdAsync_WhenExists_ShouldReturnEntity()
    {
        // Arrange
        var id = Guid.NewGuid();
        var entity = CreateValid{{name}}(id);
        _mockRepository.Setup(x => x.GetByIdAsync(id, It.IsAny<CancellationToken>()))
            .ReturnsAsync(entity);

        // Act
        var result = await _sut.GetByIdAsync(id);

        // Assert
        result.Should().NotBeNull();
        result!.Id.Should().Be(id);
    }

    [Fact]
    public async Task GetByIdAsync_WhenNotExists_ShouldReturnNull()
    {
        // Arrange
        var id = Guid.NewGuid();
        _mockRepository.Setup(x => x.GetByIdAsync(id, It.IsAny<CancellationToken>()))
            .ReturnsAsync(({{name}}?)null);

        // Act
        var result = await _sut.GetByIdAsync(id);

        // Assert
        result.Should().BeNull();
    }

    #endregion

    #region GetAll Tests

    [Fact]
    public async Task GetAllAsync_WhenEntitiesExist_ShouldReturnAll()
    {
        // Arrange
        var entities = new List<{{name}}>
        {
            CreateValid{{name}}(),
            CreateValid{{name}}()
        };
        _mockRepository.Setup(x => x.GetAllAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(entities);

        // Act
        var result = await _sut.GetAllAsync();

        // Assert
        result.Should().HaveCount(2);
    }

    [Fact]
    public async Task GetAllAsync_WhenNoEntities_ShouldReturnEmptyList()
    {
        // Arrange
        _mockRepository.Setup(x => x.GetAllAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<{{name}}>());

        // Act
        var result = await _sut.GetAllAsync();

        // Assert
        result.Should().BeEmpty();
    }

    #endregion

    #region Create Tests

    [Fact]
    public async Task CreateAsync_WhenValidData_ShouldCreateAndReturnEntity()
    {
        // Arrange
        var request = new Create{{name}}Request
        {
            Code = "NEW-001",
            // Add other required properties
        };

        _mockRepository.Setup(x => x.AddAsync(It.IsAny<{{name}}>(), It.IsAny<CancellationToken>()))
            .Returns(Task.CompletedTask);
        _mockUnitOfWork.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(1);

        // Act
        var result = await _sut.CreateAsync(request);

        // Assert
        result.Should().NotBeNull();
        result.Code.Should().Be(request.Code);
        _mockRepository.Verify(x => x.AddAsync(It.IsAny<{{name}}>(), It.IsAny<CancellationToken>()), Times.Once);
        _mockUnitOfWork.Verify(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task CreateAsync_WhenDuplicateCode_ShouldThrowException()
    {
        // Arrange
        var request = new Create{{name}}Request { Code = "EXISTING-001" };
        _mockRepository.Setup(x => x.ExistsByCodeAsync(request.Code, It.IsAny<CancellationToken>()))
            .ReturnsAsync(true);

        // Act
        var act = () => _sut.CreateAsync(request);

        // Assert
        await act.Should().ThrowAsync<BusinessException>()
            .WithMessage("*already exists*");
    }

    #endregion

    #region Update Tests

    [Fact]
    public async Task UpdateAsync_WhenExists_ShouldUpdateEntity()
    {
        // Arrange
        var id = Guid.NewGuid();
        var entity = CreateValid{{name}}(id);
        var request = new Update{{name}}Request
        {
            // Add properties to update
        };

        _mockRepository.Setup(x => x.GetByIdAsync(id, It.IsAny<CancellationToken>()))
            .ReturnsAsync(entity);
        _mockUnitOfWork.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(1);

        // Act
        var result = await _sut.UpdateAsync(id, request);

        // Assert
        result.Should().NotBeNull();
        _mockUnitOfWork.Verify(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task UpdateAsync_WhenNotExists_ShouldThrowException()
    {
        // Arrange
        var id = Guid.NewGuid();
        var request = new Update{{name}}Request();
        _mockRepository.Setup(x => x.GetByIdAsync(id, It.IsAny<CancellationToken>()))
            .ReturnsAsync(({{name}}?)null);

        // Act
        var act = () => _sut.UpdateAsync(id, request);

        // Assert
        await act.Should().ThrowAsync<NotFoundException>();
    }

    #endregion

    #region Delete Tests

    {{#if includeSoftDelete}}
    [Fact]
    public async Task DeleteAsync_WhenExists_ShouldSoftDelete()
    {
        // Arrange
        var id = Guid.NewGuid();
        var entity = CreateValid{{name}}(id);
        _mockRepository.Setup(x => x.GetByIdAsync(id, It.IsAny<CancellationToken>()))
            .ReturnsAsync(entity);
        _mockUnitOfWork.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(1);

        // Act
        await _sut.DeleteAsync(id);

        // Assert
        entity.IsDeleted.Should().BeTrue();
        _mockUnitOfWork.Verify(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task DeleteAsync_WhenAlreadyDeleted_ShouldThrowException()
    {
        // Arrange
        var id = Guid.NewGuid();
        var entity = CreateValid{{name}}(id);
        entity.SoftDelete("previous@example.com");
        _mockRepository.Setup(x => x.GetByIdAsync(id, It.IsAny<CancellationToken>()))
            .ReturnsAsync(entity);

        // Act
        var act = () => _sut.DeleteAsync(id);

        // Assert
        await act.Should().ThrowAsync<BusinessException>()
            .WithMessage("*already deleted*");
    }
    {{else}}
    [Fact]
    public async Task DeleteAsync_WhenExists_ShouldDelete()
    {
        // Arrange
        var id = Guid.NewGuid();
        var entity = CreateValid{{name}}(id);
        _mockRepository.Setup(x => x.GetByIdAsync(id, It.IsAny<CancellationToken>()))
            .ReturnsAsync(entity);
        _mockUnitOfWork.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(1);

        // Act
        await _sut.DeleteAsync(id);

        // Assert
        _mockRepository.Verify(x => x.Remove(entity), Times.Once);
        _mockUnitOfWork.Verify(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }
    {{/if}}

    [Fact]
    public async Task DeleteAsync_WhenNotExists_ShouldThrowException()
    {
        // Arrange
        var id = Guid.NewGuid();
        _mockRepository.Setup(x => x.GetByIdAsync(id, It.IsAny<CancellationToken>()))
            .ReturnsAsync(({{name}}?)null);

        // Act
        var act = () => _sut.DeleteAsync(id);

        // Assert
        await act.Should().ThrowAsync<NotFoundException>();
    }

    #endregion

    {{#unless isSystemEntity}}
    #region Tenant Isolation Tests

    [Fact]
    public async Task CreateAsync_ShouldUseCurrentTenantId()
    {
        // Arrange
        var expectedTenantId = Guid.NewGuid();
        _mockTenantContext.Setup(x => x.TenantId).Returns(expectedTenantId);

        var request = new Create{{name}}Request { Code = "TEST" };
        {{name}}? capturedEntity = null;
        _mockRepository.Setup(x => x.AddAsync(It.IsAny<{{name}}>(), It.IsAny<CancellationToken>()))
            .Callback<{{name}}, CancellationToken>((e, _) => capturedEntity = e)
            .Returns(Task.CompletedTask);
        _mockUnitOfWork.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(1);

        // Act
        await _sut.CreateAsync(request);

        // Assert
        capturedEntity.Should().NotBeNull();
        capturedEntity!.TenantId.Should().Be(expectedTenantId);
    }

    [Fact]
    public async Task GetByIdAsync_ShouldRespectTenantScope()
    {
        // Arrange
        var id = Guid.NewGuid();
        var tenantId = Guid.NewGuid();
        _mockTenantContext.Setup(x => x.TenantId).Returns(tenantId);

        // Act
        await _sut.GetByIdAsync(id);

        // Assert
        _mockRepository.Verify(
            x => x.GetByIdAsync(id, It.IsAny<CancellationToken>()),
            Times.Once
        );
        // Repository should be configured to filter by tenant
    }

    #endregion
    {{/unless}}

    #region Error Handling Tests

    [Fact]
    public async Task CreateAsync_WhenRepositoryFails_ShouldThrowException()
    {
        // Arrange
        var request = new Create{{name}}Request { Code = "TEST" };
        _mockRepository.Setup(x => x.AddAsync(It.IsAny<{{name}}>(), It.IsAny<CancellationToken>()))
            .ThrowsAsync(new Exception("Database error"));

        // Act
        var act = () => _sut.CreateAsync(request);

        // Assert
        await act.Should().ThrowAsync<Exception>()
            .WithMessage("*Database error*");
    }

    [Fact]
    public async Task UpdateAsync_WhenConcurrencyConflict_ShouldThrowException()
    {
        // Arrange
        var id = Guid.NewGuid();
        var entity = CreateValid{{name}}(id);
        var request = new Update{{name}}Request();

        _mockRepository.Setup(x => x.GetByIdAsync(id, It.IsAny<CancellationToken>()))
            .ReturnsAsync(entity);
        _mockUnitOfWork.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()))
            .ThrowsAsync(new DbUpdateConcurrencyException());

        // Act
        var act = () => _sut.UpdateAsync(id, request);

        // Assert
        await act.Should().ThrowAsync<ConcurrencyException>();
    }

    #endregion

    #region Helper Methods

    private static {{name}} CreateValid{{name}}(Guid? id = null)
    {
        {{#if isSystemEntity}}
        var entity = {{name}}.Create("TEST-001", "test@example.com");
        {{else}}
        var entity = {{name}}.Create(Guid.NewGuid(), "TEST-001", "test@example.com");
        {{/if}}

        if (id.HasValue)
        {
            // Use reflection to set Id for testing
            typeof({{name}}).GetProperty("Id")!.SetValue(entity, id.Value);
        }

        return entity;
    }

    #endregion
}
