using System.ComponentModel.DataAnnotations;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Graduation;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Jiaowu.Api.Controllers;

[ApiController]
[Authorize]
[Route("api/graduation-clearance")]
public sealed class GraduationClearanceController(
    AppDbContext db,
    ICurrentUserDataScope currentUserDataScope) : ControllerBase
{
    private const string Managers =
        SystemRoles.SuperAdmin + "," + SystemRoles.AcademicAdmin;
    private const string Workers =
        Managers + "," + SystemRoles.CollegeAdmin + "," + SystemRoles.Counselor;

    [HttpGet("batches")]
    [Authorize(Roles = Workers)]
    public async Task<ActionResult> GetBatches(CancellationToken token)
    {
        var batches = await db.GraduationClearanceBatches.AsNoTracking()
            .OrderByDescending(x => x.GraduationYear)
            .ThenByDescending(x => x.CreatedAt)
            .Select(x => new
            {
                x.Id, x.Name, x.GraduationYear, x.Status, x.Notes,
                x.ClosedAt, x.CreatedAt, ItemCount = x.Items.Count
            }).ToListAsync(token);
        var statistics = await ScopedRecords().AsNoTracking()
            .GroupBy(x => x.GraduationClearanceItem!.GraduationClearanceBatchId)
            .Select(group => new
            {
                BatchId = group.Key,
                StudentCount = group.Select(x => x.StudentId).Distinct().Count(),
                RecordCount = group.Count(),
                CompletedCount = group.Count(x =>
                    x.Status != GraduationClearanceRecordStatus.Pending)
            }).ToDictionaryAsync(x => x.BatchId, token);
        return Ok(batches.Select(batch =>
        {
            statistics.TryGetValue(batch.Id, out var summary);
            return new
            {
                batch.Id, batch.Name, batch.GraduationYear, batch.Status,
                batch.Notes, batch.ClosedAt, batch.CreatedAt, batch.ItemCount,
                StudentCount = summary?.StudentCount ?? 0,
                RecordCount = summary?.RecordCount ?? 0,
                CompletedCount = summary?.CompletedCount ?? 0
            };
        }));
    }

    [HttpGet("batches/{id:guid}")]
    [Authorize(Roles = Workers)]
    public async Task<ActionResult> GetBatch(
        Guid id,
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 20,
        [FromQuery] string? keyword = null,
        [FromQuery] string? progress = null,
        CancellationToken token = default)
    {
        page = Math.Max(page, 1);
        pageSize = Math.Clamp(pageSize, 10, 100);
        var normalizedProgress = progress?.Trim();
        if (normalizedProgress is not null and not "Pending" and not "Completed")
            return ValidationProblem("离校办理进度筛选值无效。");

        var batch = await db.GraduationClearanceBatches.AsNoTracking()
            .Where(x => x.Id == id)
            .Select(x => new
            {
                x.Id, x.Name, x.GraduationYear, x.Status,
                x.Notes, x.ClosedAt, x.CreatedAt,
                Items = x.Items.OrderBy(item => item.SortOrder).Select(item => new
                {
                    item.Id, item.Code, item.Name, item.ResponsibleUnit,
                    item.ResponsibleRole, item.IsRequired, item.SortOrder
                })
            }).FirstOrDefaultAsync(token);
        if (batch is null) return NotFound();

        var records = ScopedRecords().AsNoTracking()
            .Where(x => x.GraduationClearanceItem!.GraduationClearanceBatchId == id);
        var summary = await records
            .GroupBy(_ => 1)
            .Select(group => new
            {
                StudentCount = group.Select(x => x.StudentId).Distinct().Count(),
                RecordCount = group.Count(),
                CompletedCount = group.Count(x =>
                    x.Status != GraduationClearanceRecordStatus.Pending)
            })
            .FirstOrDefaultAsync(token);

        var students = db.Students.AsNoTracking()
            .Where(student => records.Any(record => record.StudentId == student.Id));
        var normalizedKeyword = keyword?.Trim();
        if (!string.IsNullOrWhiteSpace(normalizedKeyword))
            students = students.Where(student =>
                student.StudentNumber.Contains(normalizedKeyword) ||
                student.Name.Contains(normalizedKeyword) ||
                student.AdministrativeClass!.Name.Contains(normalizedKeyword) ||
                student.AdministrativeClass.Major!.Name.Contains(normalizedKeyword));
        if (normalizedProgress == "Pending")
            students = students.Where(student => records.Any(record =>
                record.StudentId == student.Id &&
                record.GraduationClearanceItem!.IsRequired &&
                record.Status == GraduationClearanceRecordStatus.Pending));
        else if (normalizedProgress == "Completed")
            students = students.Where(student => !records.Any(record =>
                record.StudentId == student.Id &&
                record.GraduationClearanceItem!.IsRequired &&
                record.Status == GraduationClearanceRecordStatus.Pending));

        var total = await students.CountAsync(token);
        var studentPage = await students
            .OrderBy(student => student.StudentNumber)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(student => new
            {
                student.Id,
                student.StudentNumber,
                student.Name,
                ClassName = student.AdministrativeClass!.Name,
                MajorName = student.AdministrativeClass.Major!.Name
            }).ToListAsync(token);
        var studentIds = studentPage.Select(student => student.Id).ToArray();
        var pageRecords = await records
            .WhereIn(studentIds, x => x.StudentId)
            .OrderBy(x => x.Student!.StudentNumber)
            .ThenBy(x => x.GraduationClearanceItem!.SortOrder)
            .Select(x => new
            {
                x.Id, x.StudentId, x.Student!.StudentNumber, x.Student.Name,
                ClassName = x.Student.AdministrativeClass!.Name,
                MajorName = x.Student.AdministrativeClass.Major!.Name,
                CollegeName = x.Student.AdministrativeClass.Major.College!.Name,
                ItemId = x.GraduationClearanceItemId,
                ItemName = x.GraduationClearanceItem!.Name,
                x.GraduationClearanceItem.ResponsibleUnit,
                x.GraduationClearanceItem.ResponsibleRole,
                x.GraduationClearanceItem.IsRequired,
                x.Status, x.Notes, x.CompletedAt
            }).ToListAsync(token);
        var recordsByStudent = pageRecords
            .GroupBy(record => record.StudentId)
            .ToDictionary(group => group.Key, group => group.ToList());
        var studentItems = studentPage.Select(student => new
        {
            StudentId = student.Id,
            student.StudentNumber,
            student.Name,
            student.ClassName,
            student.MajorName,
            Records = recordsByStudent.GetValueOrDefault(student.Id) ?? []
        }).ToList();
        return Ok(new
        {
            batch.Id, batch.Name, batch.GraduationYear, batch.Status,
            batch.Notes, batch.ClosedAt, batch.CreatedAt, batch.Items,
            StudentCount = summary?.StudentCount ?? 0,
            RecordCount = summary?.RecordCount ?? 0,
            CompletedCount = summary?.CompletedCount ?? 0,
            Students = new
            {
                Items = studentItems,
                Total = total,
                Page = page,
                PageSize = pageSize
            }
        });
    }

    [HttpPost("batches")]
    [Authorize(Roles = Managers)]
    public async Task<ActionResult> Create(
        GraduationClearanceBatchRequest request,
        CancellationToken token)
    {
        var allowedRoles = new[]
        {
            SystemRoles.AcademicAdmin,
            SystemRoles.CollegeAdmin,
            SystemRoles.Counselor
        };
        if (request.Items.Count == 0)
            return ValidationProblem("至少配置一个离校事项。");
        if (request.Items.Select(x => x.Code.Trim())
            .Distinct(StringComparer.OrdinalIgnoreCase).Count() != request.Items.Count)
            return ValidationProblem("离校事项编码不能重复。");
        if (request.Items.Any(x => !allowedRoles.Contains(
                x.ResponsibleRole, StringComparer.OrdinalIgnoreCase)))
            return ValidationProblem("离校事项责任角色无效。");

        var batch = new GraduationClearanceBatch
        {
            Name = request.Name.Trim(),
            GraduationYear = request.GraduationYear,
            Notes = request.Notes?.Trim(),
            Items = request.Items.Select((item, index) =>
                new GraduationClearanceItem
                {
                    Code = item.Code.Trim(),
                    Name = item.Name.Trim(),
                    ResponsibleUnit = item.ResponsibleUnit.Trim(),
                    ResponsibleRole = item.ResponsibleRole,
                    IsRequired = item.IsRequired,
                    SortOrder = index + 1
                }).ToList()
        };
        db.GraduationClearanceBatches.Add(batch);
        await db.SaveChangesAsync(token);
        return Created(string.Empty, new { batch.Id });
    }

    [HttpPost("batches/{id:guid}/generate")]
    [Authorize(Roles = Managers)]
    public async Task<ActionResult> Generate(Guid id, CancellationToken token)
    {
        var batch = await db.GraduationClearanceBatches
            .Include(x => x.Items)
            .FirstOrDefaultAsync(x => x.Id == id, token);
        if (batch is null) return NotFound();
        if (batch.Status != GraduationClearanceBatchStatus.Open)
            return ConflictProblem("已关闭批次不能重新生成办理记录。");

        var studentIds = await db.GraduationAuditResults.AsNoTracking()
            .Where(x =>
                x.GraduationAuditBatch!.GraduationYear == batch.GraduationYear &&
                x.GraduationAuditBatch.Status == GraduationAuditBatchStatus.Published &&
                x.Conclusion == GraduationAuditConclusion.Eligible &&
                x.Student!.Status == StudentStatus.Graduated)
            .Select(x => x.StudentId)
            .Distinct()
            .ToListAsync(token);
        var existing = await db.GraduationClearanceRecords.AsNoTracking()
            .Where(x => x.GraduationClearanceItem!.GraduationClearanceBatchId == id)
            .Select(x => new { x.GraduationClearanceItemId, x.StudentId })
            .ToListAsync(token);
        var existingKeys = existing
            .Select(x => (x.GraduationClearanceItemId, x.StudentId))
            .ToHashSet();
        var created = 0;
        foreach (var item in batch.Items)
        foreach (var studentId in studentIds)
        {
            if (existingKeys.Contains((item.Id, studentId))) continue;
            db.GraduationClearanceRecords.Add(new GraduationClearanceRecord
            {
                GraduationClearanceItemId = item.Id,
                StudentId = studentId
            });
            created++;
        }
        await db.SaveChangesAsync(token);
        return Ok(new { StudentCount = studentIds.Count, CreatedRecordCount = created });
    }

    [HttpPut("records/{id:guid}")]
    [Authorize(Roles = Workers)]
    public async Task<ActionResult> UpdateRecord(
        Guid id,
        GraduationClearanceRecordRequest request,
        CancellationToken token)
    {
        var record = await db.GraduationClearanceRecords
            .Include(x => x.GraduationClearanceItem)
            .ThenInclude(x => x!.GraduationClearanceBatch)
            .Include(x => x.Student)
            .ThenInclude(x => x!.AdministrativeClass)
            .ThenInclude(x => x!.Major)
            .FirstOrDefaultAsync(x => x.Id == id, token);
        if (record is null) return NotFound();
        if (record.GraduationClearanceItem!.GraduationClearanceBatch!.Status !=
            GraduationClearanceBatchStatus.Open)
            return ConflictProblem("批次已关闭，不能修改办理记录。");
        if (!CanManage(record)) return Forbid();

        record.Status = request.Status;
        record.Notes = request.Notes?.Trim();
        record.CompletedAt = request.Status == GraduationClearanceRecordStatus.Pending
            ? null
            : DateTime.UtcNow;
        record.CompletedByUserId =
            request.Status == GraduationClearanceRecordStatus.Pending
                ? null
                : currentUserDataScope.Current.UserId;
        await db.SaveChangesAsync(token);
        return NoContent();
    }

    [HttpPost("batches/{id:guid}/close")]
    [Authorize(Roles = Managers)]
    public async Task<ActionResult> Close(Guid id, CancellationToken token)
    {
        var batch = await db.GraduationClearanceBatches
            .FirstOrDefaultAsync(x => x.Id == id, token);
        if (batch is null) return NotFound();
        if (batch.Status != GraduationClearanceBatchStatus.Open)
            return ConflictProblem("该批次已经关闭。");
        var hasIncompleteRequiredItem = await db.GraduationClearanceRecords
            .AnyAsync(record =>
                record.GraduationClearanceItem!.GraduationClearanceBatchId == id &&
                record.GraduationClearanceItem.IsRequired &&
                record.Status == GraduationClearanceRecordStatus.Pending,
                token);
        if (hasIncompleteRequiredItem)
            return ConflictProblem("仍有必办离校事项尚未完成。");
        batch.Status = GraduationClearanceBatchStatus.Closed;
        batch.ClosedAt = DateTime.UtcNow;
        await db.SaveChangesAsync(token);
        return NoContent();
    }

    [HttpGet("my-clearance")]
    [Authorize(Roles = SystemRoles.Student)]
    public async Task<ActionResult> GetMyClearance(CancellationToken token)
    {
        var userId = currentUserDataScope.Current.UserId;
        var batch = await db.GraduationClearanceBatches.AsNoTracking()
            .Where(x => x.Items.Any(item =>
                item.Records.Any(record => record.Student!.UserId == userId)))
            .OrderByDescending(x => x.GraduationYear)
            .ThenByDescending(x => x.CreatedAt)
            .Select(x => new
            {
                x.Id, x.Name, x.GraduationYear, x.Status, x.Notes, x.ClosedAt,
                Items = x.Items.OrderBy(item => item.SortOrder)
                    .SelectMany(item => item.Records
                        .Where(record => record.Student!.UserId == userId)
                        .Select(record => new
                        {
                            record.Id, ItemName = item.Name, item.ResponsibleUnit,
                            item.IsRequired, record.Status,
                            record.Notes, record.CompletedAt
                        }))
            }).FirstOrDefaultAsync(token);
        return Ok(batch);
    }

    private IQueryable<GraduationClearanceRecord> ScopedRecords()
    {
        var scope = currentUserDataScope.Current;
        var source = db.GraduationClearanceRecords.AsQueryable();
        if (scope.IsInRole(SystemRoles.Counselor))
            return source.Where(x =>
                x.Student!.AdministrativeClass!.CounselorUserId == scope.UserId);
        if (scope.IsInRole(SystemRoles.CollegeAdmin))
            return source.Where(x =>
                x.Student!.AdministrativeClass!.Major!.CollegeId == scope.CollegeId);
        return source;
    }

    private bool CanManage(GraduationClearanceRecord record)
    {
        var scope = currentUserDataScope.Current;
        if (scope.IsInRole(SystemRoles.SuperAdmin)) return true;
        var role = record.GraduationClearanceItem!.ResponsibleRole;
        if (role == SystemRoles.AcademicAdmin &&
            scope.IsInRole(SystemRoles.AcademicAdmin)) return true;
        if (role == SystemRoles.CollegeAdmin &&
            scope.IsInRole(SystemRoles.CollegeAdmin))
            return record.Student!.AdministrativeClass!.Major!.CollegeId ==
                   scope.CollegeId;
        return role == SystemRoles.Counselor &&
               scope.IsInRole(SystemRoles.Counselor) &&
               record.Student!.AdministrativeClass!.CounselorUserId == scope.UserId;
    }

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

public sealed record GraduationClearanceBatchRequest(
    [Required, MinLength(3), MaxLength(120)] string Name,
    [Range(2000, 2200)] int GraduationYear,
    [MaxLength(500)] string? Notes,
    IReadOnlyList<GraduationClearanceItemRequest> Items);

public sealed record GraduationClearanceItemRequest(
    [Required, MinLength(2), MaxLength(30)] string Code,
    [Required, MinLength(2), MaxLength(100)] string Name,
    [Required, MinLength(2), MaxLength(100)] string ResponsibleUnit,
    [Required, MaxLength(30)] string ResponsibleRole,
    bool IsRequired);

public sealed record GraduationClearanceRecordRequest(
    GraduationClearanceRecordStatus Status,
    [MaxLength(500)] string? Notes);
