using System.ComponentModel.DataAnnotations;
using Jiaowu.Api.Contracts;
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.EntityFrameworkCore;

namespace Jiaowu.Api.Controllers;

[ApiController]
[Authorize(Roles = ReadRoles)]
[Route("api/personnel")]
public sealed class PersonnelController(
    AppDbContext db,
    ICurrentUserDataScope currentUserDataScope,
    UserManager<ApplicationUser> userManager,
    IAppCache cache) : ControllerBase
{
    private const string ReadRoles =
        SystemRoles.SuperAdmin + "," +
        SystemRoles.AcademicAdmin + "," +
        SystemRoles.CollegeAdmin + "," +
        SystemRoles.Counselor + "," +
        SystemRoles.Teacher + "," +
        SystemRoles.Student;

    private const string WriteRoles =
        SystemRoles.SuperAdmin + "," +
        SystemRoles.AcademicAdmin + "," +
        SystemRoles.CollegeAdmin;

    [HttpGet("teachers")]
    public async Task<ActionResult<PagedResult<object>>> GetTeachers(
        [FromQuery] PersonnelQuery query,
        CancellationToken cancellationToken)
    {
        var page = NormalizePage(query.Page);
        var pageSize = NormalizePageSize(query.PageSize);
        var source = ApplyTeacherScope(db.Teachers.AsNoTracking());
        if (query.CollegeId.HasValue)
            source = source.Where(x => x.CollegeId == query.CollegeId);
        if (!string.IsNullOrWhiteSpace(query.Keyword))
        {
            var keyword = query.Keyword.Trim();
            source = source.Where(x =>
                x.TeacherNumber.Contains(keyword) ||
                x.Name.Contains(keyword) ||
                (x.Title != null && x.Title.Contains(keyword)));
        }
        if (query.TeacherStatus.HasValue)
            source = source.Where(x => x.Status == query.TeacherStatus);

        var total = await source.CountAsync(cancellationToken);
        var items = await source
            .OrderBy(x => x.TeacherNumber)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(x => new
            {
                x.Id,
                x.TeacherNumber,
                x.Name,
                x.Gender,
                x.CollegeId,
                CollegeName = x.College!.Name,
                x.Title,
                x.Status,
                x.HireDate,
                x.IsExternal,
                x.Phone,
                x.Email,
                x.Notes,
                x.UserId,
                x.CreatedAt
            })
            .ToListAsync(cancellationToken);

        return Ok(new PagedResult<object>(items, total, page, pageSize));
    }

    [HttpPost("teachers")]
    [Authorize(Roles = WriteRoles)]
    public async Task<ActionResult> CreateTeacher(
        TeacherRequest request,
        CancellationToken cancellationToken)
    {
        var validation = await ValidateCollegeAsync(request.CollegeId, cancellationToken);
        if (validation is not null) return validation;

        var entity = new Teacher
        {
            TeacherNumber = request.TeacherNumber.Trim(),
            Name = request.Name.Trim(),
            Gender = request.Gender,
            CollegeId = request.CollegeId,
            Title = Normalize(request.Title),
            Status = request.Status,
            HireDate = request.HireDate,
            IsExternal = request.IsExternal,
            Phone = Normalize(request.Phone),
            Email = Normalize(request.Email),
            Notes = Normalize(request.Notes)
        };
        db.Teachers.Add(entity);
        return await SaveCreatedAsync(entity.Id, cancellationToken);
    }

    [HttpPut("teachers/{id:guid}")]
    [Authorize(Roles = WriteRoles)]
    public async Task<ActionResult> UpdateTeacher(
        Guid id,
        TeacherRequest request,
        CancellationToken cancellationToken)
    {
        var entity = await db.Teachers.FindAsync([id], cancellationToken);
        if (entity is null) return NotFound();
        if (!CanAccessCollege(entity.CollegeId)) return Forbid();
        var validation = await ValidateCollegeAsync(request.CollegeId, cancellationToken);
        if (validation is not null) return validation;

        entity.TeacherNumber = request.TeacherNumber.Trim();
        entity.Name = request.Name.Trim();
        entity.Gender = request.Gender;
        entity.CollegeId = request.CollegeId;
        entity.Title = Normalize(request.Title);
        entity.Status = request.Status;
        entity.HireDate = request.HireDate;
        entity.IsExternal = request.IsExternal;
        entity.Phone = Normalize(request.Phone);
        entity.Email = Normalize(request.Email);
        entity.Notes = Normalize(request.Notes);
        return await SaveNoContentAsync(cancellationToken);
    }

    [HttpDelete("teachers/{id:guid}")]
    [Authorize(Roles = WriteRoles)]
    public async Task<IActionResult> DeleteTeacher(Guid id, CancellationToken cancellationToken)
    {
        var entity = await db.Teachers.FindAsync([id], cancellationToken);
        if (entity is null) return NotFound();
        if (!CanAccessCollege(entity.CollegeId)) return Forbid();
        if (entity.UserId.HasValue)
        {
            return ConflictProblem("教师已关联登录账号，请先解除账号关联后再删除。");
        }

        db.Teachers.Remove(entity);
        return await SaveNoContentAsync(cancellationToken);
    }

    [HttpPost("teachers/{id:guid}/activate-account")]
    [Authorize(Roles = WriteRoles)]
    public async Task<ActionResult> ActivateTeacherAccount(
        Guid id,
        TeacherAccountActivationRequest request,
        CancellationToken cancellationToken)
    {
        return await db.ExecuteInRetriableTransactionAsync<ActionResult>(
            async transaction =>
            {
                db.ChangeTracker.Clear();
                var teacher = await db.Teachers.FindAsync([id], cancellationToken);
                if (teacher is null) return NotFound();
                if (!CanAccessCollege(teacher.CollegeId)) return Forbid();
                if (teacher.Status != TeacherStatus.Active)
                {
                    return ConflictProblem("仅在职教师可以激活登录账号。");
                }
                if (teacher.UserId.HasValue)
                {
                    return ConflictProblem("该教师档案已经关联登录账号。");
                }

                var userName = teacher.TeacherNumber.Trim();
                if (await userManager.FindByNameAsync(userName) is not null)
                {
                    return ConflictProblem(
                        "该工号已有登录账号但未正确关联，请到账号管理中核对。");
                }

                var user = new ApplicationUser
                {
                    UserName = userName,
                    DisplayName = teacher.Name,
                    StaffNumber = userName,
                    CollegeId = teacher.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.Teacher);
                if (!result.Succeeded)
                {
                    await transaction.RollbackAsync(cancellationToken);
                    return IdentityValidationProblem(result);
                }

                teacher.UserId = user.Id;
                await db.SaveChangesAsync(cancellationToken);
                await transaction.CommitAsync(cancellationToken);
                await cache.RemoveByTagAsync(
                    AppCacheTags.Timetables,
                    cancellationToken);
                return Ok(new { user.Id, UserName = userName });
            },
            cancellationToken);
    }

    [HttpGet("students")]
    public async Task<ActionResult<PagedResult<object>>> GetStudents(
        [FromQuery] PersonnelQuery query,
        CancellationToken cancellationToken)
    {
        var roles = currentUserDataScope.Current.Roles;
        var canReadFullProfile = roles.Any(role =>
            role is SystemRoles.SuperAdmin or
                SystemRoles.AcademicAdmin or
                SystemRoles.CollegeAdmin or
                SystemRoles.Counselor);
        var page = NormalizePage(query.Page);
        var pageSize = NormalizePageSize(query.PageSize);
        var source = ApplyStudentScope(db.Students.AsNoTracking());
        if (query.CollegeId.HasValue)
        {
            source = source.Where(x =>
                x.AdministrativeClass!.Major!.CollegeId == query.CollegeId.Value);
        }
        if (query.MajorId.HasValue)
            source = source.Where(x => x.AdministrativeClass!.MajorId == query.MajorId);
        if (query.ClassId.HasValue)
            source = source.Where(x => x.AdministrativeClassId == query.ClassId);
        if (query.EnrollmentYear.HasValue)
            source = source.Where(x => x.EnrollmentYear == query.EnrollmentYear);
        if (query.StudentStatus.HasValue)
            source = source.Where(x => x.Status == query.StudentStatus);
        if (!string.IsNullOrWhiteSpace(query.Keyword))
        {
            var keyword = query.Keyword.Trim();
            source = source.Where(x =>
                x.StudentNumber.Contains(keyword) || x.Name.Contains(keyword));
        }

        var total = await source.CountAsync(cancellationToken);
        var items = await source
            .OrderBy(x => x.StudentNumber)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(x => new
            {
                x.Id,
                x.StudentNumber,
                x.Name,
                x.Gender,
                x.AdministrativeClassId,
                ClassName = x.AdministrativeClass!.Name,
                MajorId = x.AdministrativeClass.MajorId,
                MajorName = x.AdministrativeClass.Major!.Name,
                CollegeId = x.AdministrativeClass.Major.CollegeId,
                CollegeName = x.AdministrativeClass.Major.College!.Name,
                x.EnrollmentYear,
                x.EnrollmentDate,
                x.Status,
                x.DateOfBirth,
                EnglishName = canReadFullProfile ? x.EnglishName : null,
                IdCardNumber = canReadFullProfile ? x.IdCardNumber : null,
                Nationality = canReadFullProfile ? x.Nationality : null,
                Ethnicity = canReadFullProfile ? x.Ethnicity : null,
                PoliticalStatus = canReadFullProfile ? x.PoliticalStatus : null,
                NativePlace = canReadFullProfile ? x.NativePlace : null,
                HouseholdAddress = canReadFullProfile ? x.HouseholdAddress : null,
                CurrentAddress = canReadFullProfile ? x.CurrentAddress : null,
                PostalCode = canReadFullProfile ? x.PostalCode : null,
                x.Phone,
                x.Email,
                Qq = canReadFullProfile ? x.Qq : null,
                x.WeChat,
                x.EmergencyContactName,
                x.EmergencyContactRelationship,
                x.EmergencyContactPhone,
                x.SpecialTags,
                x.SpecialNeeds,
                Biography = canReadFullProfile ? x.Biography : null,
                Notes = canReadFullProfile ? x.Notes : null,
                x.UserId,
                x.CreatedAt
            })
            .ToListAsync(cancellationToken);

        return Ok(new PagedResult<object>(items, total, page, pageSize));
    }

    [HttpPost("students")]
    [Authorize(Roles = WriteRoles)]
    public async Task<ActionResult> CreateStudent(
        StudentRequest request,
        CancellationToken cancellationToken)
    {
        var targetClass = await db.AdministrativeClasses.AsNoTracking()
            .Where(x => x.Id == request.AdministrativeClassId)
            .Select(x => new { x.Id, CollegeId = x.Major!.CollegeId })
            .FirstOrDefaultAsync(cancellationToken);
        if (targetClass is null)
            return ValidationProblem("所选行政班不存在。");
        if (!CanAccessCollege(targetClass.CollegeId))
            return Forbid();

        var entity = new Student
        {
            StudentNumber = request.StudentNumber.Trim(),
            Name = request.Name.Trim(),
            Gender = request.Gender,
            AdministrativeClassId = request.AdministrativeClassId,
            EnrollmentYear = request.EnrollmentYear,
            EnrollmentDate = request.EnrollmentDate,
            Status = request.Status,
            DateOfBirth = request.DateOfBirth,
            EnglishName = Normalize(request.EnglishName),
            IdCardNumber = Normalize(request.IdCardNumber),
            Nationality = Normalize(request.Nationality),
            Ethnicity = Normalize(request.Ethnicity),
            PoliticalStatus = Normalize(request.PoliticalStatus),
            NativePlace = Normalize(request.NativePlace),
            HouseholdAddress = Normalize(request.HouseholdAddress),
            CurrentAddress = Normalize(request.CurrentAddress),
            PostalCode = Normalize(request.PostalCode),
            Phone = Normalize(request.Phone),
            Email = Normalize(request.Email),
            Qq = Normalize(request.Qq),
            WeChat = Normalize(request.WeChat),
            EmergencyContactName = Normalize(request.EmergencyContactName),
            EmergencyContactRelationship = Normalize(
                request.EmergencyContactRelationship),
            EmergencyContactPhone = Normalize(request.EmergencyContactPhone),
            SpecialTags = Normalize(request.SpecialTags),
            SpecialNeeds = Normalize(request.SpecialNeeds),
            Biography = Normalize(request.Biography),
            Notes = Normalize(request.Notes)
        };
        db.Students.Add(entity);
        return await SaveCreatedAsync(entity.Id, cancellationToken);
    }

    [HttpPut("students/{id:guid}")]
    [Authorize(Roles = WriteRoles)]
    public async Task<ActionResult> UpdateStudent(
        Guid id,
        StudentRequest request,
        CancellationToken cancellationToken)
    {
        var entity = await db.Students
            .Include(x => x.AdministrativeClass)
            .ThenInclude(x => x!.Major)
            .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
        if (entity is null) return NotFound();
        if (!CanAccessCollege(entity.AdministrativeClass!.Major!.CollegeId)) return Forbid();

        var targetClass = await db.AdministrativeClasses.AsNoTracking()
            .Where(x => x.Id == request.AdministrativeClassId)
            .Select(x => new { x.Id, CollegeId = x.Major!.CollegeId })
            .FirstOrDefaultAsync(cancellationToken);
        if (targetClass is null)
            return ValidationProblem("所选行政班不存在。");
        if (!CanAccessCollege(targetClass.CollegeId))
            return Forbid();

        entity.StudentNumber = request.StudentNumber.Trim();
        entity.Name = request.Name.Trim();
        entity.Gender = request.Gender;
        entity.AdministrativeClassId = request.AdministrativeClassId;
        entity.EnrollmentYear = request.EnrollmentYear;
        entity.EnrollmentDate = request.EnrollmentDate;
        entity.Status = request.Status;
        entity.DateOfBirth = request.DateOfBirth;
        entity.EnglishName = Normalize(request.EnglishName);
        entity.IdCardNumber = Normalize(request.IdCardNumber);
        entity.Nationality = Normalize(request.Nationality);
        entity.Ethnicity = Normalize(request.Ethnicity);
        entity.PoliticalStatus = Normalize(request.PoliticalStatus);
        entity.NativePlace = Normalize(request.NativePlace);
        entity.HouseholdAddress = Normalize(request.HouseholdAddress);
        entity.CurrentAddress = Normalize(request.CurrentAddress);
        entity.PostalCode = Normalize(request.PostalCode);
        entity.Phone = Normalize(request.Phone);
        entity.Email = Normalize(request.Email);
        entity.Qq = Normalize(request.Qq);
        entity.WeChat = Normalize(request.WeChat);
        entity.EmergencyContactName = Normalize(request.EmergencyContactName);
        entity.EmergencyContactRelationship = Normalize(
            request.EmergencyContactRelationship);
        entity.EmergencyContactPhone = Normalize(request.EmergencyContactPhone);
        entity.SpecialTags = Normalize(request.SpecialTags);
        entity.SpecialNeeds = Normalize(request.SpecialNeeds);
        entity.Biography = Normalize(request.Biography);
        entity.Notes = Normalize(request.Notes);
        return await SaveNoContentAsync(cancellationToken);
    }

    [HttpDelete("students/{id:guid}")]
    [Authorize(Roles = WriteRoles)]
    public async Task<IActionResult> DeleteStudent(Guid id, CancellationToken cancellationToken)
    {
        var entity = await db.Students
            .Include(x => x.AdministrativeClass)
            .ThenInclude(x => x!.Major)
            .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
        if (entity is null) return NotFound();
        if (!CanAccessCollege(entity.AdministrativeClass!.Major!.CollegeId)) return Forbid();
        if (entity.UserId.HasValue)
        {
            return ConflictProblem("学生已关联登录账号，请先解除账号关联后再删除。");
        }

        db.Students.Remove(entity);
        return await SaveNoContentAsync(cancellationToken);
    }

    private IQueryable<Teacher> ApplyTeacherScope(IQueryable<Teacher> source)
    {
        var scope = currentUserDataScope.Current;
        if (scope.Scope == DataScope.All) return source;
        if (scope.Scope == DataScope.College)
            return source.Where(x => x.CollegeId == scope.RestrictedCollegeId);
        if (scope.Scope == DataScope.Class)
        {
            return source.Where(teacher => db.TeachingTasks.Any(task =>
                task.Teachers.Any(item => item.TeacherId == teacher.Id) &&
                task.Classes.Any(item =>
                    item.AdministrativeClass!.CounselorUserId == scope.UserId)));
        }

        var userId = scope.UserId;
        return scope.IsInRole(SystemRoles.Teacher)
            ? source.Where(x => x.UserId == userId)
            : source.Where(_ => false);
    }

    private IQueryable<Student> ApplyStudentScope(IQueryable<Student> source)
    {
        var scope = currentUserDataScope.Current;
        if (scope.Scope == DataScope.All) return source;
        if (scope.Scope == DataScope.College)
        {
            return source.Where(x =>
                x.AdministrativeClass!.Major!.CollegeId == scope.RestrictedCollegeId);
        }
        if (scope.Scope == DataScope.Class)
        {
            return source.Where(x =>
                x.AdministrativeClass!.CounselorUserId == scope.UserId);
        }

        var userId = scope.UserId;
        var isTeacher = scope.IsInRole(SystemRoles.Teacher);
        var isStudent = scope.IsInRole(SystemRoles.Student);
        return source.Where(student =>
            isStudent && student.UserId == userId ||
            isTeacher && db.TeachingTasks.Any(task =>
                task.Teachers.Any(item => item.Teacher!.UserId == userId) &&
                task.Classes.Any(item =>
                    item.AdministrativeClassId == student.AdministrativeClassId)));
    }

    private bool CanAccessCollege(Guid collegeId) =>
        currentUserDataScope.Current.CanAccessCollege(collegeId);

    private async Task<ActionResult?> ValidateCollegeAsync(
        Guid collegeId,
        CancellationToken cancellationToken)
    {
        if (!CanAccessCollege(collegeId)) return Forbid();
        if (!await db.Colleges.AnyAsync(x => x.Id == collegeId, cancellationToken))
            return ValidationProblem("所选学院不存在。");
        return null;
    }

    private async Task<ActionResult> SaveCreatedAsync(
        Guid id,
        CancellationToken cancellationToken)
    {
        try
        {
            await db.SaveChangesAsync(cancellationToken);
            await cache.RemoveByTagAsync(
                AppCacheTags.Timetables,
                cancellationToken);
            await cache.RemoveByTagAsync(
                AppCacheTags.Analytics,
                cancellationToken);
            return Created(string.Empty, new { id });
        }
        catch (DbUpdateException)
        {
            return ConflictProblem("编号已存在，或关联数据无效。");
        }
    }

    private async Task<ActionResult> SaveNoContentAsync(CancellationToken cancellationToken)
    {
        try
        {
            await db.SaveChangesAsync(cancellationToken);
            await cache.RemoveByTagAsync(
                AppCacheTags.Timetables,
                cancellationToken);
            await cache.RemoveByTagAsync(
                AppCacheTags.Analytics,
                cancellationToken);
            return NoContent();
        }
        catch (DbUpdateException)
        {
            return ConflictProblem("编号已存在，或该档案已被其他业务引用。");
        }
    }

    private ActionResult ConflictProblem(string detail) =>
        Conflict(new ProblemDetails
        {
            Title = "无法完成操作",
            Detail = detail,
            Status = StatusCodes.Status409Conflict
        });

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

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

    private static int NormalizePage(int page) => Math.Max(1, page);
    private static int NormalizePageSize(int pageSize) => Math.Clamp(pageSize, 10, 100);
}

public sealed record PersonnelQuery(
    int Page = 1,
    int PageSize = 20,
    string? Keyword = null,
    Guid? CollegeId = null,
    Guid? MajorId = null,
    Guid? ClassId = null,
    int? EnrollmentYear = null,
    TeacherStatus? TeacherStatus = null,
    StudentStatus? StudentStatus = null);

public sealed record TeacherRequest(
    [Required, MaxLength(30)] string TeacherNumber,
    [Required, MaxLength(50)] string Name,
    Gender Gender,
    Guid CollegeId,
    [MaxLength(30)] string? Title,
    TeacherStatus Status,
    DateOnly? HireDate,
    bool IsExternal,
    [MaxLength(30)] string? Phone,
    [EmailAddress, MaxLength(100)] string? Email,
    [MaxLength(500)] string? Notes);

public sealed record StudentRequest(
    [Required, MaxLength(30)] string StudentNumber,
    [Required, MaxLength(50)] string Name,
    Gender Gender,
    Guid AdministrativeClassId,
    [Range(2000, 2200)] int EnrollmentYear,
    DateOnly EnrollmentDate,
    StudentStatus Status,
    DateOnly? DateOfBirth,
    [MaxLength(100)] string? EnglishName,
    [MaxLength(30)] string? IdCardNumber,
    [MaxLength(50)] string? Nationality,
    [MaxLength(50)] string? Ethnicity,
    [MaxLength(50)] string? PoliticalStatus,
    [MaxLength(100)] string? NativePlace,
    [MaxLength(300)] string? HouseholdAddress,
    [MaxLength(300)] string? CurrentAddress,
    [MaxLength(20)] string? PostalCode,
    [MaxLength(30)] string? Phone,
    [EmailAddress, MaxLength(100)] string? Email,
    [MaxLength(30)] string? Qq,
    [MaxLength(60)] string? WeChat,
    [MaxLength(50)] string? EmergencyContactName,
    [MaxLength(30)] string? EmergencyContactRelationship,
    [MaxLength(30)] string? EmergencyContactPhone,
    [MaxLength(300)] string? SpecialTags,
    [MaxLength(1000)] string? SpecialNeeds,
    [MaxLength(1000)] string? Biography,
    [MaxLength(500)] string? Notes);

public sealed record TeacherAccountActivationRequest(
    [Required, MinLength(8), MaxLength(100)] string Password);
