{{!-- SmartStack Controller Test Template --}}
{{!-- Generates integration tests for API controllers following SmartStack conventions --}}

using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using System.Net;
using System.Net.Http.Json;
using Xunit;
using {{namespace}}.Api;
using {{namespace}}.Api.Controllers;
using {{namespace}}.Application.DTOs;
using {{namespace}}.Application.Interfaces;
using {{namespace}}.Domain.Entities;

namespace {{namespace}}.Tests.Integration.Controllers;

/// <summary>
/// Integration tests for <see cref="{{name}}Controller"/>.
/// Follows SmartStack testing conventions: {Method}_When{Condition}_Should{Result}
/// </summary>
public class {{name}}ControllerTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;
    private readonly HttpClient _client;
    private readonly Mock<I{{name}}Service> _mockService;

    public {{name}}ControllerTests(WebApplicationFactory<Program> factory)
    {
        _mockService = new Mock<I{{name}}Service>();

        _factory = factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureServices(services =>
            {
                // Remove the real service and add mock
                var descriptor = services.SingleOrDefault(
                    d => d.ServiceType == typeof(I{{name}}Service));
                if (descriptor != null)
                    services.Remove(descriptor);

                services.AddScoped(_ => _mockService.Object);
            });
        });

        _client = _factory.CreateClient();
    }

    #region GET Tests

    [Fact]
    public async Task GetAll_WhenCalled_ShouldReturn200WithList()
    {
        // Arrange
        var items = new PaginatedResult<{{name}}Response>
        {
            Items = new List<{{name}}Response>
            {
                new() { Id = Guid.NewGuid(), Code = "TEST-001" },
                new() { Id = Guid.NewGuid(), Code = "TEST-002" }
            },
            Total = 2,
            Page = 1,
            PageSize = 10
        };
        _mockService.Setup(x => x.GetAllAsync(It.IsAny<PaginationParams>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(items);

        // Act
        var response = await _client.GetAsync("/api/{{lowerName}}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var result = await response.Content.ReadFromJsonAsync<PaginatedResult<{{name}}Response>>();
        result.Should().NotBeNull();
        result!.Items.Should().HaveCount(2);
    }

    [Fact]
    public async Task GetById_WhenExists_ShouldReturn200()
    {
        // Arrange
        var id = Guid.NewGuid();
        var item = new {{name}}Response { Id = id, Code = "TEST-001" };
        _mockService.Setup(x => x.GetByIdAsync(id, It.IsAny<CancellationToken>()))
            .ReturnsAsync(item);

        // Act
        var response = await _client.GetAsync($"/api/{{lowerName}}/{id}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var result = await response.Content.ReadFromJsonAsync<{{name}}Response>();
        result!.Id.Should().Be(id);
    }

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

        // Act
        var response = await _client.GetAsync($"/api/{{lowerName}}/{id}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    #endregion

    #region POST Tests

    [Fact]
    public async Task Create_WhenValidData_ShouldReturn201()
    {
        // Arrange
        var request = new Create{{name}}Request { Code = "NEW-001" };
        var created = new {{name}}Response { Id = Guid.NewGuid(), Code = request.Code };
        _mockService.Setup(x => x.CreateAsync(It.IsAny<Create{{name}}Request>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(created);

        // Act
        var response = await _client.PostAsJsonAsync("/api/{{lowerName}}", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Created);
        response.Headers.Location.Should().NotBeNull();
        var result = await response.Content.ReadFromJsonAsync<{{name}}Response>();
        result!.Code.Should().Be(request.Code);
    }

    [Fact]
    public async Task Create_WhenInvalidData_ShouldReturn400()
    {
        // Arrange
        var request = new Create{{name}}Request { Code = "" }; // Invalid

        // Act
        var response = await _client.PostAsJsonAsync("/api/{{lowerName}}", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    [Fact]
    public async Task Create_WhenDuplicate_ShouldReturn409()
    {
        // Arrange
        var request = new Create{{name}}Request { Code = "EXISTING" };
        _mockService.Setup(x => x.CreateAsync(It.IsAny<Create{{name}}Request>(), It.IsAny<CancellationToken>()))
            .ThrowsAsync(new BusinessException("Code already exists"));

        // Act
        var response = await _client.PostAsJsonAsync("/api/{{lowerName}}", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Conflict);
    }

    #endregion

    #region PUT Tests

    [Fact]
    public async Task Update_WhenValidData_ShouldReturn200()
    {
        // Arrange
        var id = Guid.NewGuid();
        var request = new Update{{name}}Request { /* properties */ };
        var updated = new {{name}}Response { Id = id, Code = "UPDATED" };
        _mockService.Setup(x => x.UpdateAsync(id, It.IsAny<Update{{name}}Request>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(updated);

        // Act
        var response = await _client.PutAsJsonAsync($"/api/{{lowerName}}/{id}", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
    }

    [Fact]
    public async Task Update_WhenNotExists_ShouldReturn404()
    {
        // Arrange
        var id = Guid.NewGuid();
        var request = new Update{{name}}Request();
        _mockService.Setup(x => x.UpdateAsync(id, It.IsAny<Update{{name}}Request>(), It.IsAny<CancellationToken>()))
            .ThrowsAsync(new NotFoundException($"{{name}} {id} not found"));

        // Act
        var response = await _client.PutAsJsonAsync($"/api/{{lowerName}}/{id}", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    [Fact]
    public async Task Update_WhenConcurrencyConflict_ShouldReturn409()
    {
        // Arrange
        var id = Guid.NewGuid();
        var request = new Update{{name}}Request();
        _mockService.Setup(x => x.UpdateAsync(id, It.IsAny<Update{{name}}Request>(), It.IsAny<CancellationToken>()))
            .ThrowsAsync(new ConcurrencyException("Concurrency conflict"));

        // Act
        var response = await _client.PutAsJsonAsync($"/api/{{lowerName}}/{id}", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Conflict);
    }

    #endregion

    #region DELETE Tests

    [Fact]
    public async Task Delete_WhenExists_ShouldReturn204()
    {
        // Arrange
        var id = Guid.NewGuid();
        _mockService.Setup(x => x.DeleteAsync(id, It.IsAny<CancellationToken>()))
            .Returns(Task.CompletedTask);

        // Act
        var response = await _client.DeleteAsync($"/api/{{lowerName}}/{id}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NoContent);
    }

    [Fact]
    public async Task Delete_WhenNotExists_ShouldReturn404()
    {
        // Arrange
        var id = Guid.NewGuid();
        _mockService.Setup(x => x.DeleteAsync(id, It.IsAny<CancellationToken>()))
            .ThrowsAsync(new NotFoundException($"{{name}} {id} not found"));

        // Act
        var response = await _client.DeleteAsync($"/api/{{lowerName}}/{id}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    #endregion

    {{#if includeAuthorization}}
    #region Authorization Tests

    [Fact]
    public async Task GetAll_WhenUnauthorized_ShouldReturn401()
    {
        // Arrange
        var unauthenticatedClient = _factory.CreateClient();
        // Don't add auth header

        // Act
        var response = await unauthenticatedClient.GetAsync("/api/{{lowerName}}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
    }

    [Fact]
    public async Task Create_WhenForbidden_ShouldReturn403()
    {
        // Arrange - User without create permission
        var request = new Create{{name}}Request { Code = "TEST" };

        // Act
        var response = await _client.PostAsJsonAsync("/api/{{lowerName}}", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Forbidden);
    }

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

        // Act
        var response = await _client.DeleteAsync($"/api/{{lowerName}}/{id}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Forbidden);
    }

    #endregion
    {{/if}}

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

    [Fact]
    public async Task GetAll_ShouldOnlyReturnCurrentTenantData()
    {
        // Arrange
        var tenantAId = Guid.NewGuid();
        var tenantAItems = new PaginatedResult<{{name}}Response>
        {
            Items = new List<{{name}}Response>
            {
                new() { Id = Guid.NewGuid(), TenantId = tenantAId, Code = "A-001" }
            },
            Total = 1,
            Page = 1,
            PageSize = 10
        };
        _mockService.Setup(x => x.GetAllAsync(It.IsAny<PaginationParams>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(tenantAItems);

        // Act
        var response = await _client.GetAsync("/api/{{lowerName}}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var result = await response.Content.ReadFromJsonAsync<PaginatedResult<{{name}}Response>>();
        result.Should().NotBeNull();
        result!.Items.Should().OnlyContain(x => x.TenantId == tenantAId);
    }

    [Fact]
    public async Task GetById_WhenDifferentTenant_ShouldReturn404()
    {
        // Arrange
        var id = Guid.NewGuid();
        // Entity belongs to different tenant
        _mockService.Setup(x => x.GetByIdAsync(id, It.IsAny<CancellationToken>()))
            .ReturnsAsync(({{name}}Response?)null);

        // Act
        var response = await _client.GetAsync($"/api/{{lowerName}}/{id}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    #endregion
    {{/unless}}

    #region Validation Tests

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public async Task Create_WhenInvalidCode_ShouldReturn400(string? invalidCode)
    {
        // Arrange
        var request = new Create{{name}}Request { Code = invalidCode! };

        // Act
        var response = await _client.PostAsJsonAsync("/api/{{lowerName}}", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    [Fact]
    public async Task Create_WhenCodeTooLong_ShouldReturn400()
    {
        // Arrange
        var request = new Create{{name}}Request
        {
            Code = new string('A', 256) // Exceeds max length
        };

        // Act
        var response = await _client.PostAsJsonAsync("/api/{{lowerName}}", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    #endregion

    #region Pagination Tests

    [Fact]
    public async Task GetAll_WithPagination_ShouldReturnPagedResults()
    {
        // Arrange
        var items = Enumerable.Range(1, 100)
            .Select(i => new {{name}}Response { Id = Guid.NewGuid(), Code = $"TEST-{i:D3}" })
            .ToList();
        var paginatedResult = new PaginatedResult<{{name}}Response>
        {
            Items = items.Take(10).ToList(),
            Total = 100,
            Page = 1,
            PageSize = 10
        };
        _mockService.Setup(x => x.GetAllAsync(It.IsAny<PaginationParams>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(paginatedResult);

        // Act
        var response = await _client.GetAsync("/api/{{lowerName}}?page=1&pageSize=10");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var result = await response.Content.ReadFromJsonAsync<PaginatedResult<{{name}}Response>>();
        result.Should().NotBeNull();
        result!.Items.Should().HaveCount(10);
        result.Total.Should().Be(100);
    }

    #endregion

    #region Error Response Format Tests

    [Fact]
    public async Task Create_WhenValidationFails_ShouldReturnProblemDetails()
    {
        // Arrange
        var request = new Create{{name}}Request { Code = "" };

        // Act
        var response = await _client.PostAsJsonAsync("/api/{{lowerName}}", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
        var problemDetails = await response.Content.ReadFromJsonAsync<ProblemDetails>();
        problemDetails.Should().NotBeNull();
        problemDetails!.Status.Should().Be(400);
    }

    #endregion
}
