using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
using Jiaowu.Api.Contracts;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Jiaowu.Api.Controllers;

[ApiController]
[Authorize(Roles = SystemRoles.SuperAdmin)]
[Route("api/users")]
public sealed class UsersController(
    AppDbContext db,
    UserManager<ApplicationUser> userManager,
    RoleManager<ApplicationRole> roleManager) : ControllerBase
{
    [HttpGet]
    public async Task<ActionResult<PagedResult<UserListItem>>> GetUsers(
        int page = 1,
        int pageSize = 20,
        string? keyword = null,
        string? roleName = null,
        Guid? collegeId = null,
        bool? isEnabled = null,
        bool? hasLoggedIn = null,
        CancellationToken cancellationToken = default)
    {
        if (page < 1 || pageSize is < 1 or > 100)
            return ValidationProblem("页码必须大于 0，且每页条数应在 1 至 100 之间。");

        var query = userManager.Users.AsNoTracking();
        if (!string.IsNullOrWhiteSpace(keyword))
        {
            keyword = keyword.Trim();
            query = query.Where(user =>
                user.UserName!.Contains(keyword) ||
                user.DisplayName.Contains(keyword) ||
                (user.StaffNumber != null && user.StaffNumber.Contains(keyword)) ||
                (from userRole in db.UserRoles
                 join role in db.Roles on userRole.RoleId equals role.Id
                 where userRole.UserId == user.Id && role.Name!.Contains(keyword)
                 select role.Id).Any());
        }
        if (!string.IsNullOrWhiteSpace(roleName))
        {
            roleName = roleName.Trim();
            query = query.Where(user =>
                (from userRole in db.UserRoles
                 join candidateRole in db.Roles on userRole.RoleId equals candidateRole.Id
                 where userRole.UserId == user.Id && candidateRole.Name == roleName
                 select userRole.RoleId).Any());
        }
        if (collegeId.HasValue) query = query.Where(user => user.CollegeId == collegeId.Value);
        if (isEnabled.HasValue) query = query.Where(user => user.IsEnabled == isEnabled.Value);
        if (hasLoggedIn.HasValue)
            query = hasLoggedIn.Value
                ? query.Where(user => user.LastLoginAt != null)
                : query.Where(user => user.LastLoginAt == null);

        var total = await query.CountAsync(cancellationToken);
        var users = await query
            .OrderBy(x => x.UserName)
            .ThenBy(x => x.Id)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(x => new
            {
                x.Id,
                x.UserName,
                x.DisplayName,
                x.StaffNumber,
                x.CollegeId,
                x.IsEnabled,
                x.LastLoginAt,
                x.CreatedAt
            })
            .ToListAsync(cancellationToken);

        var userIds = users.Select(x => x.Id).ToArray();
        var roleRows = await (
            from userRole in db.UserRoles.AsNoTracking()
            join role in db.Roles.AsNoTracking() on userRole.RoleId equals role.Id
            where userIds.Contains(userRole.UserId)
            select new { userRole.UserId, RoleName = role.Name! })
            .ToListAsync(cancellationToken);
        var rolesByUser = roleRows
            .GroupBy(x => x.UserId)
            .ToDictionary(
                group => group.Key,
                group => (IReadOnlyCollection<string>)group
                    .Select(x => x.RoleName)
                    .OrderBy(x => x)
                    .ToArray());

        var items = users.Select(user => new UserListItem(
            user.Id,
            user.UserName ?? string.Empty,
            user.DisplayName,
            user.StaffNumber,
            user.CollegeId,
            user.IsEnabled,
            user.LastLoginAt,
            user.CreatedAt,
            rolesByUser.GetValueOrDefault(user.Id, Array.Empty<string>())))
            .ToArray();
        return Ok(new PagedResult<UserListItem>(items, total, page, pageSize));
    }

    [HttpGet("roles")]
    public async Task<ActionResult<object>> GetRoles(CancellationToken cancellationToken) =>
        Ok(await roleManager.Roles.AsNoTracking()
            .OrderBy(x => x.Name)
            .Select(x => new { x.Name, x.Description, x.DataScope })
            .ToListAsync(cancellationToken));

    [HttpPost]
    public async Task<ActionResult> Create(
        CreateUserRequest request,
        CancellationToken cancellationToken)
    {
        var roles = request.Roles.Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
        var invalidRoles = ValidateRoles(roles);
        if (invalidRoles is not null) return invalidRoles;
        var staffNumber = Normalize(request.StaffNumber);
        return await db.ExecuteInRetriableTransactionAsync<ActionResult>(
            async transaction =>
            {
                db.ChangeTracker.Clear();
                var profiles = await ResolveProfilesAsync(staffNumber, roles);
                if (profiles.Error is not null) return profiles.Error;

                var user = new ApplicationUser
                {
                    UserName = request.UserName.Trim(),
                    DisplayName = request.DisplayName.Trim(),
                    StaffNumber = staffNumber,
                    CollegeId = request.CollegeId,
                    LockoutEnabled = true,
                    IsEnabled = true
                };
                var result = await userManager.CreateAsync(user, request.Password);
                if (!result.Succeeded)
                    return IdentityValidationProblem(result);

                result = await userManager.AddToRolesAsync(user, roles);
                if (!result.Succeeded)
                    return IdentityValidationProblem(result);

                if (profiles.Teacher is not null)
                    profiles.Teacher.UserId = user.Id;
                if (profiles.Student is not null)
                    profiles.Student.UserId = user.Id;
                await db.SaveChangesAsync(cancellationToken);
                await transaction.CommitAsync(cancellationToken);
                return CreatedAtAction(
                    nameof(GetUsers),
                    new { id = user.Id },
                    new { user.Id });
            },
            cancellationToken);
    }

    [HttpPut("{id:guid}/status")]
    public async Task<IActionResult> SetStatus(Guid id, SetUserStatusRequest request)
    {
        var user = await userManager.FindByIdAsync(id.ToString());
        if (user is null) return NotFound();
        if (User.FindFirstValue(ClaimTypes.NameIdentifier) == id.ToString() &&
            !request.IsEnabled)
            return ValidationProblem("不能停用当前登录账号。");

        user.IsEnabled = request.IsEnabled;
        var result = await userManager.UpdateAsync(user);
        return result.Succeeded ? NoContent() : IdentityValidationProblem(result);
    }

    [HttpPut("{id:guid}/roles")]
    public async Task<IActionResult> SetRoles(
        Guid id,
        SetRolesRequest request,
        CancellationToken cancellationToken)
    {
        var roles = request.Roles.Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
        var invalidRoles = ValidateRoles(roles);
        if (invalidRoles is not null) return invalidRoles;
        var staffNumber = Normalize(request.StaffNumber);
        return await db.ExecuteInRetriableTransactionAsync<IActionResult>(
            async transaction =>
            {
                db.ChangeTracker.Clear();
                var user = await userManager.FindByIdAsync(id.ToString());
                if (user is null) return NotFound();
                var profiles = await ResolveProfilesAsync(
                    staffNumber,
                    roles,
                    user.Id);
                if (profiles.Error is not null) return profiles.Error;

                var existing = await userManager.GetRolesAsync(user);
                if (User.FindFirstValue(ClaimTypes.NameIdentifier) == id.ToString() &&
                    existing.Contains(SystemRoles.SuperAdmin) &&
                    !roles.Contains(
                        SystemRoles.SuperAdmin,
                        StringComparer.OrdinalIgnoreCase))
                {
                    return ValidationProblem(
                        "不能移除当前账号的超级管理员角色。");
                }

                user.StaffNumber = staffNumber;
                user.CollegeId = request.CollegeId;
                var updateResult = await userManager.UpdateAsync(user);
                if (!updateResult.Succeeded)
                    return IdentityValidationProblem(updateResult);
                var removeResult = await userManager.RemoveFromRolesAsync(
                    user,
                    existing.Except(roles, StringComparer.OrdinalIgnoreCase));
                if (!removeResult.Succeeded)
                    return IdentityValidationProblem(removeResult);
                var addResult = await userManager.AddToRolesAsync(
                    user,
                    roles.Except(existing, StringComparer.OrdinalIgnoreCase));
                if (!addResult.Succeeded)
                    return IdentityValidationProblem(addResult);

                var linkedTeachers = await db.Teachers
                    .Where(x => x.UserId == id)
                    .ToListAsync();
                var linkedStudents = await db.Students
                    .Where(x => x.UserId == id)
                    .ToListAsync();
                if (!roles.Contains(
                        SystemRoles.Teacher,
                        StringComparer.OrdinalIgnoreCase))
                    foreach (var teacher in linkedTeachers)
                        teacher.UserId = null;
                if (!roles.Contains(
                        SystemRoles.Student,
                        StringComparer.OrdinalIgnoreCase))
                    foreach (var student in linkedStudents)
                        student.UserId = null;
                if (profiles.Teacher is not null)
                    profiles.Teacher.UserId = id;
                if (profiles.Student is not null)
                    profiles.Student.UserId = id;
                await db.SaveChangesAsync(cancellationToken);
                await transaction.CommitAsync(cancellationToken);
                return NoContent();
            },
            cancellationToken);
    }

    private ActionResult? ValidateRoles(IReadOnlyCollection<string> roles)
    {
        if (roles.Count == 0) return ValidationProblem("至少需要分配一个角色。");
        var invalidRoles = roles
            .Except(SystemRoles.All, StringComparer.OrdinalIgnoreCase)
            .ToArray();
        return invalidRoles.Length == 0
            ? null
            : ValidationProblem($"无效角色：{string.Join("、", invalidRoles)}");
    }

    private async Task<(Teacher? Teacher, Student? Student, ActionResult? Error)>
        ResolveProfilesAsync(
            string? staffNumber,
            IReadOnlyCollection<string> roles,
            Guid? currentUserId = null)
    {
        var needsTeacher = roles.Contains(
            SystemRoles.Teacher,
            StringComparer.OrdinalIgnoreCase);
        var needsStudent = roles.Contains(
            SystemRoles.Student,
            StringComparer.OrdinalIgnoreCase);
        if (!needsTeacher && !needsStudent) return (null, null, null);
        if (string.IsNullOrWhiteSpace(staffNumber))
            return (null, null, ValidationProblem("教师或学生角色必须填写对应的工号/学号。"));

        Teacher? teacher = null;
        Student? student = null;
        if (needsTeacher)
        {
            teacher = await db.Teachers.SingleOrDefaultAsync(
                x => x.TeacherNumber == staffNumber);
            if (teacher is null)
                return (null, null, ValidationProblem("未找到与该工号对应的教师档案。"));
            if (teacher.UserId.HasValue && teacher.UserId != currentUserId)
                return (null, null, ConflictProblem("该教师档案已经关联其他账号。"));
        }
        if (needsStudent)
        {
            student = await db.Students.SingleOrDefaultAsync(
                x => x.StudentNumber == staffNumber);
            if (student is null)
                return (null, null, ValidationProblem("未找到与该学号对应的学生档案。"));
            if (student.UserId.HasValue && student.UserId != currentUserId)
                return (null, null, ConflictProblem("该学生档案已经关联其他账号。"));
        }

        return (teacher, student, null);
    }

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

    private ActionResult ConflictProblem(string detail) =>
        Conflict(new ProblemDetails
        {
            Title = "账号关联冲突",
            Detail = detail,
            Status = StatusCodes.Status409Conflict
        });

    private static string? Normalize(string? value) =>
        string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}

public sealed record CreateUserRequest(
    [Required, MaxLength(50)] string UserName,
    [Required, MaxLength(50)] string DisplayName,
    [Required, MinLength(8), MaxLength(100)] string Password,
    [MaxLength(30)] string? StaffNumber,
    Guid? CollegeId,
    [MinLength(1)] string[] Roles);

public sealed record SetUserStatusRequest(bool IsEnabled);
public sealed record UserListItem(
    Guid Id,
    string UserName,
    string DisplayName,
    string? StaffNumber,
    Guid? CollegeId,
    bool IsEnabled,
    DateTime? LastLoginAt,
    DateTime CreatedAt,
    IReadOnlyCollection<string> Roles);
public sealed record SetRolesRequest(
    [MaxLength(30)] string? StaffNumber,
    Guid? CollegeId,
    [MinLength(1)] string[] Roles);
