using System.Security.Claims;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Excel;
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 UsersExcelController(
    AppDbContext db,
    UserManager<ApplicationUser> userManager) : ControllerBase
{
    private static readonly string[] Headers =
    [
        "登录账号", "姓名", "初始密码", "工号/学号", "学院编码", "角色", "状态"
    ];

    [HttpGet("template")]
    public IActionResult DownloadTemplate()
    {
        var bytes = ExcelWorkbookHelper.Create(
            "账号导入",
            Headers,
            [],
            [
                "登录账号是唯一标识；已存在时更新账号，不存在时创建账号。",
                $"角色可填写：{string.Join("、", SystemRoles.All)}；多个角色用顿号或逗号分隔。",
                "新账号必须填写不少于 8 位的初始密码；更新账号时密码留空表示不修改。",
                "教师或学生角色必须填写已存在且未关联其他账号的工号/学号。",
                "状态填写“启用”或“停用”；整批校验失败时不会写入。"
            ]);
        return File(bytes, ExcelWorkbookHelper.ContentType, "用户与权限导入模板.xlsx");
    }

    [HttpGet("export")]
    public async Task<IActionResult> Export(CancellationToken cancellationToken)
    {
        var colleges = await db.Colleges.AsNoTracking()
            .ToDictionaryAsync(x => x.Id, x => x.Code, cancellationToken);
        var users = await userManager.Users.AsNoTracking()
            .OrderBy(x => x.UserName).ToListAsync(cancellationToken);
        var rows = new List<IReadOnlyList<object?>>();
        foreach (var user in users)
        {
            var identityUser = await userManager.FindByIdAsync(user.Id.ToString());
            var roles = identityUser is null
                ? []
                : await userManager.GetRolesAsync(identityUser);
            rows.Add(Row(
                user.UserName, user.DisplayName, string.Empty, user.StaffNumber,
                user.CollegeId.HasValue
                    ? colleges.GetValueOrDefault(user.CollegeId.Value)
                    : null,
                string.Join("、", roles),
                user.IsEnabled ? "启用" : "停用"));
        }
        var bytes = ExcelWorkbookHelper.Create("用户与权限", Headers, rows);
        return File(bytes, ExcelWorkbookHelper.ContentType,
            $"用户与权限-{DateTime.Now:yyyyMMdd-HHmm}.xlsx");
    }

    [HttpPost("import")]
    [RequestSizeLimit(10 * 1024 * 1024)]
    public async Task<ActionResult<ExcelImportResult>> Import(
        IFormFile file,
        CancellationToken cancellationToken)
    {
        IReadOnlyList<ExcelRow> rows;
        try
        {
            rows = await ExcelWorkbookHelper.ReadAsync(file, Headers, cancellationToken);
        }
        catch (InvalidDataException exception)
        {
            return ValidationProblem(exception.Message);
        }
        if (rows.Count == 0) return ValidationProblem("Excel 中没有可导入的账号。");

        return await db.ExecuteInRetriableTransactionAsync<
            ActionResult<ExcelImportResult>>(
            async transaction =>
            {
                db.ChangeTracker.Clear();
                var colleges = await db.Colleges.AsNoTracking().ToDictionaryAsync(
                    x => x.Code, StringComparer.OrdinalIgnoreCase, cancellationToken);
                var users = await userManager.Users.ToDictionaryAsync(
                    x => x.UserName!, StringComparer.OrdinalIgnoreCase, cancellationToken);
                var teachers = await db.Teachers.ToDictionaryAsync(
                    x => x.TeacherNumber,
                    StringComparer.OrdinalIgnoreCase,
                    cancellationToken);
                var students = await db.Students.ToDictionaryAsync(
                    x => x.StudentNumber,
                    StringComparer.OrdinalIgnoreCase,
                    cancellationToken);
                var currentUserId = User.FindFirstValue(ClaimTypes.NameIdentifier);
                var errors = new List<string>();
                var plans = new List<UserImportPlan>();
                var seenNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                var plannedTeachers = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                var plannedStudents = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

                foreach (var row in rows)
                {
                    var userName = Required(row, "登录账号", errors);
                    var displayName = Required(row, "姓名", errors);
                    if (userName is null || displayName is null) continue;
                    if (!seenNames.Add(userName))
                    {
                        errors.Add($"第 {row.RowNumber} 行：登录账号“{userName}”在文件中重复。");
                        continue;
                    }

                    var existing = users.GetValueOrDefault(userName);
                    var password = Optional(row, "初始密码");
                    if (existing is null && (password?.Length ?? 0) < 8)
                        errors.Add($"第 {row.RowNumber} 行：新账号的初始密码至少需要 8 位。");
                    if (password is { Length: > 0 and < 8 })
                        errors.Add($"第 {row.RowNumber} 行：密码至少需要 8 位。");

                    var roleNames = ParseRoles(row, errors);
                    var enabled = ParseEnabled(row, errors);
                    var staffNumber = Optional(row, "工号/学号");
                    Guid? collegeId = null;
                    var collegeCode = Optional(row, "学院编码");
                    if (collegeCode is not null)
                    {
                        if (!colleges.TryGetValue(collegeCode, out var college))
                            errors.Add(
                                $"第 {row.RowNumber} 行：学院编码“{collegeCode}”不存在。");
                        else collegeId = college.Id;
                    }

                    Teacher? teacher = null;
                    Student? student = null;
                    if (roleNames.Contains(
                            SystemRoles.Teacher,
                            StringComparer.OrdinalIgnoreCase))
                    {
                        if (staffNumber is null ||
                            !teachers.TryGetValue(staffNumber, out teacher))
                            errors.Add($"第 {row.RowNumber} 行：教师角色必须填写有效工号。");
                        else if (teacher.UserId.HasValue &&
                                 teacher.UserId != existing?.Id)
                            errors.Add(
                                $"第 {row.RowNumber} 行：工号“{staffNumber}”已关联其他账号。");
                        else if (!plannedTeachers.Add(staffNumber))
                            errors.Add(
                                $"第 {row.RowNumber} 行：工号“{staffNumber}”在文件中关联了多个账号。");
                    }
                    if (roleNames.Contains(
                            SystemRoles.Student,
                            StringComparer.OrdinalIgnoreCase))
                    {
                        if (staffNumber is null ||
                            !students.TryGetValue(staffNumber, out student))
                            errors.Add($"第 {row.RowNumber} 行：学生角色必须填写有效学号。");
                        else if (student.UserId.HasValue &&
                                 student.UserId != existing?.Id)
                            errors.Add(
                                $"第 {row.RowNumber} 行：学号“{staffNumber}”已关联其他账号。");
                        else if (!plannedStudents.Add(staffNumber))
                            errors.Add(
                                $"第 {row.RowNumber} 行：学号“{staffNumber}”在文件中关联了多个账号。");
                    }

                    if (existing?.Id.ToString() == currentUserId)
                    {
                        if (!enabled)
                            errors.Add($"第 {row.RowNumber} 行：不能停用当前登录账号。");
                        if (!roleNames.Contains(
                                SystemRoles.SuperAdmin,
                                StringComparer.OrdinalIgnoreCase))
                            errors.Add(
                                $"第 {row.RowNumber} 行：不能移除当前账号的超级管理员角色。");
                    }

                    plans.Add(new UserImportPlan(
                        row.RowNumber, existing, userName, displayName, password,
                        staffNumber, collegeId, roleNames, enabled, teacher, student));
                }

                if (errors.Count > 0) return ImportValidationProblem(errors);

                var created = 0;
                var updated = 0;
                foreach (var plan in plans)
                {
                    var user = plan.Existing;
                    if (user is null)
                    {
                        user = new ApplicationUser
                        {
                            UserName = plan.UserName,
                            DisplayName = plan.DisplayName,
                            StaffNumber = plan.StaffNumber,
                            CollegeId = plan.CollegeId,
                            IsEnabled = plan.IsEnabled,
                            LockoutEnabled = true
                        };
                        var createResult = await userManager.CreateAsync(
                            user,
                            plan.Password!);
                        if (!createResult.Succeeded)
                        {
                            await transaction.RollbackAsync(cancellationToken);
                            return IdentityValidationProblem(
                                createResult,
                                plan.RowNumber);
                        }
                        created++;
                    }
                    else
                    {
                        user.DisplayName = plan.DisplayName;
                        user.StaffNumber = plan.StaffNumber;
                        user.CollegeId = plan.CollegeId;
                        user.IsEnabled = plan.IsEnabled;
                        var updateResult = await userManager.UpdateAsync(user);
                        if (!updateResult.Succeeded)
                        {
                            await transaction.RollbackAsync(cancellationToken);
                            return IdentityValidationProblem(
                                updateResult,
                                plan.RowNumber);
                        }
                        if (plan.Password is not null)
                        {
                            var token = await userManager
                                .GeneratePasswordResetTokenAsync(user);
                            var passwordResult = await userManager.ResetPasswordAsync(
                                user,
                                token,
                                plan.Password);
                            if (!passwordResult.Succeeded)
                            {
                                await transaction.RollbackAsync(cancellationToken);
                                return IdentityValidationProblem(
                                    passwordResult,
                                    plan.RowNumber);
                            }
                        }
                        updated++;
                    }

                    var existingRoles = await userManager.GetRolesAsync(user);
                    var removeResult = await userManager.RemoveFromRolesAsync(
                        user,
                        existingRoles.Except(
                            plan.Roles,
                            StringComparer.OrdinalIgnoreCase));
                    if (!removeResult.Succeeded)
                    {
                        await transaction.RollbackAsync(cancellationToken);
                        return IdentityValidationProblem(removeResult, plan.RowNumber);
                    }
                    var addResult = await userManager.AddToRolesAsync(
                        user,
                        plan.Roles.Except(
                            existingRoles,
                            StringComparer.OrdinalIgnoreCase));
                    if (!addResult.Succeeded)
                    {
                        await transaction.RollbackAsync(cancellationToken);
                        return IdentityValidationProblem(addResult, plan.RowNumber);
                    }

                    foreach (var linked in await db.Teachers
                                 .Where(x => x.UserId == user.Id)
                                 .ToListAsync(cancellationToken))
                        if (linked != plan.Teacher) linked.UserId = null;
                    foreach (var linked in await db.Students
                                 .Where(x => x.UserId == user.Id)
                                 .ToListAsync(cancellationToken))
                        if (linked != plan.Student) linked.UserId = null;
                    if (plan.Teacher is not null) plan.Teacher.UserId = user.Id;
                    if (plan.Student is not null) plan.Student.UserId = user.Id;
                    await db.SaveChangesAsync(cancellationToken);
                }

                await transaction.CommitAsync(cancellationToken);
                return Ok(new ExcelImportResult(created, updated, plans.Count));
            },
            cancellationToken);
    }

    [HttpPut("{id:guid}/password")]
    public async Task<IActionResult> ResetPassword(Guid id, ResetUserPasswordRequest request)
    {
        var user = await userManager.FindByIdAsync(id.ToString());
        if (user is null) return NotFound();
        var token = await userManager.GeneratePasswordResetTokenAsync(user);
        var result = await userManager.ResetPasswordAsync(user, token, request.NewPassword);
        return result.Succeeded ? NoContent() : IdentityValidationProblem(result);
    }

    private ActionResult ImportValidationProblem(IReadOnlyList<string> errors)
    {
        foreach (var error in errors.Take(50))
            ModelState.AddModelError("file", error);
        if (errors.Count > 50)
            ModelState.AddModelError("file", $"另有 {errors.Count - 50} 条错误未显示。");
        return ValidationProblem(ModelState);
    }

    private ActionResult IdentityValidationProblem(
        IdentityResult result,
        int? rowNumber = null)
    {
        foreach (var error in result.Errors)
            ModelState.AddModelError(
                "identity",
                rowNumber.HasValue
                    ? $"第 {rowNumber} 行：{error.Description}"
                    : error.Description);
        return ValidationProblem(ModelState);
    }

    private static string[] ParseRoles(ExcelRow row, List<string> errors)
    {
        var roles = row["角色"]
            .Split([',', '，', ';', '；', '、'], StringSplitOptions.RemoveEmptyEntries |
                                                  StringSplitOptions.TrimEntries)
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToArray();
        if (roles.Length == 0)
            errors.Add($"第 {row.RowNumber} 行：“角色”至少填写一个角色。");
        var invalid = roles.Except(SystemRoles.All, StringComparer.OrdinalIgnoreCase).ToArray();
        if (invalid.Length > 0)
            errors.Add($"第 {row.RowNumber} 行：无效角色“{string.Join("、", invalid)}”。");
        return roles;
    }

    private static bool ParseEnabled(ExcelRow row, List<string> errors)
    {
        var value = row["状态"];
        if (value is "启用" or "是" or "1" ||
            value.Equals("true", StringComparison.OrdinalIgnoreCase)) return true;
        if (value is "停用" or "否" or "0" ||
            value.Equals("false", StringComparison.OrdinalIgnoreCase)) return false;
        errors.Add($"第 {row.RowNumber} 行：“状态”请填写“启用”或“停用”。");
        return true;
    }

    private static string? Required(ExcelRow row, string header, List<string> errors)
    {
        var value = Optional(row, header);
        if (value is not null) return value;
        errors.Add($"第 {row.RowNumber} 行：“{header}”不能为空。");
        return null;
    }

    private static string? Optional(ExcelRow row, string header) =>
        string.IsNullOrWhiteSpace(row[header]) ? null : row[header].Trim();

    private static IReadOnlyList<object?> Row(params object?[] values) => values;

    private sealed record UserImportPlan(
        int RowNumber,
        ApplicationUser? Existing,
        string UserName,
        string DisplayName,
        string? Password,
        string? StaffNumber,
        Guid? CollegeId,
        string[] Roles,
        bool IsEnabled,
        Teacher? Teacher,
        Student? Student);
}

public sealed record ResetUserPasswordRequest(
    [System.ComponentModel.DataAnnotations.Required,
     System.ComponentModel.DataAnnotations.MinLength(8),
     System.ComponentModel.DataAnnotations.MaxLength(100)]
    string NewPassword);
