using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
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.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;

namespace Jiaowu.Api.Controllers;

[ApiController]
[Route("api/auth")]
public sealed class AuthController(
    AppDbContext db,
    UserManager<ApplicationUser> userManager,
    IAuthSessionService authSessionService,
    IAppCache cache) : ControllerBase
{
    [AllowAnonymous]
    [HttpGet("activation-options")]
    public async Task<ActionResult> GetActivationOptions(CancellationToken cancellationToken)
    {
        var result = await cache.GetOrCreateAsync(
            AppCacheKeys.ActivationOptions,
            async token =>
            {
                var colleges = await db.Colleges.AsNoTracking()
                    .Where(x => x.IsEnabled)
                    .OrderBy(x => x.Code)
                    .Select(x => new ActivationCollegeOption(x.Id, x.Code, x.Name))
                    .ToListAsync(token);
                var majors = await db.Majors.AsNoTracking()
                    .Where(x => x.IsEnabled && x.College!.IsEnabled)
                    .OrderBy(x => x.Code)
                    .Select(x => new ActivationMajorOption(
                        x.Id, x.Code, x.Name, x.CollegeId))
                    .ToListAsync(token);
                var classes = await db.AdministrativeClasses.AsNoTracking()
                    .Where(x =>
                        x.IsEnabled &&
                        x.Major!.IsEnabled &&
                        x.Major.College!.IsEnabled)
                    .OrderByDescending(x => x.Grade)
                    .ThenBy(x => x.Code)
                    .Select(x => new ActivationClassOption(
                        x.Id, x.Code, x.Name, x.Grade, x.MajorId))
                    .ToListAsync(token);
                return new ActivationOptionsResponse(
                    colleges,
                    majors,
                    classes,
                    classes.Select(x => x.Grade)
                        .Distinct()
                        .OrderByDescending(x => x)
                        .ToList());
            },
            AppCacheProfile.ReferenceData,
            [AppCacheTags.BaseData],
            cancellationToken);
        return Ok(result);
    }

    [AllowAnonymous]
    [EnableRateLimiting("public-auth")]
    [HttpPost("activate-student")]
    public async Task<ActionResult> ActivateStudent(
        StudentActivationRequest request,
        CancellationToken cancellationToken)
    {
        var name = request.Name.Trim();
        var studentNumber = request.StudentNumber.Trim();
        var executionStrategy = db.Database.CreateExecutionStrategy();
        return await executionStrategy.ExecuteAsync<ActionResult>(async () =>
        {
            var student = await db.Students
                .Include(x => x.AdministrativeClass)
                .ThenInclude(x => x!.Major)
                .FirstOrDefaultAsync(x =>
                        x.Status == StudentStatus.Active &&
                        x.Name == name &&
                        x.StudentNumber == studentNumber &&
                        x.EnrollmentYear == request.Grade &&
                        x.AdministrativeClassId == request.AdministrativeClassId &&
                        x.AdministrativeClass!.Grade == request.Grade &&
                        x.AdministrativeClass.MajorId == request.MajorId &&
                        x.AdministrativeClass.Major!.CollegeId == request.CollegeId,
                    cancellationToken);
            if (student is null)
                return ActivationProblem(
                    "填写的信息与在籍学生档案不完全一致，请核对后重试。",
                    StatusCodes.Status400BadRequest);
            if (student.UserId.HasValue)
                return ActivationProblem(
                    "该学号已经激活，请直接登录；如忘记密码请联系管理员重置。",
                    StatusCodes.Status409Conflict);
            if (await userManager.FindByNameAsync(studentNumber) is not null)
                return ActivationProblem(
                    "该学号已有登录账号但未正确关联，请联系管理员处理。",
                    StatusCodes.Status409Conflict);

            await using var transaction =
                await db.Database.BeginTransactionAsync(cancellationToken);
            var user = new ApplicationUser
            {
                UserName = studentNumber,
                DisplayName = student.Name,
                StaffNumber = studentNumber,
                CollegeId = request.CollegeId,
                IsEnabled = true,
                LockoutEnabled = true
            };
            var result = await userManager.CreateAsync(user, request.Password);
            if (!result.Succeeded)
            {
                await transaction.RollbackAsync(cancellationToken);
                return IdentityValidationProblem(result);
            }
            result = await userManager.AddToRoleAsync(user, SystemRoles.Student);
            if (!result.Succeeded)
            {
                await transaction.RollbackAsync(cancellationToken);
                return IdentityValidationProblem(result);
            }

            student.UserId = user.Id;
            await db.SaveChangesAsync(cancellationToken);
            await transaction.CommitAsync(cancellationToken);
            return Ok(new { UserName = studentNumber });
        });
    }

    [AllowAnonymous]
    [EnableRateLimiting("public-auth")]
    [HttpPost("login")]
    public async Task<ActionResult<LoginResponse>> Login(
        LoginRequest request,
        CancellationToken cancellationToken)
    {
        var user = await userManager.FindByNameAsync(request.UserName);
        if (user is null || !user.IsEnabled)
        {
            return Unauthorized(new ProblemDetails
            {
                Title = "登录失败",
                Detail = "账号或密码不正确，或账号已停用。",
                Status = StatusCodes.Status401Unauthorized
            });
        }

        if (await userManager.IsLockedOutAsync(user) ||
            !await userManager.CheckPasswordAsync(user, request.Password))
        {
            await userManager.AccessFailedAsync(user);
            return Unauthorized(new ProblemDetails
            {
                Title = "登录失败",
                Detail = "账号或密码不正确，或账号已停用。",
                Status = StatusCodes.Status401Unauthorized
            });
        }

        await userManager.ResetAccessFailedCountAsync(user);
        user.LastLoginAt = DateTime.UtcNow;
        await userManager.UpdateAsync(user);
        var roles = await userManager.GetRolesAsync(user);

        var session = await authSessionService.CreateAsync(
            user,
            roles,
            request.IsNativeApp
                ? AuthenticationClientType.App
                : AuthenticationClientType.Web,
            cancellationToken);
        return CreateLoginResponse(session);
    }

    [AllowAnonymous]
    [EnableRateLimiting("token-refresh")]
    [HttpPost("refresh")]
    public async Task<ActionResult<LoginResponse>> Refresh(
        RefreshTokenRequest request,
        CancellationToken cancellationToken)
    {
        var session = await authSessionService.RefreshAsync(
            request.RefreshToken,
            cancellationToken);
        if (session is null)
        {
            return Unauthorized(new ProblemDetails
            {
                Title = "登录已过期",
                Detail = "登录已过期或刷新令牌已失效，请重新登录。",
                Status = StatusCodes.Status401Unauthorized
            });
        }

        return CreateLoginResponse(session);
    }

    [AllowAnonymous]
    [HttpPost("logout")]
    public async Task<IActionResult> Logout(
        RefreshTokenRequest request,
        CancellationToken cancellationToken)
    {
        await authSessionService.RevokeAsync(request.RefreshToken, cancellationToken);
        return NoContent();
    }

    [Authorize]
    [HttpGet("me")]
    public async Task<ActionResult<CurrentUserResponse>> Me()
    {
        var id = User.FindFirstValue(ClaimTypes.NameIdentifier);
        var user = id is null ? null : await userManager.FindByIdAsync(id);
        if (user is null || !user.IsEnabled)
        {
            return Unauthorized();
        }

        var roles = await userManager.GetRolesAsync(user);
        return new CurrentUserResponse(
            user.Id,
            user.UserName!,
            user.DisplayName,
            roles,
            user.CollegeId,
            EffectiveDataScopeResolver.Resolve(roles).ToString());
    }

    private ActionResult IdentityValidationProblem(IdentityResult result)
    {
        foreach (var error in result.Errors)
            ModelState.AddModelError(error.Code, error.Description);
        return ValidationProblem(ModelState);
    }

    private ActionResult ActivationProblem(string detail, int status) =>
        StatusCode(status, new ProblemDetails
        {
            Title = "账号激活失败",
            Detail = detail,
            Status = status
        });

    internal static LoginResponse CreateLoginResponse(AuthSessionResult session) =>
        new(
            session.AccessToken,
            session.AccessTokenExpiresAt,
            session.RefreshToken,
            session.SessionExpiresAt,
            new CurrentUserResponse(
                session.User.Id,
                session.User.UserName!,
                session.User.DisplayName,
                session.Roles,
                session.User.CollegeId,
                EffectiveDataScopeResolver.Resolve(session.Roles).ToString()));
}

public sealed record LoginRequest(
    [Required, MaxLength(100)] string UserName,
    [Required, MaxLength(100)] string Password,
    bool IsNativeApp = false);

public sealed record RefreshTokenRequest(
    [Required, MinLength(40), MaxLength(200)] string RefreshToken);

public sealed record StudentActivationRequest(
    [Required, MaxLength(50)] string Name,
    [Required, MaxLength(30)] string StudentNumber,
    Guid CollegeId,
    Guid MajorId,
    [Range(2000, 2200)] int Grade,
    Guid AdministrativeClassId,
    [Required, MinLength(8), MaxLength(100)] string Password);

public sealed record LoginResponse(
    string Token,
    DateTime AccessTokenExpiresAt,
    string RefreshToken,
    DateTime SessionExpiresAt,
    CurrentUserResponse User);

public sealed record CurrentUserResponse(
    Guid Id,
    string UserName,
    string DisplayName,
    IEnumerable<string> Roles,
    Guid? CollegeId,
    string EffectiveDataScope);

public sealed record ActivationOptionsResponse(
    IReadOnlyList<ActivationCollegeOption> Colleges,
    IReadOnlyList<ActivationMajorOption> Majors,
    IReadOnlyList<ActivationClassOption> Classes,
    IReadOnlyList<int> Grades);

public sealed record ActivationCollegeOption(Guid Id, string Code, string Name);

public sealed record ActivationMajorOption(
    Guid Id,
    string Code,
    string Name,
    Guid CollegeId);

public sealed record ActivationClassOption(
    Guid Id,
    string Code,
    string Name,
    int Grade,
    Guid MajorId);
