{{!-- SmartStack Repository Test Template --}}
{{!-- Generates integration tests for repositories following SmartStack conventions --}}

using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Xunit;
using {{namespace}}.Domain.Entities;
using {{namespace}}.Infrastructure.Data;
using {{namespace}}.Infrastructure.Repositories;

namespace {{namespace}}.Tests.Integration.Repositories;

/// <summary>
/// Integration tests for <see cref="{{name}}Repository"/>.
/// Uses in-memory database for isolation.
/// Follows SmartStack testing conventions: {Method}_When{Condition}_Should{Result}
/// </summary>
public class {{name}}RepositoryTests : IDisposable
{
    private readonly ApplicationDbContext _context;
    private readonly {{name}}Repository _sut;
    {{#unless isSystemEntity}}
    private readonly Guid _tenantId = Guid.NewGuid();
    {{/unless}}

    public {{name}}RepositoryTests()
    {
        var options = new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        _context = new ApplicationDbContext(options);
        _sut = new {{name}}Repository(_context);
    }

    public void Dispose()
    {
        _context.Dispose();
    }

    #region GetByIdAsync Tests

    [Fact]
    public async Task GetByIdAsync_WhenExists_ShouldReturnEntity()
    {
        // Arrange
        var entity = CreateAndSave{{name}}();

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

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

    [Fact]
    public async Task GetByIdAsync_WhenNotExists_ShouldReturnNull()
    {
        // Arrange
        var nonExistentId = Guid.NewGuid();

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

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

    {{#if includeSoftDelete}}
    [Fact]
    public async Task GetByIdAsync_WhenSoftDeleted_ShouldReturnNull()
    {
        // Arrange
        var entity = CreateAndSave{{name}}();
        entity.SoftDelete("deleter@example.com");
        await _context.SaveChangesAsync();

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

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

    [Fact]
    public async Task GetByIdAsync_WhenSoftDeletedAndIncludeDeleted_ShouldReturnEntity()
    {
        // Arrange
        var entity = CreateAndSave{{name}}();
        entity.SoftDelete("deleter@example.com");
        await _context.SaveChangesAsync();

        // Act
        var result = await _sut.GetByIdAsync(entity.Id, includeDeleted: true);

        // Assert
        result.Should().NotBeNull();
        result!.IsDeleted.Should().BeTrue();
    }
    {{/if}}

    #endregion

    #region GetAllAsync Tests

    [Fact]
    public async Task GetAllAsync_WhenEntitiesExist_ShouldReturnAll()
    {
        // Arrange
        CreateAndSave{{name}}("CODE-001");
        CreateAndSave{{name}}("CODE-002");
        CreateAndSave{{name}}("CODE-003");

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

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

    [Fact]
    public async Task GetAllAsync_WhenEmpty_ShouldReturnEmptyList()
    {
        // Act
        var result = await _sut.GetAllAsync();

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

    {{#if includeSoftDelete}}
    [Fact]
    public async Task GetAllAsync_ShouldExcludeSoftDeleted()
    {
        // Arrange
        CreateAndSave{{name}}("ACTIVE-001");
        var deleted = CreateAndSave{{name}}("DELETED-001");
        deleted.SoftDelete("deleter@example.com");
        await _context.SaveChangesAsync();

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

        // Assert
        result.Should().HaveCount(1);
        result.Should().OnlyContain(x => !x.IsDeleted);
    }
    {{/if}}

    #endregion

    #region AddAsync Tests

    [Fact]
    public async Task AddAsync_WhenValidEntity_ShouldPersist()
    {
        // Arrange
        {{#if isSystemEntity}}
        var entity = {{name}}.Create("NEW-001", "test@example.com");
        {{else}}
        var entity = {{name}}.Create(_tenantId, "NEW-001", "test@example.com");
        {{/if}}

        // Act
        await _sut.AddAsync(entity);
        await _context.SaveChangesAsync();

        // Assert
        var persisted = await _context.Set<{{name}}>().FindAsync(entity.Id);
        persisted.Should().NotBeNull();
        persisted!.Code.Should().Be("NEW-001");
    }

    [Fact]
    public async Task AddAsync_ShouldGenerateId()
    {
        // Arrange
        {{#if isSystemEntity}}
        var entity = {{name}}.Create("TEST", "test@example.com");
        {{else}}
        var entity = {{name}}.Create(_tenantId, "TEST", "test@example.com");
        {{/if}}

        // Act
        await _sut.AddAsync(entity);
        await _context.SaveChangesAsync();

        // Assert
        entity.Id.Should().NotBe(Guid.Empty);
    }

    #endregion

    #region Update Tests

    [Fact]
    public async Task Update_WhenEntityModified_ShouldPersistChanges()
    {
        // Arrange
        var entity = CreateAndSave{{name}}("ORIGINAL");
        entity.Update("updater@example.com");

        // Act
        await _context.SaveChangesAsync();

        // Assert
        var updated = await _context.Set<{{name}}>().FindAsync(entity.Id);
        updated!.UpdatedBy.Should().Be("updater@example.com");
    }

    #endregion

    #region Remove Tests

    [Fact]
    public async Task Remove_WhenCalled_ShouldDeleteEntity()
    {
        // Arrange
        var entity = CreateAndSave{{name}}();
        var entityId = entity.Id;

        // Act
        _sut.Remove(entity);
        await _context.SaveChangesAsync();

        // Assert
        var deleted = await _context.Set<{{name}}>().FindAsync(entityId);
        deleted.Should().BeNull();
    }

    #endregion

    #region ExistsByCodeAsync Tests

    [Fact]
    public async Task ExistsByCodeAsync_WhenExists_ShouldReturnTrue()
    {
        // Arrange
        CreateAndSave{{name}}("EXISTING");

        // Act
        var result = await _sut.ExistsByCodeAsync("EXISTING");

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

    [Fact]
    public async Task ExistsByCodeAsync_WhenNotExists_ShouldReturnFalse()
    {
        // Act
        var result = await _sut.ExistsByCodeAsync("NON-EXISTENT");

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

    {{#if includeSoftDelete}}
    [Fact]
    public async Task ExistsByCodeAsync_WhenSoftDeleted_ShouldReturnFalse()
    {
        // Arrange
        var entity = CreateAndSave{{name}}("DELETED");
        entity.SoftDelete("deleter@example.com");
        await _context.SaveChangesAsync();

        // Act
        var result = await _sut.ExistsByCodeAsync("DELETED");

        // Assert
        result.Should().BeFalse();
    }
    {{/if}}

    #endregion

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

    [Fact]
    public async Task GetAllAsync_ShouldOnlyReturnCurrentTenantEntities()
    {
        // Arrange
        var tenantA = Guid.NewGuid();
        var tenantB = Guid.NewGuid();

        CreateAndSave{{name}}("A-001", tenantA);
        CreateAndSave{{name}}("A-002", tenantA);
        CreateAndSave{{name}}("B-001", tenantB);

        // Create a tenant-scoped repository for tenant A
        var tenantARepo = new {{name}}Repository(_context, tenantA);

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

        // Assert
        result.Should().HaveCount(2);
        result.Should().OnlyContain(x => x.TenantId == tenantA);
    }

    [Fact]
    public async Task GetByIdAsync_WhenDifferentTenant_ShouldReturnNull()
    {
        // Arrange
        var tenantA = Guid.NewGuid();
        var tenantB = Guid.NewGuid();
        var entity = CreateAndSave{{name}}("TEST", tenantA);

        var tenantBRepo = new {{name}}Repository(_context, tenantB);

        // Act
        var result = await tenantBRepo.GetByIdAsync(entity.Id);

        // Assert
        result.Should().BeNull("entity belongs to different tenant");
    }

    [Fact]
    public async Task AddAsync_ShouldEnforceTenantId()
    {
        // Arrange
        var entity = {{name}}.Create(_tenantId, "TEST", "test@example.com");

        // Act
        await _sut.AddAsync(entity);
        await _context.SaveChangesAsync();

        // Assert
        var persisted = await _context.Set<{{name}}>().FindAsync(entity.Id);
        persisted!.TenantId.Should().Be(_tenantId);
    }

    #endregion
    {{/unless}}

    #region Query Tests

    [Fact]
    public async Task FindByConditionAsync_ShouldFilterCorrectly()
    {
        // Arrange
        CreateAndSave{{name}}("ALPHA");
        CreateAndSave{{name}}("BETA");
        CreateAndSave{{name}}("ALPHA-2");

        // Act
        var result = await _sut.FindByConditionAsync(x => x.Code.StartsWith("ALPHA"));

        // Assert
        result.Should().HaveCount(2);
        result.Should().OnlyContain(x => x.Code.StartsWith("ALPHA"));
    }

    [Fact]
    public async Task CountAsync_ShouldReturnCorrectCount()
    {
        // Arrange
        CreateAndSave{{name}}("ONE");
        CreateAndSave{{name}}("TWO");
        CreateAndSave{{name}}("THREE");

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

        // Assert
        result.Should().Be(3);
    }

    #endregion

    #region Pagination Tests

    [Fact]
    public async Task GetPagedAsync_ShouldReturnCorrectPage()
    {
        // Arrange
        for (int i = 1; i <= 25; i++)
        {
            CreateAndSave{{name}}($"CODE-{i:D3}");
        }

        // Act
        var result = await _sut.GetPagedAsync(page: 2, pageSize: 10);

        // Assert
        result.Items.Should().HaveCount(10);
        result.TotalCount.Should().Be(25);
        result.TotalPages.Should().Be(3);
        result.CurrentPage.Should().Be(2);
    }

    #endregion

    #region Concurrency Tests

    [Fact]
    public async Task Update_WhenConcurrentModification_ShouldThrowException()
    {
        // Arrange
        var entity = CreateAndSave{{name}}();

        // Simulate concurrent modification
        var concurrentContext = new ApplicationDbContext(
            new DbContextOptionsBuilder<ApplicationDbContext>()
                .UseInMemoryDatabase(_context.Database.GetDbConnection().Database)
                .Options
        );
        var concurrentEntity = await concurrentContext.Set<{{name}}>().FindAsync(entity.Id);
        concurrentEntity!.Update("concurrent@example.com");
        await concurrentContext.SaveChangesAsync();

        // Act
        entity.Update("original@example.com");
        var act = () => _context.SaveChangesAsync();

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

    #endregion

    #region Helper Methods

    private {{name}} CreateAndSave{{name}}(string code = "TEST-001"{{#unless isSystemEntity}}, Guid? tenantId = null{{/unless}})
    {
        {{#if isSystemEntity}}
        var entity = {{name}}.Create(code, "test@example.com");
        {{else}}
        var entity = {{name}}.Create(tenantId ?? _tenantId, code, "test@example.com");
        {{/if}}

        _context.Set<{{name}}>().Add(entity);
        _context.SaveChanges();

        return entity;
    }

    #endregion
}
