using System.ComponentModel.DataAnnotations;
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/evaluations")]
public sealed class EvaluationsController(
    AppDbContext db,
    ICurrentUserDataScope currentUserDataScope) : ControllerBase
{
    private const string AdminRoles =
        SystemRoles.SuperAdmin + "," +
        SystemRoles.AcademicAdmin;

    private const string StaffRoles =
        SystemRoles.SuperAdmin + "," +
        SystemRoles.AcademicAdmin + "," +
        SystemRoles.CollegeAdmin + "," +
        SystemRoles.Teacher + "," +
        SystemRoles.Leader;

    // ── Admin: Manage setups ────────────────────────────────────────

    [HttpGet("setups")]
    [Authorize(Roles = AdminRoles)]
    public async Task<ActionResult> GetSetups(CancellationToken cancellationToken)
    {
        var setups = await db.EvaluationSetups.AsNoTracking()
            .Include(x => x.Dimensions.OrderBy(d => d.SortOrder))
            .Include(x => x.AcademicTerm)
            .OrderByDescending(x => x.AcademicTerm!.StartDate)
            .Select(x => new
            {
                x.Id,
                x.Name,
                x.Status,
                x.AcademicTermId,
                TermName = x.AcademicTerm!.Name,
                TermIsCurrent = x.AcademicTerm.IsCurrent,
                TermIsArchived = x.AcademicTerm.IsArchived,
                x.StartsAt,
                x.EndsAt,
                Dimensions = x.Dimensions.Select(d => new
                {
                    d.Id,
                    d.Name,
                    d.MaxScore,
                    d.SortOrder
                }),
                RecordCount = x.Records.Count,
                x.CreatedAt
            })
            .ToListAsync(cancellationToken);
        return Ok(setups);
    }

    [HttpPost("setups")]
    [Authorize(Roles = AdminRoles)]
    public async Task<ActionResult> CreateSetup(
        EvaluationSetupRequest request,
        CancellationToken cancellationToken)
    {
        if (request.Dimensions.Count == 0)
            return ValidationProblem("至少需要一个评分维度。");
        if (request.Dimensions.Sum(d => d.MaxScore) != 100)
            return ValidationProblem("所有维度的满分之和必须等于 100。");

        var termExists = await db.AcademicTerms.AnyAsync(
            x => x.Id == request.AcademicTermId, cancellationToken);
        if (!termExists) return ValidationProblem("所选学期不存在。");

        var existing = await db.EvaluationSetups
            .AnyAsync(x => x.AcademicTermId == request.AcademicTermId, cancellationToken);
        if (existing) return ConflictProblem("该学期已有评教方案。");

        var setup = new EvaluationSetup
        {
            AcademicTermId = request.AcademicTermId,
            Name = request.Name.Trim(),
            Status = EvaluationSetupStatus.Draft,
            StartsAt = request.StartsAt,
            EndsAt = request.EndsAt,
            Dimensions = request.Dimensions.Select((d, i) => new EvaluationDimension
            {
                Name = d.Name.Trim(),
                MaxScore = d.MaxScore,
                SortOrder = i
            }).ToList()
        };
        db.EvaluationSetups.Add(setup);
        await db.SaveChangesAsync(cancellationToken);
        return Created(string.Empty, new { setup.Id });
    }

    [HttpPut("setups/{id:guid}")]
    [Authorize(Roles = AdminRoles)]
    public async Task<ActionResult> UpdateSetup(
        Guid id,
        EvaluationSetupRequest request,
        CancellationToken cancellationToken)
    {
        if (request.Dimensions.Count == 0)
            return ValidationProblem("至少需要一个评分维度。");
        if (request.Dimensions.Sum(d => d.MaxScore) != 100)
            return ValidationProblem("所有维度的满分之和必须等于 100。");

        var setup = await db.EvaluationSetups
            .Include(x => x.Dimensions)
            .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
        if (setup is null) return NotFound();
        if (setup.Status == EvaluationSetupStatus.Closed)
            return ConflictProblem("已关闭的评教方案不能修改。");

        setup.Name = request.Name.Trim();
        setup.StartsAt = request.StartsAt;
        setup.EndsAt = request.EndsAt;

        db.EvaluationDimensions.RemoveRange(setup.Dimensions);
        setup.Dimensions = request.Dimensions.Select((d, i) => new EvaluationDimension
        {
            EvaluationSetupId = setup.Id,
            Name = d.Name.Trim(),
            MaxScore = d.MaxScore,
            SortOrder = i
        }).ToList();

        await db.SaveChangesAsync(cancellationToken);
        return NoContent();
    }

    [HttpPost("setups/{id:guid}/open")]
    [Authorize(Roles = AdminRoles)]
    public async Task<ActionResult> OpenSetup(Guid id, CancellationToken cancellationToken)
    {
        var setup = await db.EvaluationSetups.FindAsync([id], cancellationToken);
        if (setup is null) return NotFound();
        if (setup.Status == EvaluationSetupStatus.Closed)
            return ConflictProblem("已关闭的方案不能重新开启。");
        setup.Status = EvaluationSetupStatus.Open;
        await db.SaveChangesAsync(cancellationToken);
        return NoContent();
    }

    [HttpPost("setups/{id:guid}/close")]
    [Authorize(Roles = AdminRoles)]
    public async Task<ActionResult> CloseSetup(Guid id, CancellationToken cancellationToken)
    {
        var setup = await db.EvaluationSetups.FindAsync([id], cancellationToken);
        if (setup is null) return NotFound();
        setup.Status = EvaluationSetupStatus.Closed;
        await db.SaveChangesAsync(cancellationToken);
        return NoContent();
    }

    [HttpDelete("setups/{id:guid}")]
    [Authorize(Roles = AdminRoles)]
    public async Task<ActionResult> DeleteSetup(Guid id, CancellationToken cancellationToken)
    {
        var setup = await db.EvaluationSetups
            .Include(x => x.Records)
            .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
        if (setup is null) return NotFound();
        if (setup.Records.Count > 0)
            return ConflictProblem("已有评教记录的方案不能删除。");
        db.EvaluationSetups.Remove(setup);
        await db.SaveChangesAsync(cancellationToken);
        return NoContent();
    }

    // ── Student: Evaluate courses ────────────────────────────────────

    [HttpGet("my-tasks")]
    [Authorize(Roles = SystemRoles.Student)]
    public async Task<ActionResult> GetMyTasks(CancellationToken cancellationToken)
    {
        var userId = currentUserDataScope.Current.UserId;
        var studentId = await db.Students
            .Where(s => s.UserId == userId)
            .Select(s => (Guid?)s.Id)
            .FirstOrDefaultAsync(cancellationToken);
        if (!studentId.HasValue)
            return ConflictProblem("当前账号未关联学生档案。");

        // Find open evaluation setups
        var openSetups = await db.EvaluationSetups.AsNoTracking()
            .Where(x => x.Status == EvaluationSetupStatus.Open)
            .Select(x => new { x.Id, x.AcademicTermId })
            .ToListAsync(cancellationToken);

        if (openSetups.Count == 0)
            return Ok(new { Setups = Array.Empty<object>(), Tasks = Array.Empty<object>() });

        // Find enrolled teaching tasks for the student in those terms
        var termIds = openSetups.Select(x => x.AcademicTermId).ToHashSet();
        var enrollments = await db.CourseEnrollments.AsNoTracking()
            .Where(e =>
                e.Status == CourseEnrollmentStatus.Enrolled &&
                e.StudentId == studentId.Value)
            .WhereIn(
                termIds,
                e => e.CourseSelectionOffering!.TeachingTask!.AcademicTermId)
            .Select(e => new
            {
                e.CourseSelectionOffering!.TeachingTaskId,
                e.CourseSelectionOffering.TeachingTask!.AcademicTermId,
                e.CourseSelectionOffering.TeachingTask.TaskNumber,
                TaskName = e.CourseSelectionOffering.TeachingTask.Name,
                CourseCode = e.CourseSelectionOffering.TeachingTask.Course!.Code,
                CourseName = e.CourseSelectionOffering.TeachingTask.Course.Name,
                TeacherNames = e.CourseSelectionOffering.TeachingTask.Teachers
                    .OrderByDescending(t => t.IsPrimary)
                    .Select(t => t.Teacher!.Name)
            })
            .ToListAsync(cancellationToken);

        // Check which tasks have already been evaluated
        var setupIds = openSetups.Select(s => s.Id).ToArray();
        var evaluatedIds = await db.EvaluationRecords.AsNoTracking()
            .Where(r => r.StudentId == studentId.Value)
            .WhereIn(setupIds, r => r.EvaluationSetupId)
            .Select(r => r.TeachingTaskId)
            .ToHashSetAsync(cancellationToken);

        var tasks = enrollments.Select(e => new
        {
            e.TeachingTaskId,
            e.AcademicTermId,
            e.TaskNumber,
            e.TaskName,
            e.CourseCode,
            e.CourseName,
            e.TeacherNames,
            SetupId = openSetups.First(s => s.AcademicTermId == e.AcademicTermId).Id,
            IsEvaluated = evaluatedIds.Contains(e.TeachingTaskId)
        }).ToList();

        return Ok(new { Tasks = tasks });
    }

    [HttpGet("tasks/{teachingTaskId:guid}/form")]
    [Authorize(Roles = SystemRoles.Student)]
    public async Task<ActionResult> GetEvaluationForm(
        Guid teachingTaskId,
        CancellationToken cancellationToken)
    {
        var userId = currentUserDataScope.Current.UserId;
        var studentId = await db.Students
            .Where(s => s.UserId == userId)
            .Select(s => (Guid?)s.Id)
            .FirstOrDefaultAsync(cancellationToken);
        if (!studentId.HasValue)
            return ConflictProblem("当前账号未关联学生档案。");

        var task = await db.TeachingTasks.AsNoTracking()
            .Where(x => x.Id == teachingTaskId)
            .Select(x => new
            {
                x.Id,
                x.TaskNumber,
                x.Name,
                x.AcademicTermId,
                CourseCode = x.Course!.Code,
                CourseName = x.Course.Name,
                TeacherNames = x.Teachers
                    .OrderByDescending(t => t.IsPrimary)
                    .Select(t => t.Teacher!.Name)
            })
            .FirstOrDefaultAsync(cancellationToken);
        if (task is null) return NotFound();

        var setup = await db.EvaluationSetups.AsNoTracking()
            .Include(x => x.Dimensions.OrderBy(d => d.SortOrder))
            .Where(x => x.AcademicTermId == task.AcademicTermId &&
                        x.Status == EvaluationSetupStatus.Open)
            .FirstOrDefaultAsync(cancellationToken);
        if (setup is null) return ConflictProblem("当前学期没有开放的评教活动。");

        // Check if already evaluated
        var exists = await db.EvaluationRecords.AnyAsync(
            x => x.EvaluationSetupId == setup.Id &&
                 x.StudentId == studentId.Value &&
                 x.TeachingTaskId == teachingTaskId,
            cancellationToken);
        if (exists) return ConflictProblem("您已经对该课程进行过评教。");

        return Ok(new
        {
            SetupId = setup.Id,
            setup.Name,
            Dimensions = setup.Dimensions.Select(d => new
            {
                d.Id,
                d.Name,
                d.MaxScore,
                d.SortOrder
            }),
            Task = task
        });
    }

    [HttpPost("tasks/{teachingTaskId:guid}/submit")]
    [Authorize(Roles = SystemRoles.Student)]
    public async Task<ActionResult> SubmitEvaluation(
        Guid teachingTaskId,
        SubmitEvaluationRequest request,
        CancellationToken cancellationToken)
    {
        var userId = currentUserDataScope.Current.UserId;
        var studentId = await db.Students
            .Where(s => s.UserId == userId)
            .Select(s => (Guid?)s.Id)
            .FirstOrDefaultAsync(cancellationToken);
        if (!studentId.HasValue)
            return ConflictProblem("当前账号未关联学生档案。");

        var setup = await db.EvaluationSetups
            .Include(x => x.Dimensions)
            .FirstOrDefaultAsync(x => x.Id == request.SetupId, cancellationToken);
        if (setup is null) return NotFound("评教方案不存在。");
        if (setup.Status != EvaluationSetupStatus.Open)
            return ConflictProblem("该评教活动已结束。");

        // Validate against dimensions
        var dimMap = setup.Dimensions.ToDictionary(d => d.Id);
        if (request.Scores.Count != dimMap.Count)
            return ValidationProblem("评分维度数量不匹配。");

        var totalScore = 0;
        foreach (var score in request.Scores)
        {
            if (!dimMap.TryGetValue(score.DimensionId, out var dim))
                return ValidationProblem($"无效的评分维度。");
            if (score.Score < 0 || score.Score > dim.MaxScore)
                return ValidationProblem($"「{dim.Name}」评分超出范围（0-{dim.MaxScore}）。");
            totalScore += score.Score;
        }

        // Check duplicate
        var exists = await db.EvaluationRecords.AnyAsync(
            x => x.EvaluationSetupId == setup.Id &&
                 x.StudentId == studentId.Value &&
                 x.TeachingTaskId == teachingTaskId,
            cancellationToken);
        if (exists) return ConflictProblem("您已经对该课程进行过评教。");

        var record = new EvaluationRecord
        {
            EvaluationSetupId = setup.Id,
            StudentId = studentId.Value,
            TeachingTaskId = teachingTaskId,
            SubmittedAt = DateTime.UtcNow,
            Scores = request.Scores.Select(s => new EvaluationScore
            {
                EvaluationDimensionId = s.DimensionId,
                Score = s.Score
            }).ToList()
        };
        db.EvaluationRecords.Add(record);
        await db.SaveChangesAsync(cancellationToken);

        return Ok(new { record.Id, TotalScore = totalScore });
    }

    // ── Teacher / Staff: View results ─────────────────────────────────

    [HttpGet("my-results")]
    [Authorize(Roles = SystemRoles.Teacher)]
    public async Task<ActionResult> GetMyResults(CancellationToken cancellationToken)
    {
        var userId = currentUserDataScope.Current.UserId;
        var teacherId = await db.Teachers
            .Where(t => t.UserId == userId)
            .Select(t => (Guid?)t.Id)
            .FirstOrDefaultAsync(cancellationToken);
        if (!teacherId.HasValue)
            return ConflictProblem("当前账号未关联教师档案。");

        var taskIds = await db.TeachingTaskTeachers
            .Where(x => x.TeacherId == teacherId.Value)
            .Select(x => x.TeachingTaskId)
            .ToListAsync(cancellationToken);

        if (taskIds.Count == 0)
            return Ok(new { Tasks = Array.Empty<object>() });

        var taskSummaries = await db.EvaluationRecords.AsNoTracking()
            .WhereIn(taskIds, record => record.TeachingTaskId)
            .Where(record => record.EvaluationSetup!.Status == EvaluationSetupStatus.Closed)
            .GroupBy(record => new
            {
                record.EvaluationSetupId,
                SetupName = record.EvaluationSetup!.Name,
                SetupStartDate = record.EvaluationSetup.AcademicTerm!.StartDate,
                record.TeachingTaskId,
                TaskNumber = record.TeachingTask!.TaskNumber,
                TaskName = record.TeachingTask.Name,
                record.TeachingTask.AcademicTermId,
                CourseCode = record.TeachingTask.Course!.Code,
                CourseName = record.TeachingTask.Course.Name
            })
            .Select(group => new
            {
                group.Key,
                StudentCount = group.Count(),
                AverageTotal = group.Average(record =>
                    record.Scores.Sum(score => score.Score))
            })
            .OrderByDescending(x => x.Key.SetupStartDate)
            .ThenBy(x => x.Key.CourseCode)
            .ThenBy(x => x.Key.TaskNumber)
            .ToListAsync(cancellationToken);

        var dimensionSummaries = await (
            from score in db.EvaluationScores.AsNoTracking()
            join record in db.EvaluationRecords.AsNoTracking()
                on score.EvaluationRecordId equals record.Id
            join dimension in db.EvaluationDimensions.AsNoTracking()
                on score.EvaluationDimensionId equals dimension.Id
            where taskIds.Contains(record.TeachingTaskId) &&
                  record.EvaluationSetup!.Status == EvaluationSetupStatus.Closed
            group score by new
            {
                record.EvaluationSetupId,
                record.TeachingTaskId,
                dimension.Id,
                dimension.Name,
                dimension.MaxScore,
                dimension.SortOrder
            }
            into scoreGroup
            select new
            {
                scoreGroup.Key,
                AverageScore = scoreGroup.Average(score => score.Score),
                TotalScore = scoreGroup.Sum(score => score.Score),
                Count = scoreGroup.Count()
            })
            .ToListAsync(cancellationToken);

        var dimensionsByTask = dimensionSummaries
            .GroupBy(item => (item.Key.EvaluationSetupId, item.Key.TeachingTaskId))
            .ToDictionary(
                group => group.Key,
                group => (IReadOnlyCollection<TeacherEvaluationDimensionResult>)group
                    .OrderBy(item => item.Key.SortOrder)
                    .Select(item => new TeacherEvaluationDimensionResult(
                        item.Key.Id,
                        item.Key.Name,
                        item.Key.MaxScore,
                        Math.Round((double)item.AverageScore, 1),
                        item.TotalScore,
                        item.Count))
                    .ToArray());
        var result = taskSummaries.Select(item => new TeacherEvaluationTaskResult(
            item.Key.EvaluationSetupId,
            item.Key.SetupName,
            new TeacherEvaluationTask(
                item.Key.TeachingTaskId,
                item.Key.TaskNumber,
                item.Key.TaskName,
                item.Key.AcademicTermId,
                item.Key.CourseCode,
                item.Key.CourseName),
            item.StudentCount,
            Math.Round((double)item.AverageTotal, 1),
            dimensionsByTask.GetValueOrDefault(
                (item.Key.EvaluationSetupId, item.Key.TeachingTaskId),
                Array.Empty<TeacherEvaluationDimensionResult>())))
            .ToArray();

        return Ok(new { Tasks = result });
    }

    [HttpGet("setups/{id:guid}/results")]
    [Authorize(Roles = StaffRoles)]
    public async Task<ActionResult> GetSetupResults(
        Guid id,
        CancellationToken cancellationToken)
    {
        var setup = await db.EvaluationSetups.AsNoTracking()
            .Include(x => x.Dimensions.OrderBy(d => d.SortOrder))
            .Include(x => x.AcademicTerm)
            .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
        if (setup is null) return NotFound();

        var records = await db.EvaluationRecords.AsNoTracking()
            .Where(r => r.EvaluationSetupId == id)
            .Include(r => r.Scores)
            .Include(r => r.TeachingTask)
            .ThenInclude(t => t!.Course)
            .ThenInclude(c => c!.College)
            .ToListAsync(cancellationToken);

        // By teaching task
        var byTask = records
            .GroupBy(r => new
            {
                r.TeachingTaskId,
                r.TeachingTask!.TaskNumber,
                TaskName = r.TeachingTask.Name,
                CourseName = r.TeachingTask.Course!.Name,
                CollegeName = r.TeachingTask.Course.College!.Name,
                TeacherNames = string.Join("、",
                    r.TeachingTask.Teachers
                        .OrderByDescending(t => t.IsPrimary)
                        .Select(t => t.Teacher!.Name))
            })
            .Select(g =>
            {
                var dimAvgs = setup.Dimensions.Select(dim =>
                {
                    var scores = g
                        .SelectMany(r => r.Scores)
                        .Where(s => s.EvaluationDimensionId == dim.Id)
                        .Select(s => (double)s.Score)
                        .ToList();
                    return new
                    {
                        DimensionName = dim.Name,
                        AverageScore = scores.Count > 0
                            ? Math.Round(scores.Average(), 1) : 0,
                        Count = scores.Count
                    };
                }).ToList();

                var totals = g.Select(r => (double)r.Scores.Sum(s => s.Score)).ToList();
                return new
                {
                    g.Key.TeachingTaskId,
                    g.Key.TaskNumber,
                    g.Key.TaskName,
                    g.Key.CourseName,
                    g.Key.CollegeName,
                    g.Key.TeacherNames,
                    StudentCount = g.Count(),
                    AverageTotal = totals.Count > 0
                        ? Math.Round(totals.Average(), 1) : 0,
                    Dimensions = dimAvgs
                };
            })
            .OrderByDescending(x => x.AverageTotal)
            .ToList();

        return Ok(new
        {
            Setup = new
            {
                setup.Id,
                setup.Name,
                setup.Status,
                TermName = setup.AcademicTerm!.Name,
                setup.StartsAt,
                setup.EndsAt,
                Dimensions = setup.Dimensions.Select(d => new
                {
                    d.Id,
                    d.Name,
                    d.MaxScore,
                    d.SortOrder
                })
            },
            ByTask = byTask,
            TotalRecords = records.Count,
            DistinctTasks = byTask.Count,
            DistinctStudents = records.Select(r => r.StudentId).Distinct().Count()
        });
    }

    // ── Helpers ──────────────────────────────────────────────────────

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

public sealed record EvaluationSetupRequest(
    Guid AcademicTermId,
    [MaxLength(120)] string Name,
    DateTime? StartsAt,
    DateTime? EndsAt,
    IReadOnlyCollection<EvaluationDimensionRequest> Dimensions);

public sealed record EvaluationDimensionRequest(
    [MaxLength(60)] string Name,
    int MaxScore);

public sealed record SubmitEvaluationRequest(
    Guid SetupId,
    IReadOnlyCollection<EvaluationScoreRequest> Scores);

public sealed record EvaluationScoreRequest(
    Guid DimensionId,
    int Score);

public sealed record TeacherEvaluationTaskResult(
    Guid SetupId,
    string SetupName,
    TeacherEvaluationTask Task,
    int StudentCount,
    double AverageTotal,
    IReadOnlyCollection<TeacherEvaluationDimensionResult> Dimensions);

public sealed record TeacherEvaluationTask(
    Guid Id,
    string TaskNumber,
    string Name,
    Guid AcademicTermId,
    string CourseCode,
    string CourseName);

public sealed record TeacherEvaluationDimensionResult(
    Guid DimensionId,
    string DimensionName,
    int MaxScore,
    double AverageScore,
    decimal TotalScore,
    int Count);
