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.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Jiaowu.Api.Controllers;

[ApiController]
[Authorize]
[Route("api/warnings")]
public sealed class WarningsController(AppDbContext db, ICurrentUserDataScope scope) : ControllerBase
{
    private const string Managers = SystemRoles.SuperAdmin + "," + SystemRoles.AcademicAdmin;

    // ═══════════ Rules (admin) ═══════════
    [HttpGet("rules")]
    [Authorize(Roles = Managers)]
    public async Task<ActionResult> GetRules(Guid academicTermId, CancellationToken ct) =>
        Ok(await db.WarningRules.AsNoTracking().Where(x => x.AcademicTermId == academicTermId)
            .OrderBy(x => x.Type).Select(x => new { x.Id, Type = (int)x.Type, x.Name, x.Threshold, x.IsEnabled, x.NotifyStudent, x.NotifyCounselor, x.Description, x.AutoCheckEnabled, CheckDayOfWeek = x.CheckDayOfWeek ?? 0, x.CheckHour, x.CheckMinute, x.LastCheckAt })
            .ToListAsync(ct));

    [HttpPut("rules")]
    [Authorize(Roles = Managers)]
    public async Task<ActionResult> SaveRules(Guid academicTermId, List<WarningRuleDto> rules, CancellationToken ct)
    {
        if (rules.GroupBy(x => x.Type).Any(group => group.Count() > 1))
            return BadRequest(new ProblemDetails { Title = "预警类型不能重复。", Status = StatusCodes.Status400BadRequest });
        if (rules.Any(x => x.CheckDayOfWeek is < 0 or > 7 || x.CheckHour is < 0 or > 23 || x.CheckMinute is < 0 or > 59))
            return BadRequest(new ProblemDetails { Title = "自动检测时间无效。", Status = StatusCodes.Status400BadRequest });

        var existing = await db.WarningRules.Where(x => x.AcademicTermId == academicTermId).ToListAsync(ct);
        var incomingTypes = rules.Select(x => x.Type).ToHashSet();
        db.WarningRules.RemoveRange(existing.Where(x => !incomingTypes.Contains(x.Type)));
        foreach (var r in rules)
        {
            var entity = existing.FirstOrDefault(x => x.Type == r.Type);
            if (entity is null)
            {
                entity = new WarningRule
                {
                    AcademicTermId = academicTermId,
                    Type = r.Type,
                    Name = r.Name.Trim()
                };
                db.WarningRules.Add(entity);
            }

            entity.Name = r.Name.Trim();
            entity.Threshold = r.Threshold;
            entity.IsEnabled = r.IsEnabled;
            entity.NotifyStudent = r.NotifyStudent;
            entity.NotifyCounselor = r.NotifyCounselor;
            entity.Description = r.Description?.Trim();
            entity.AutoCheckEnabled = r.AutoCheckEnabled;
            entity.CheckDayOfWeek = r.CheckDayOfWeek == 0 ? null : r.CheckDayOfWeek;
            entity.CheckHour = r.CheckHour;
            entity.CheckMinute = r.CheckMinute;
        }
        await db.SaveChangesAsync(ct);
        return NoContent();
    }

    // ═══════════ Detection + Records ═══════════
    [HttpPost("detect")]
    [Authorize(Roles = Managers)]
    public async Task<ActionResult> DetectWarnings(Guid academicTermId, CancellationToken ct)
    {
        var rules = await db.WarningRules.Where(x => x.AcademicTermId == academicTermId && x.IsEnabled).ToListAsync(ct);
        if (rules.Count == 0) return Ok(new { Generated = 0, Message = "当前学期无启用的预警规则。" });

        var studentInfos = await db.Students.Where(x => x.Status == StudentStatus.Active).Select(x => new StudentInfo(x.Id, x.UserId, x.Name, x.AdministrativeClassId)).ToListAsync(ct);
        var generated = new List<WarningRecord>();

        foreach (var rule in rules)
        {
            switch (rule.Type)
            {
                case WarningType.FailedCredits:
                    generated.AddRange(await DetectFailedCredits(rule, academicTermId, studentInfos, ct));
                    break;
                case WarningType.LowGPA:
                    generated.AddRange(await DetectLowGPA(rule, academicTermId, studentInfos, ct));
                    break;
                case WarningType.Absenteeism:
                    generated.AddRange(await DetectAbsenteeism(rule, academicTermId, studentInfos, ct));
                    break;
                case WarningType.GraduationDelay:
                    generated.AddRange(await DetectGraduationDelay(rule, academicTermId, studentInfos, ct));
                    break;
            }
        }

        if (generated.Count > 0)
        {
            db.WarningRecords.AddRange(generated);
            await db.SaveChangesAsync(ct);

            var studentsById = studentInfos.ToDictionary(student => student.Id);
            var rulesByType = rules.ToDictionary(rule => rule.Type);
            var classIds = studentInfos.Select(student => student.ClassId).Distinct().ToArray();
            var counselorsByClass = await db.AdministrativeClasses.AsNoTracking()
                .WhereIn(classIds, item => item.Id)
                .Where(item => item.CounselorUserId != null)
                .Select(item => new { item.Id, CounselorUserId = item.CounselorUserId!.Value })
                .ToDictionaryAsync(item => item.Id, item => item.CounselorUserId, ct);
            foreach (var w in generated)
            {
                var si = studentsById[w.StudentId];
                var r = rulesByType[w.Type];
                if (r.NotifyStudent && si.UserId.HasValue)
                    await NotificationService.SendAsync(db, si.UserId.Value, "学业预警", w.Detail, "/warnings", ct, NotificationCategory.Warning);
                if (r.NotifyCounselor && counselorsByClass.TryGetValue(si.ClassId, out var counselorId))
                {
                    await NotificationService.SendAsync(db, counselorId, "学生学业预警", $"{si.Name}：{w.Detail}", "/warnings", ct, NotificationCategory.Warning);
                }
            }
        }

        return Ok(new { Generated = generated.Count });
    }

    [HttpGet("records")]
    [Authorize(Roles = Managers + "," + SystemRoles.Counselor)]
    public async Task<ActionResult<PagedResult<WarningRecordListItem>>> GetRecords(
        Guid? academicTermId,
        WarningType? type,
        int page = 1,
        int pageSize = 20,
        CancellationToken ct = default)
    {
        if (page < 1 || pageSize is < 1 or > 100)
            return ValidationProblem("页码必须大于 0，且每页条数应在 1 至 100 之间。");

        var q = db.WarningRecords.AsNoTracking().AsQueryable();
        if (scope.Current.Scope == DataScope.College || scope.Current.IsInRole(SystemRoles.Counselor))
        {
            var classIds = await db.AdministrativeClasses.Where(c => c.CounselorUserId == scope.Current.UserId).Select(c => c.Id).ToListAsync(ct);
            if (classIds.Count > 0)
                q = q.WhereIn(classIds, x => x.Student!.AdministrativeClassId);
            else if (scope.Current.RestrictedCollegeId.HasValue)
                q = q.Where(x => x.Student!.AdministrativeClass!.Major!.CollegeId == scope.Current.RestrictedCollegeId);
        }
        if (academicTermId.HasValue) q = q.Where(x => x.AcademicTermId == academicTermId);
        if (type.HasValue) q = q.Where(x => x.Type == type);

        var total = await q.CountAsync(ct);
        var items = await q
            .OrderByDescending(x => x.CreatedAt)
            .ThenByDescending(x => x.Id)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(x => new WarningRecordListItem(
                x.Id,
                x.StudentId,
                x.Student!.Name,
                x.Student.StudentNumber,
                x.Student.AdministrativeClass!.Name,
                (int)x.Type,
                (int)x.Status,
                x.TriggerValue,
                x.Detail,
                x.AcknowledgedAt,
                x.AcknowledgeComment,
                x.CreatedAt))
            .ToListAsync(ct);
        return Ok(new PagedResult<WarningRecordListItem>(items, total, page, pageSize));
    }

    // ═══════════ Student ═══════════
    [HttpGet("my-warnings")]
    [Authorize(Roles = SystemRoles.Student)]
    public async Task<ActionResult> GetMyWarnings(CancellationToken ct)
    {
        var sid = await GetStudentIdAsync(ct);
        if (sid is null) return StudentNotFound();
        return Ok(await db.WarningRecords.AsNoTracking().Where(x => x.StudentId == sid).OrderByDescending(x => x.CreatedAt)
            .Select(x => new { x.Id, Type = (int)x.Type, Status = (int)x.Status, x.TriggerValue, x.Detail, x.AcknowledgedAt, x.AcknowledgeComment, x.CreatedAt, TermName = x.AcademicTermId })
            .ToListAsync(ct));
    }

    [HttpPost("{id:guid}/acknowledge")]
    [Authorize(Roles = SystemRoles.Student)]
    public async Task<ActionResult> Acknowledge(Guid id, [FromBody] AckBody body, CancellationToken ct)
    {
        var sid = await GetStudentIdAsync(ct);
        if (sid is null) return StudentNotFound();
        var w = await db.WarningRecords.FirstOrDefaultAsync(x => x.Id == id && x.StudentId == sid, ct);
        if (w is null) return NotFound();
        if (w.Status != WarningStatus.Active) return ConflictProblem("该预警已处理。");
        w.Status = WarningStatus.Acknowledged;
        w.AcknowledgedAt = DateTime.UtcNow;
        w.AcknowledgeComment = body.Comment?.Trim();
        await db.SaveChangesAsync(ct);
        return NoContent();
    }

    // ═══════════ Detection logic ═══════════
    private async Task<List<WarningRecord>> DetectFailedCredits(WarningRule rule, Guid termId, List<StudentInfo> students, CancellationToken ct)
    {
        var studentIds = students.Select(student => student.Id).ToArray();
        var existingStudentIds = await db.WarningRecords.AsNoTracking()
            .Where(record => record.AcademicTermId == termId && record.Type == rule.Type)
            .Select(record => record.StudentId)
            .ToHashSetAsync(ct);
        var failedByStudent = await db.GradeRecords.AsNoTracking()
            .Where(record => studentIds.Contains(record.StudentId) &&
                             record.GradeSheet!.TeachingTask!.AcademicTermId == termId &&
                             record.GradeSheet.Status == GradeSheetStatus.Published &&
                             record.TotalScore < 60)
            .GroupBy(record => record.StudentId)
            .Select(group => new { StudentId = group.Key, Failed = group.Count() })
            .ToListAsync(ct);
        return failedByStudent
            .Where(item => item.Failed >= rule.Threshold &&
                           !existingStudentIds.Contains(item.StudentId))
            .Select(item => new WarningRecord
            {
                StudentId = item.StudentId,
                Type = rule.Type,
                TriggerValue = item.Failed,
                Detail = $"不及格课程 {item.Failed} 门，达到预警阈值 {rule.Threshold} 门。",
                AcademicTermId = termId
            })
            .ToList();
    }

    private async Task<List<WarningRecord>> DetectLowGPA(WarningRule rule, Guid termId, List<StudentInfo> students, CancellationToken ct)
    {
        var studentIds = students.Select(student => student.Id).ToArray();
        var existingStudentIds = await ExistingWarningStudentIdsAsync(rule.Type, termId, ct);
        var gpaByStudent = await db.GradeRecords.AsNoTracking()
            .Where(record => studentIds.Contains(record.StudentId) &&
                             record.GradeSheet!.TeachingTask!.AcademicTermId == termId &&
                             record.GradeSheet.Status == GradeSheetStatus.Published &&
                             record.GradePoint != null)
            .GroupBy(record => record.StudentId)
            .Select(group => new { StudentId = group.Key, Gpa = group.Average(record => record.GradePoint!.Value) })
            .ToListAsync(ct);
        return gpaByStudent.Where(item => item.Gpa < rule.Threshold && !existingStudentIds.Contains(item.StudentId))
            .Select(item => new WarningRecord { StudentId = item.StudentId, Type = rule.Type, TriggerValue = Math.Round(item.Gpa, 2), Detail = $"平均绩点 {item.Gpa:F2}，低于预警阈值 {rule.Threshold}。", AcademicTermId = termId }).ToList();
    }

    private async Task<List<WarningRecord>> DetectAbsenteeism(WarningRule rule, Guid termId, List<StudentInfo> students, CancellationToken ct)
    {
        var studentIds = students.Select(student => student.Id).ToArray();
        var existingStudentIds = await ExistingWarningStudentIdsAsync(rule.Type, termId, ct);
        var absencesByStudent = await db.AttendanceRecords.AsNoTracking()
            .Where(record => studentIds.Contains(record.StudentId) && record.AttendanceSheet!.Status == AttendanceSheetStatus.Submitted && record.AttendanceSheet.TeachingTask!.AcademicTermId == termId && (record.Status == AttendanceStatus.Absent || record.Status == AttendanceStatus.Late))
            .GroupBy(record => record.StudentId).Select(group => new { StudentId = group.Key, Absent = group.Count() }).ToListAsync(ct);
        return absencesByStudent.Where(item => item.Absent >= rule.Threshold && !existingStudentIds.Contains(item.StudentId))
            .Select(item => new WarningRecord { StudentId = item.StudentId, Type = rule.Type, TriggerValue = item.Absent, Detail = $"缺勤/迟到 {item.Absent} 次，达到预警阈值 {rule.Threshold} 次。", AcademicTermId = termId }).ToList();
    }

    private async Task<List<WarningRecord>> DetectGraduationDelay(WarningRule rule, Guid termId, List<StudentInfo> students, CancellationToken ct)
    {
        var studentIds = students.Select(student => student.Id).ToArray();
        var existingStudentIds = await ExistingWarningStudentIdsAsync(rule.Type, termId, ct);
        var failedByStudent = await db.GradeRecords.AsNoTracking().Where(record => studentIds.Contains(record.StudentId) && record.GradeSheet!.Status == GradeSheetStatus.Published && record.TotalScore < 60).GroupBy(record => record.StudentId).Select(group => new { StudentId = group.Key, Failed = group.Count() }).ToListAsync(ct);
        return failedByStudent.Where(item => item.Failed >= rule.Threshold && !existingStudentIds.Contains(item.StudentId))
            .Select(item => new WarningRecord { StudentId = item.StudentId, Type = rule.Type, TriggerValue = item.Failed, Detail = $"累计不及格 {item.Failed} 门，达到延毕预警阈值 {rule.Threshold} 门。", AcademicTermId = termId }).ToList();
    }

    private Task<HashSet<Guid>> ExistingWarningStudentIdsAsync(WarningType type, Guid termId, CancellationToken ct) =>
        db.WarningRecords.AsNoTracking().Where(record => record.AcademicTermId == termId && record.Type == type).Select(record => record.StudentId).ToHashSetAsync(ct);

    private async Task<Guid?> GetStudentIdAsync(CancellationToken ct) => await db.Students.Where(s => s.UserId == scope.Current.UserId).Select(s => (Guid?)s.Id).FirstOrDefaultAsync(ct);
    private ActionResult StudentNotFound() => Conflict(new ProblemDetails { Title = "未关联学生档案", Status = 409 });
    private ActionResult ConflictProblem(string d) => Conflict(new ProblemDetails { Title = "操作失败", Detail = d, Status = 409 });
}

public sealed record StudentInfo(Guid Id, Guid? UserId, string Name, Guid ClassId);
public sealed record WarningRecordListItem(
    Guid Id,
    Guid StudentId,
    string StudentName,
    string StudentNumber,
    string ClassName,
    int Type,
    int Status,
    decimal TriggerValue,
    string Detail,
    DateTime? AcknowledgedAt,
    string? AcknowledgeComment,
    DateTime CreatedAt);
public sealed record WarningRuleDto(WarningType Type, [MaxLength(100)] string Name, decimal Threshold, bool IsEnabled, bool NotifyStudent, bool NotifyCounselor, [MaxLength(300)] string? Description, bool AutoCheckEnabled, int? CheckDayOfWeek, int CheckHour, int CheckMinute);
public sealed record AckBody([MaxLength(300)] string? Comment);
