{{!-- SmartStack Security Test Template --}}
{{!-- Generates security-focused tests for entities, services, and controllers --}}

using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using System.Net;
using System.Net.Http.Json;
using Xunit;
using {{namespace}}.Api;
using {{namespace}}.Domain.Entities;

namespace {{namespace}}.Tests.Security;

/// <summary>
/// Security tests for {{name}}.
/// Covers: Authentication, Authorization, Input Validation, Tenant Isolation, Data Protection
/// Follows SmartStack testing conventions: {Method}_When{Condition}_Should{Result}
/// </summary>
public class {{name}}SecurityTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    private readonly HttpClient _unauthenticatedClient;

    public {{name}}SecurityTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
        _unauthenticatedClient = factory.CreateClient();
        // Note: _client should have auth headers, _unauthenticatedClient should not
    }

    #region Authentication Tests

    [Fact]
    public async Task GetAll_WhenNotAuthenticated_ShouldReturn401()
    {
        // Act
        var response = await _unauthenticatedClient.GetAsync("/api/{{lowerName}}");

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

    [Fact]
    public async Task Create_WhenNotAuthenticated_ShouldReturn401()
    {
        // Arrange
        var request = new { Code = "TEST" };

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

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

    [Fact]
    public async Task Delete_WhenNotAuthenticated_ShouldReturn401()
    {
        // Act
        var response = await _unauthenticatedClient.DeleteAsync($"/api/{{lowerName}}/{Guid.NewGuid()}");

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

    [Fact]
    public async Task Api_WhenTokenExpired_ShouldReturn401()
    {
        // Arrange
        var expiredTokenClient = CreateClientWithExpiredToken();

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

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

    [Fact]
    public async Task Api_WhenTokenMalformed_ShouldReturn401()
    {
        // Arrange
        var malformedTokenClient = CreateClientWithMalformedToken();

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

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

    #endregion

    #region Authorization Tests

    [Fact]
    public async Task Create_WhenUserLacksPermission_ShouldReturn403()
    {
        // Arrange
        var readOnlyClient = CreateClientWithReadOnlyPermissions();
        var request = new { Code = "TEST" };

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

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

    [Fact]
    public async Task Delete_WhenUserLacksAdminRole_ShouldReturn403()
    {
        // Arrange
        var regularUserClient = CreateClientWithRegularUserRole();

        // Act
        var response = await regularUserClient.DeleteAsync($"/api/{{lowerName}}/{Guid.NewGuid()}");

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

    [Fact]
    public async Task Update_WhenUserCannotModifyOthersData_ShouldReturn403()
    {
        // Arrange
        var otherUserId = Guid.NewGuid();
        var request = new { Name = "Hacked" };

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

        // Assert
        // Should be 403 if trying to modify another user's data
        response.StatusCode.Should().BeOneOf(HttpStatusCode.Forbidden, HttpStatusCode.NotFound);
    }

    #endregion

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

    [Fact]
    public async Task GetById_WhenAccessingOtherTenantData_ShouldReturn404()
    {
        // Arrange
        var otherTenantEntityId = Guid.NewGuid(); // ID from different tenant

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

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound,
            "accessing other tenant's data should appear as if it doesn't exist");
    }

    [Fact]
    public async Task Update_WhenTargetingOtherTenantData_ShouldReturn404()
    {
        // Arrange
        var otherTenantEntityId = Guid.NewGuid();
        var request = new { Name = "Hacked" };

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

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

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

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

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

    [Fact]
    public async Task Create_ShouldNotAllowTenantIdInRequest()
    {
        // Arrange - Try to create with a different tenant ID
        var maliciousRequest = new
        {
            Code = "TEST",
            TenantId = Guid.NewGuid() // Attempting to specify tenant
        };

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

        // Assert
        // Should either ignore the TenantId or return 400
        response.StatusCode.Should().BeOneOf(HttpStatusCode.Created, HttpStatusCode.BadRequest);

        if (response.StatusCode == HttpStatusCode.Created)
        {
            // If created, verify tenant was not the one in request
            var created = await response.Content.ReadFromJsonAsync<{{name}}Response>();
            created!.TenantId.Should().NotBe(maliciousRequest.TenantId,
                "server should assign tenant based on auth context, not request");
        }
    }

    #endregion
    {{/unless}}

    #region Input Validation / Injection Prevention Tests

    [Theory]
    [InlineData("<script>alert('xss')</script>")]
    [InlineData("<img src=x onerror=alert('xss')>")]
    [InlineData("javascript:alert('xss')")]
    public async Task Create_WhenXssAttempt_ShouldSanitizeOrReject(string xssPayload)
    {
        // Arrange
        var request = new { Code = "TEST", Name = xssPayload };

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

        // Assert
        response.StatusCode.Should().BeOneOf(HttpStatusCode.BadRequest, HttpStatusCode.Created);

        if (response.StatusCode == HttpStatusCode.Created)
        {
            var created = await response.Content.ReadFromJsonAsync<{{name}}Response>();
            created!.Name.Should().NotContain("<script>", "XSS should be sanitized");
            created.Name.Should().NotContain("javascript:", "XSS should be sanitized");
        }
    }

    [Theory]
    [InlineData("'; DROP TABLE {{name}}s; --")]
    [InlineData("1; DELETE FROM {{name}}s WHERE 1=1; --")]
    [InlineData("1 OR 1=1")]
    public async Task Create_WhenSqlInjectionAttempt_ShouldPrevent(string sqlPayload)
    {
        // Arrange
        var request = new { Code = sqlPayload };

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

        // Assert
        // Should be rejected by validation or handled safely by parameterized queries
        response.StatusCode.Should().BeOneOf(HttpStatusCode.BadRequest, HttpStatusCode.Created);

        // Verify database wasn't affected
        var allResponse = await _client.GetAsync("/api/{{lowerName}}");
        allResponse.StatusCode.Should().Be(HttpStatusCode.OK);
    }

    [Theory]
    [InlineData("{{'{{'}}constructor{{'}}'}}")]
    [InlineData("{{'{{'}}__proto__{{'}}'}}")]
    [InlineData("{\"$type\":\"System.Diagnostics.Process\"}")]
    public async Task Create_WhenPrototypePollutionAttempt_ShouldPrevent(string payload)
    {
        // Arrange
        var request = new { Code = "TEST", Description = payload };

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

        // Assert
        response.StatusCode.Should().BeOneOf(HttpStatusCode.BadRequest, HttpStatusCode.Created);
    }

    [Theory]
    [InlineData("../../../etc/passwd")]
    [InlineData("..\\..\\..\\windows\\system32")]
    [InlineData("file:///etc/passwd")]
    public async Task Create_WhenPathTraversalAttempt_ShouldPrevent(string pathPayload)
    {
        // Arrange
        var request = new { Code = "TEST", FilePath = pathPayload };

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

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

    #endregion

    #region IDOR (Insecure Direct Object Reference) Tests

    [Fact]
    public async Task GetById_WhenGuessingIds_ShouldNotLeakData()
    {
        // Arrange - Try sequential IDs
        var guessedIds = Enumerable.Range(1, 10).Select(i => Guid.NewGuid());

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

            // Assert
            response.StatusCode.Should().BeOneOf(
                HttpStatusCode.NotFound,
                HttpStatusCode.OK, // Only if it's user's own data
                "should not leak existence of other users' data"
            );
        }
    }

    #endregion

    #region Rate Limiting Tests

    [Fact]
    public async Task Api_WhenExcessiveRequests_ShouldReturn429()
    {
        // Arrange
        var tasks = Enumerable.Range(1, 100)
            .Select(_ => _client.GetAsync("/api/{{lowerName}}"));

        // Act
        var responses = await Task.WhenAll(tasks);

        // Assert
        responses.Should().Contain(r => r.StatusCode == HttpStatusCode.TooManyRequests,
            "rate limiting should be enforced");
    }

    #endregion

    #region Sensitive Data Exposure Tests

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

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

        if (response.StatusCode == HttpStatusCode.OK)
        {
            var content = await response.Content.ReadAsStringAsync();

            // Assert
            content.Should().NotContain("password", StringComparison.OrdinalIgnoreCase);
            content.Should().NotContain("secret", StringComparison.OrdinalIgnoreCase);
            content.Should().NotContain("connectionString", StringComparison.OrdinalIgnoreCase);
            content.Should().NotContain("apiKey", StringComparison.OrdinalIgnoreCase);
        }
    }

    [Fact]
    public async Task ErrorResponse_ShouldNotExposeStackTrace()
    {
        // Arrange - Trigger an error
        var request = new { Code = new string('A', 10000) }; // Very long to potentially cause error

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

        if (!response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();

            // Assert
            content.Should().NotContain("at ", "stack trace should not be exposed");
            content.Should().NotContain("Exception", "exception details should not be exposed");
            content.Should().NotContain(".cs:line", "source file info should not be exposed");
        }
    }

    #endregion

    #region CORS Tests

    [Fact]
    public async Task Api_ShouldHaveProperCorsHeaders()
    {
        // Act
        var response = await _client.GetAsync("/api/{{lowerName}}");

        // Assert
        var corsHeader = response.Headers.GetValues("Access-Control-Allow-Origin").FirstOrDefault();
        corsHeader.Should().NotBe("*", "CORS should not allow all origins in production");
    }

    #endregion

    #region Security Headers Tests

    [Fact]
    public async Task Api_ShouldHaveSecurityHeaders()
    {
        // Act
        var response = await _client.GetAsync("/api/{{lowerName}}");

        // Assert
        response.Headers.Should().ContainKey("X-Content-Type-Options");
        response.Headers.Should().ContainKey("X-Frame-Options");
        response.Headers.Should().ContainKey("X-XSS-Protection");
    }

    #endregion

    #region Helper Methods

    private HttpClient CreateClientWithExpiredToken()
    {
        // Implementation depends on your auth setup
        var client = new HttpClient();
        client.DefaultRequestHeaders.Add("Authorization", "Bearer expired.token.here");
        return client;
    }

    private HttpClient CreateClientWithMalformedToken()
    {
        var client = new HttpClient();
        client.DefaultRequestHeaders.Add("Authorization", "Bearer not-a-valid-jwt");
        return client;
    }

    private HttpClient CreateClientWithReadOnlyPermissions()
    {
        // Implementation depends on your auth setup
        return _client; // Placeholder
    }

    private HttpClient CreateClientWithRegularUserRole()
    {
        // Implementation depends on your auth setup
        return _client; // Placeholder
    }

    #endregion
}
