namespace Application.Tests.Validators;

using Application.Enrollments.Commands;
using Application.Students.Commands;
using Domain.Ports.Repositories;
using FluentAssertions;
using FluentValidation.TestHelper;
using NSubstitute;
using Xunit;

public class CreateStudentValidatorTests
{
    private readonly IStudentRepository _studentRepository;
    private readonly CreateStudentValidator _validator;

    public CreateStudentValidatorTests()
    {
        _studentRepository = Substitute.For<IStudentRepository>();
        _studentRepository.EmailExistsAsync(Arg.Any<string>(), Arg.Any<int?>(), Arg.Any<CancellationToken>())
            .Returns(false);
        _validator = new CreateStudentValidator(_studentRepository);
    }

    [Fact]
    public async Task Validate_WithValidCommand_ShouldNotHaveErrors()
    {
        // Arrange
        var command = new CreateStudentCommand("John Doe", "john@example.com");

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldNotHaveAnyValidationErrors();
    }

    [Fact]
    public async Task Validate_WithEmptyName_ShouldHaveError()
    {
        // Arrange
        var command = new CreateStudentCommand("", "john@example.com");

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldHaveValidationErrorFor(x => x.Name);
    }

    [Fact]
    public async Task Validate_WithEmptyEmail_ShouldHaveError()
    {
        // Arrange
        var command = new CreateStudentCommand("John Doe", "");

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldHaveValidationErrorFor(x => x.Email);
    }

    [Fact]
    public async Task Validate_WithInvalidEmail_ShouldHaveError()
    {
        // Arrange
        var command = new CreateStudentCommand("John Doe", "invalid-email");

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldHaveValidationErrorFor(x => x.Email);
    }

    [Fact]
    public async Task Validate_WithExistingEmail_ShouldHaveError()
    {
        // Arrange
        _studentRepository.EmailExistsAsync("existing@example.com", null, Arg.Any<CancellationToken>())
            .Returns(true);

        var command = new CreateStudentCommand("John Doe", "existing@example.com");

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldHaveValidationErrorFor(x => x.Email)
            .WithErrorMessage("Email already exists");
    }

    [Fact]
    public async Task Validate_WithNameTooLong_ShouldHaveError()
    {
        // Arrange
        var longName = new string('A', 101);
        var command = new CreateStudentCommand(longName, "john@example.com");

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldHaveValidationErrorFor(x => x.Name);
    }

    [Fact]
    public async Task Validate_WithNameTooShort_ShouldHaveError()
    {
        // Arrange
        var command = new CreateStudentCommand("AB", "john@example.com");

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldHaveValidationErrorFor(x => x.Name)
            .WithErrorMessage("Name must be at least 3 characters");
    }

    [Theory]
    [InlineData("<script>alert('xss')</script>")]
    [InlineData("John<img src=x onerror=alert(1)>")]
    [InlineData("javascript:alert(1)")]
    public async Task Validate_WithDangerousName_ShouldHaveError(string dangerousName)
    {
        // Arrange
        var command = new CreateStudentCommand(dangerousName, "john@example.com");

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldHaveValidationErrorFor(x => x.Name);
    }

    [Theory]
    [InlineData("John123")]
    [InlineData("John@Doe")]
    [InlineData("John#Doe")]
    public async Task Validate_WithInvalidCharactersInName_ShouldHaveError(string invalidName)
    {
        // Arrange
        var command = new CreateStudentCommand(invalidName, "john@example.com");

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldHaveValidationErrorFor(x => x.Name)
            .WithErrorMessage("Name contains invalid characters");
    }

    [Theory]
    [InlineData("María García")]
    [InlineData("José O'Brien")]
    [InlineData("Ana-María López")]
    [InlineData("Dr. Juan Pérez")]
    public async Task Validate_WithValidSpecialCharactersInName_ShouldPass(string validName)
    {
        // Arrange
        var command = new CreateStudentCommand(validName, "john@example.com");

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldNotHaveValidationErrorFor(x => x.Name);
    }

    [Fact]
    public async Task Validate_WithEmailTooLong_ShouldHaveError()
    {
        // Arrange
        var longEmail = new string('a', 250) + "@example.com";
        var command = new CreateStudentCommand("John Doe", longEmail);

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldHaveValidationErrorFor(x => x.Email);
    }
}

public class UpdateStudentValidatorTests
{
    private readonly IStudentRepository _studentRepository;
    private readonly UpdateStudentValidator _validator;

    public UpdateStudentValidatorTests()
    {
        _studentRepository = Substitute.For<IStudentRepository>();
        _studentRepository.EmailExistsAsync(Arg.Any<string>(), Arg.Any<int?>(), Arg.Any<CancellationToken>())
            .Returns(false);
        _validator = new UpdateStudentValidator(_studentRepository);
    }

    [Fact]
    public async Task Validate_WithValidCommand_ShouldNotHaveErrors()
    {
        // Arrange
        var command = new UpdateStudentCommand(1, "John Doe", "john@example.com");

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldNotHaveAnyValidationErrors();
    }

    [Fact]
    public async Task Validate_WithInvalidId_ShouldHaveError()
    {
        // Arrange
        var command = new UpdateStudentCommand(0, "John Doe", "john@example.com");

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldHaveValidationErrorFor(x => x.Id);
    }

    [Fact]
    public async Task Validate_WithNegativeId_ShouldHaveError()
    {
        // Arrange
        var command = new UpdateStudentCommand(-1, "John Doe", "john@example.com");

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldHaveValidationErrorFor(x => x.Id);
    }
}

public class EnrollStudentValidatorTests
{
    private readonly EnrollStudentValidator _validator;

    public EnrollStudentValidatorTests()
    {
        _validator = new EnrollStudentValidator();
    }

    [Fact]
    public async Task Validate_WithValidCommand_ShouldNotHaveErrors()
    {
        // Arrange
        var command = new EnrollStudentCommand(1, 1);

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldNotHaveAnyValidationErrors();
    }

    [Fact]
    public async Task Validate_WithInvalidStudentId_ShouldHaveError()
    {
        // Arrange
        var command = new EnrollStudentCommand(0, 1);

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldHaveValidationErrorFor(x => x.StudentId);
    }

    [Fact]
    public async Task Validate_WithInvalidSubjectId_ShouldHaveError()
    {
        // Arrange
        var command = new EnrollStudentCommand(1, 0);

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldHaveValidationErrorFor(x => x.SubjectId);
    }
}

public class UnenrollStudentValidatorTests
{
    private readonly UnenrollStudentValidator _validator;

    public UnenrollStudentValidatorTests()
    {
        _validator = new UnenrollStudentValidator();
    }

    [Fact]
    public async Task Validate_WithValidCommand_ShouldNotHaveErrors()
    {
        // Arrange
        var command = new UnenrollStudentCommand(1);

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldNotHaveAnyValidationErrors();
    }

    [Fact]
    public async Task Validate_WithInvalidEnrollmentId_ShouldHaveError()
    {
        // Arrange
        var command = new UnenrollStudentCommand(0);

        // Act
        var result = await _validator.TestValidateAsync(command);

        // Assert
        result.ShouldHaveValidationErrorFor(x => x.EnrollmentId);
    }
}
