using Jiaowu.Api.Controllers;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Caching;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;

namespace Jiaowu.Api.Tests;

public sealed class AuthControllerTests
{
    [Fact]
    public async Task RetriableTransaction_WorksWithRetryingExecutionStrategy()
    {
        await using var connection = new SqliteConnection("Data Source=:memory:");
        await connection.OpenAsync();
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseSqlite(connection)
            .ReplaceService<IExecutionStrategyFactory, RetryingExecutionStrategyFactory>()
            .Options;
        await using var db = new AppDbContext(options);
        await db.Database.EnsureCreatedAsync();

        Assert.True(db.Database.CreateExecutionStrategy().RetriesOnFailure);
        await db.ExecuteInRetriableTransactionAsync(
            async transaction =>
            {
                db.Colleges.Add(new College { Code = "TX", Name = "事务测试学院" });
                await db.SaveChangesAsync();
                await transaction.CommitAsync();
            },
            CancellationToken.None);

        Assert.Equal(1, await db.Colleges.CountAsync());
    }

    [Fact]
    public async Task ActivateStudent_WorksWithRetryingExecutionStrategy()
    {
        await using var connection = new SqliteConnection("Data Source=:memory:");
        await connection.OpenAsync();
        var services = new ServiceCollection();
        services.AddLogging();
        services.AddDbContext<AppDbContext>(options => options
            .UseSqlite(connection)
            .ReplaceService<IExecutionStrategyFactory, RetryingExecutionStrategyFactory>());
        services
            .AddIdentityCore<ApplicationUser>(options =>
            {
                options.Password.RequiredLength = 8;
                options.Password.RequireDigit = true;
                options.Password.RequireLowercase = true;
                options.Password.RequireUppercase = true;
                options.Password.RequireNonAlphanumeric = true;
            })
            .AddRoles<ApplicationRole>()
            .AddEntityFrameworkStores<AppDbContext>();

        await using var provider = services.BuildServiceProvider();
        await using var scope = provider.CreateAsyncScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await db.Database.EnsureCreatedAsync();
        var roleManager = scope.ServiceProvider
            .GetRequiredService<RoleManager<ApplicationRole>>();
        Assert.True((await roleManager.CreateAsync(new ApplicationRole
        {
            Name = SystemRoles.Student,
            Description = "学生"
        })).Succeeded);

        var college = new College { Code = "CS", Name = "计算机学院" };
        var major = new Major
        {
            Code = "SE",
            Name = "软件工程",
            CollegeId = college.Id,
            DegreeType = "工学"
        };
        var administrativeClass = new AdministrativeClass
        {
            Code = "SE202601",
            Name = "软件工程 2026 级 1 班",
            MajorId = major.Id,
            Grade = 2026
        };
        var student = new Student
        {
            StudentNumber = "202601999",
            Name = "测试学生",
            AdministrativeClassId = administrativeClass.Id,
            EnrollmentYear = 2026,
            EnrollmentDate = new DateOnly(2026, 9, 1),
            Status = StudentStatus.Active
        };
        db.AddRange(college, major, administrativeClass, student);
        await db.SaveChangesAsync();

        var userManager = scope.ServiceProvider
            .GetRequiredService<UserManager<ApplicationUser>>();
        Assert.True(db.Database.CreateExecutionStrategy().RetriesOnFailure);
        var controller = new AuthController(
            db,
            userManager,
            new StubAuthSessionService(),
            NoOpAppCache.Instance);
        var request = new StudentActivationRequest(
            student.Name,
            student.StudentNumber,
            college.Id,
            major.Id,
            administrativeClass.Grade,
            administrativeClass.Id,
            "Student@123");

        var result = await controller.ActivateStudent(request, CancellationToken.None);

        Assert.IsType<OkObjectResult>(result);
        var user = await userManager.FindByNameAsync(student.StudentNumber);
        Assert.NotNull(user);
        Assert.Equal(student.Name, user.DisplayName);
        Assert.Equal(college.Id, user.CollegeId);
        Assert.True(await userManager.IsInRoleAsync(user, SystemRoles.Student));
        await db.Entry(student).ReloadAsync();
        Assert.Equal(user.Id, student.UserId);
    }

    private sealed class RetryingExecutionStrategyFactory(
        ExecutionStrategyDependencies dependencies) : IExecutionStrategyFactory
    {
        public IExecutionStrategy Create() => new TestRetryingExecutionStrategy(dependencies);
    }

    private sealed class TestRetryingExecutionStrategy(
        ExecutionStrategyDependencies dependencies)
        : ExecutionStrategy(dependencies, 1, TimeSpan.Zero)
    {
        protected override bool ShouldRetryOn(Exception exception) => false;
    }

    private sealed class StubAuthSessionService : IAuthSessionService
    {
        public Task<AuthSessionResult> CreateAsync(
            ApplicationUser user,
            IEnumerable<string> roles,
            AuthenticationClientType clientType,
            CancellationToken cancellationToken = default) =>
            Task.FromResult(new AuthSessionResult(
                string.Empty,
                DateTime.UtcNow.AddMinutes(10),
                "test-refresh-token-value-with-sufficient-length",
                DateTime.UtcNow.AddMinutes(30),
                user,
                roles.ToList()));

        public Task<AuthSessionResult?> RefreshAsync(
            string refreshToken,
            CancellationToken cancellationToken = default) => Task.FromResult<AuthSessionResult?>(null);

        public Task RevokeAsync(
            string refreshToken,
            CancellationToken cancellationToken = default) => Task.CompletedTask;
    }
}
