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

namespace Jiaowu.Api.Controllers;

[ApiController]
[Authorize(Roles = ReadRoles)]
[Route("api/curriculum-plans")]
public sealed class CurriculumPlansController(
    AppDbContext db,
    ICurrentUserDataScope currentUserDataScope) : ControllerBase
{
    private const string ReadRoles =
        SystemRoles.SuperAdmin + "," +
        SystemRoles.AcademicAdmin + "," +
        SystemRoles.CollegeAdmin;

    [HttpGet]
    public async Task<ActionResult<PagedResult<object>>> GetPlans(
        int page = 1,
        int pageSize = 20,
        string? keyword = null,
        Guid? collegeId = null,
        Guid? majorId = null,
        int? effectiveGrade = null,
        CurriculumPlanStatus? status = null,
        CancellationToken cancellationToken = default)
    {
        page = Math.Max(1, page);
        pageSize = Math.Clamp(pageSize, 10, 100);
        var source = ScopedPlans().AsNoTracking();
        if (collegeId.HasValue)
            source = source.Where(x => x.Major!.CollegeId == collegeId.Value);
        if (majorId.HasValue) source = source.Where(x => x.MajorId == majorId);
        if (effectiveGrade.HasValue)
            source = source.Where(x => x.EffectiveGrade == effectiveGrade);
        if (status.HasValue) source = source.Where(x => x.Status == status);
        if (!string.IsNullOrWhiteSpace(keyword))
        {
            keyword = keyword.Trim();
            source = source.Where(x =>
                x.Name.Contains(keyword) ||
                x.Version.Contains(keyword) ||
                x.Major!.Name.Contains(keyword));
        }

        var total = await source.CountAsync(cancellationToken);
        var items = await source
            .OrderByDescending(x => x.EffectiveGrade)
            .ThenByDescending(x => x.CreatedAt)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(x => new
            {
                x.Id,
                x.Name,
                x.Version,
                x.MajorId,
                MajorName = x.Major!.Name,
                CollegeId = x.Major.CollegeId,
                CollegeName = x.Major.College!.Name,
                x.EffectiveGrade,
                x.TotalCredits,
                x.Status,
                x.PublishedAt,
                ModuleCount = x.Modules.Count,
                CourseCount = x.Modules.SelectMany(module => module.Courses).Count(),
                x.UpdatedAt
            })
            .ToListAsync(cancellationToken);

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

    [HttpGet("filter-options")]
    public async Task<ActionResult> GetFilterOptions(CancellationToken cancellationToken)
    {
        var collegeId = ScopedCollegeId();
        var majors = db.Majors.AsNoTracking().Where(x => x.IsEnabled);
        if (collegeId.HasValue)
            majors = majors.Where(x => x.CollegeId == collegeId.Value);

        var majorOptions = await majors
            .OrderBy(x => x.College!.SortOrder)
            .ThenBy(x => x.SortOrder)
            .ThenBy(x => x.Code)
            .Select(x => new
            {
                x.Id,
                x.Code,
                x.Name,
                x.CollegeId,
                CollegeName = x.College!.Name,
                x.SchoolingYears
            })
            .ToListAsync(cancellationToken);

        var accessibleMajors = db.Majors.AsNoTracking().Where(x => x.IsEnabled);
        if (collegeId.HasValue)
            accessibleMajors = accessibleMajors.Where(
                x => x.CollegeId == collegeId.Value);
        var colleges = await db.Colleges.AsNoTracking()
            .Where(x =>
                x.IsEnabled &&
                accessibleMajors.Any(major => major.CollegeId == x.Id))
            .OrderBy(x => x.SortOrder)
            .ThenBy(x => x.Code)
            .Select(x => new { x.Id, x.Code, x.Name })
            .ToListAsync(cancellationToken);
        var grades = await ScopedPlans()
            .AsNoTracking()
            .Select(x => x.EffectiveGrade)
            .Distinct()
            .OrderByDescending(x => x)
            .ToListAsync(cancellationToken);

        return Ok(new { Colleges = colleges, Majors = majorOptions, Grades = grades });
    }

    [HttpGet("{id:guid}")]
    public async Task<ActionResult> GetPlan(Guid id, CancellationToken cancellationToken)
    {
        var plan = await ScopedPlans()
            .AsNoTracking()
            .Where(x => x.Id == id)
            .Select(x => new
            {
                x.Id,
                x.Name,
                x.Version,
                x.MajorId,
                MajorName = x.Major!.Name,
                SchoolingYears = x.Major.SchoolingYears,
                CollegeId = x.Major.CollegeId,
                CollegeName = x.Major.College!.Name,
                x.EffectiveGrade,
                x.TotalCredits,
                x.Status,
                x.Description,
                x.PublishedAt,
                x.CreatedAt,
                x.UpdatedAt,
                Modules = x.Modules
                    .OrderBy(module => module.SortOrder)
                    .ThenBy(module => module.Code)
                    .Select(module => new
                    {
                        module.Id,
                        module.Code,
                        module.Name,
                        module.RequiredCredits,
                        module.SortOrder,
                        AssignedCredits = module.Courses.Sum(course => course.Course!.Credits),
                        Courses = module.Courses
                            .OrderBy(course => course.RecommendedSemester)
                            .ThenBy(course => course.Course!.Code)
                            .Select(course => new
                            {
                                course.Id,
                                course.CourseId,
                                CourseCode = course.Course!.Code,
                                CourseName = course.Course.Name,
                                course.Course.Credits,
                                course.Course.TotalHours,
                                course.Course.Nature,
                                CategoryName = course.Course.CourseCategory != null
                                    ? course.Course.CourseCategory.Name
                                    : null,
                                course.RecommendedSemester,
                                course.Type,
                                course.Notes
                            })
                    })
            })
            .FirstOrDefaultAsync(cancellationToken);

        return plan is null ? NotFound() : Ok(plan);
    }

    [HttpPost]
    public async Task<ActionResult> Create(
        CurriculumPlanRequest request,
        CancellationToken cancellationToken)
    {
        var major = await FindAccessibleMajorAsync(request.MajorId, cancellationToken);
        if (major is null) return ValidationProblem("所选专业不存在或不在当前数据范围内。");

        var plan = new CurriculumPlan
        {
            MajorId = request.MajorId,
            Name = request.Name.Trim(),
            Version = request.Version.Trim(),
            EffectiveGrade = request.EffectiveGrade,
            TotalCredits = request.TotalCredits,
            Description = Normalize(request.Description)
        };
        db.CurriculumPlans.Add(plan);
        return await SaveCreatedAsync(plan.Id, cancellationToken);
    }

    [HttpPut("{id:guid}")]
    public async Task<ActionResult> Update(
        Guid id,
        CurriculumPlanRequest request,
        CancellationToken cancellationToken)
    {
        var plan = await EditablePlanAsync(id, cancellationToken);
        if (plan is null) return NotFound();
        if (!CurriculumPlanEditingPolicy.CanModifyContent(plan.Status))
            return ConflictProblem("已归档的培养方案不可修改。");
        if (!CurriculumPlanEditingPolicy.CanChangeApplicability(plan.Status) &&
            (plan.MajorId != request.MajorId ||
             plan.EffectiveGrade != request.EffectiveGrade ||
             !string.Equals(plan.Version, request.Version.Trim(), StringComparison.Ordinal)))
        {
            return ConflictProblem(
                "已发布方案可以修改名称、学分说明和课程结构；如需变更适用专业、入学年级或版本号，请复制为新版本。");
        }
        var major = await FindAccessibleMajorAsync(request.MajorId, cancellationToken);
        if (major is null) return ValidationProblem("所选专业不存在或不在当前数据范围内。");

        plan.MajorId = request.MajorId;
        plan.Name = request.Name.Trim();
        plan.Version = request.Version.Trim();
        plan.EffectiveGrade = request.EffectiveGrade;
        plan.TotalCredits = request.TotalCredits;
        plan.Description = Normalize(request.Description);
        return await SaveNoContentAsync(cancellationToken);
    }

    [HttpDelete("{id:guid}")]
    public async Task<ActionResult> Delete(Guid id, CancellationToken cancellationToken)
    {
        var plan = await EditablePlanAsync(id, cancellationToken);
        if (plan is null) return NotFound();
        if (plan.Status != CurriculumPlanStatus.Draft)
            return ConflictProblem("仅草稿方案可以删除。");
        db.CurriculumPlans.Remove(plan);
        return await SaveNoContentAsync(cancellationToken);
    }

    [HttpPost("{id:guid}/clone")]
    public async Task<ActionResult> Clone(
        Guid id,
        CloneCurriculumPlanRequest request,
        CancellationToken cancellationToken)
    {
        var source = await ScopedPlans()
            .AsNoTracking()
            .Include(x => x.Modules)
            .ThenInclude(x => x.Courses)
            .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
        if (source is null) return NotFound();

        var copy = new CurriculumPlan
        {
            MajorId = source.MajorId,
            Name = request.Name.Trim(),
            Version = request.Version.Trim(),
            EffectiveGrade = request.EffectiveGrade,
            TotalCredits = source.TotalCredits,
            Description = source.Description,
            Modules = source.Modules.Select(module => new CurriculumModule
            {
                Code = module.Code,
                Name = module.Name,
                RequiredCredits = module.RequiredCredits,
                SortOrder = module.SortOrder,
                Courses = module.Courses.Select(course => new CurriculumCourse
                {
                    CourseId = course.CourseId,
                    RecommendedSemester = course.RecommendedSemester,
                    Type = course.Type,
                    Notes = course.Notes
                }).ToList()
            }).ToList()
        };
        db.CurriculumPlans.Add(copy);
        return await SaveCreatedAsync(copy.Id, cancellationToken);
    }

    [HttpPost("{id:guid}/publish")]
    public async Task<ActionResult> Publish(Guid id, CancellationToken cancellationToken)
    {
        return await db.ExecuteInRetriableTransactionAsync<ActionResult>(
            async transaction =>
            {
                db.ChangeTracker.Clear();
                var plan = await ScopedPlans()
                    .Include(x => x.Major)
                    .Include(x => x.Modules)
                    .ThenInclude(x => x.Courses)
                    .ThenInclude(x => x.Course)
                    .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
                if (plan is null) return NotFound();
                if (plan.Status != CurriculumPlanStatus.Draft)
                    return ConflictProblem("只有草稿方案可以发布。");
                if (plan.Modules.Count == 0 ||
                    plan.Modules.Any(x => x.Courses.Count == 0))
                    return ConflictProblem("发布前每个课程模块都必须配置课程。");
                if (plan.Modules.Sum(x => x.RequiredCredits) != plan.TotalCredits)
                    return ConflictProblem("各模块最低学分之和必须等于方案总学分。");
                if (plan.Modules.Any(module =>
                        module.Courses.Sum(x => x.Course!.Credits) <
                        module.RequiredCredits))
                    return ConflictProblem(
                        "存在课程学分合计低于最低学分要求的模块。");
                if (plan.Modules.SelectMany(x => x.Courses)
                    .GroupBy(x => x.CourseId)
                    .Any(x => x.Count() > 1))
                    return ConflictProblem("同一门课程不能重复加入多个模块。");
                if (plan.Modules.SelectMany(x => x.Courses).Any(x =>
                        x.RecommendedSemester >
                        plan.Major!.SchoolingYears * 2))
                    return ConflictProblem("建议学期超出了该专业学制。");

                var previousPlans = await ScopedPlans()
                    .Where(x =>
                        x.Id != plan.Id &&
                        x.MajorId == plan.MajorId &&
                        x.EffectiveGrade == plan.EffectiveGrade &&
                        x.Status == CurriculumPlanStatus.Published)
                    .ToListAsync(cancellationToken);
                foreach (var previous in previousPlans)
                {
                    previous.Status = CurriculumPlanStatus.Archived;
                }

                plan.Status = CurriculumPlanStatus.Published;
                plan.PublishedAt = DateTime.UtcNow;
                await db.SaveChangesAsync(cancellationToken);
                await transaction.CommitAsync(cancellationToken);
                return NoContent();
            },
            cancellationToken);
    }

    [HttpPost("{planId:guid}/modules")]
    public async Task<ActionResult> CreateModule(
        Guid planId,
        CurriculumModuleRequest request,
        CancellationToken cancellationToken)
    {
        var plan = await ModifiablePlanAsync(planId, cancellationToken);
        if (plan is null) return NotFound();
        var module = new CurriculumModule
        {
            CurriculumPlanId = plan.Id,
            Code = request.Code.Trim(),
            Name = request.Name.Trim(),
            RequiredCredits = request.RequiredCredits,
            SortOrder = request.SortOrder
        };
        db.CurriculumModules.Add(module);
        return await SaveCreatedAsync(module.Id, cancellationToken);
    }

    [HttpPut("{planId:guid}/modules/{moduleId:guid}")]
    public async Task<ActionResult> UpdateModule(
        Guid planId,
        Guid moduleId,
        CurriculumModuleRequest request,
        CancellationToken cancellationToken)
    {
        if (await ModifiablePlanAsync(planId, cancellationToken) is null) return NotFound();
        var module = await db.CurriculumModules
            .FirstOrDefaultAsync(x => x.Id == moduleId && x.CurriculumPlanId == planId, cancellationToken);
        if (module is null) return NotFound();
        module.Code = request.Code.Trim();
        module.Name = request.Name.Trim();
        module.RequiredCredits = request.RequiredCredits;
        module.SortOrder = request.SortOrder;
        return await SaveNoContentAsync(cancellationToken);
    }

    [HttpDelete("{planId:guid}/modules/{moduleId:guid}")]
    public async Task<ActionResult> DeleteModule(
        Guid planId,
        Guid moduleId,
        CancellationToken cancellationToken)
    {
        if (await ModifiablePlanAsync(planId, cancellationToken) is null) return NotFound();
        var module = await db.CurriculumModules
            .FirstOrDefaultAsync(x => x.Id == moduleId && x.CurriculumPlanId == planId, cancellationToken);
        if (module is null) return NotFound();
        db.CurriculumModules.Remove(module);
        return await SaveNoContentAsync(cancellationToken);
    }

    [HttpPost("{planId:guid}/modules/{moduleId:guid}/courses")]
    public async Task<ActionResult> AddCourse(
        Guid planId,
        Guid moduleId,
        CurriculumCourseRequest request,
        CancellationToken cancellationToken)
    {
        var plan = await ModifiablePlanAsync(planId, cancellationToken);
        if (plan is null) return NotFound();
        if (!await db.CurriculumModules.AnyAsync(
                x => x.Id == moduleId && x.CurriculumPlanId == planId,
                cancellationToken))
            return NotFound();
        if (!await db.Courses.AnyAsync(
                x => x.Id == request.CourseId && x.IsEnabled,
                cancellationToken))
            return ValidationProblem("所选课程不存在或已停用。");
        if (request.RecommendedSemester > plan.Major!.SchoolingYears * 2)
            return ValidationProblem("建议学期超出了该专业学制。");
        if (await db.CurriculumCourses.AnyAsync(
                x => x.CurriculumModule!.CurriculumPlanId == planId &&
                     x.CourseId == request.CourseId,
                cancellationToken))
            return ConflictProblem("该课程已存在于本方案中。");

        var item = new CurriculumCourse
        {
            CurriculumModuleId = moduleId,
            CourseId = request.CourseId,
            RecommendedSemester = request.RecommendedSemester,
            Type = request.Type,
            Notes = Normalize(request.Notes)
        };
        db.CurriculumCourses.Add(item);
        return await SaveCreatedAsync(item.Id, cancellationToken);
    }

    [HttpPost("{planId:guid}/modules/{moduleId:guid}/course-groups/{groupId:guid}")]
    public async Task<ActionResult> AddCourseGroup(
        Guid planId,
        Guid moduleId,
        Guid groupId,
        CurriculumCourseGroupImportRequest request,
        CancellationToken cancellationToken)
    {
        var plan = await ModifiablePlanAsync(planId, cancellationToken);
        if (plan is null) return NotFound();
        if (request.RecommendedSemester > plan.Major!.SchoolingYears * 2)
            return ValidationProblem("建议学期超出了该专业学制。");
        if (!await db.CurriculumModules.AnyAsync(
                x => x.Id == moduleId && x.CurriculumPlanId == planId,
                cancellationToken))
            return NotFound();

        var courseIds = await db.CourseGroupCourses.AsNoTracking()
            .Where(x => x.CourseGroupId == groupId && x.Course!.IsEnabled)
            .Select(x => x.CourseId)
            .ToListAsync(cancellationToken);
        if (courseIds.Count == 0)
            return ValidationProblem("课程组不存在，或其中没有可用课程。");
        var existingCourseIds = await db.CurriculumCourses.AsNoTracking()
            .Where(x => x.CurriculumModule!.CurriculumPlanId == planId &&
                        courseIds.Contains(x.CourseId))
            .Select(x => x.CourseId)
            .ToListAsync(cancellationToken);
        if (existingCourseIds.Count > 0)
            return ConflictProblem("课程组中有课程已存在于该培养方案，请先移除重复课程后再导入。");

        db.CurriculumCourses.AddRange(courseIds.Select(courseId => new CurriculumCourse
        {
            CurriculumModuleId = moduleId,
            CourseId = courseId,
            RecommendedSemester = request.RecommendedSemester,
            Type = request.Type,
            Notes = Normalize(request.Notes)
        }));
        return await SaveNoContentAsync(cancellationToken);
    }

    [HttpPut("{planId:guid}/modules/{moduleId:guid}/courses/{itemId:guid}")]
    public async Task<ActionResult> UpdateCourse(
        Guid planId,
        Guid moduleId,
        Guid itemId,
        CurriculumCourseRequest request,
        CancellationToken cancellationToken)
    {
        var plan = await ModifiablePlanAsync(planId, cancellationToken);
        if (plan is null) return NotFound();
        var item = await db.CurriculumCourses
            .FirstOrDefaultAsync(x =>
                x.Id == itemId &&
                x.CurriculumModuleId == moduleId &&
                x.CurriculumModule!.CurriculumPlanId == planId,
                cancellationToken);
        if (item is null) return NotFound();
        if (!await db.Courses.AnyAsync(
                x => x.Id == request.CourseId && x.IsEnabled,
                cancellationToken))
            return ValidationProblem("所选课程不存在或已停用。");
        if (request.RecommendedSemester > plan.Major!.SchoolingYears * 2)
            return ValidationProblem("建议学期超出了该专业学制。");
        if (await db.CurriculumCourses.AnyAsync(
                x => x.Id != itemId &&
                     x.CurriculumModule!.CurriculumPlanId == planId &&
                     x.CourseId == request.CourseId,
                cancellationToken))
            return ConflictProblem("该课程已存在于本方案中。");

        item.CourseId = request.CourseId;
        item.RecommendedSemester = request.RecommendedSemester;
        item.Type = request.Type;
        item.Notes = Normalize(request.Notes);
        return await SaveNoContentAsync(cancellationToken);
    }

    [HttpDelete("{planId:guid}/modules/{moduleId:guid}/courses/{itemId:guid}")]
    public async Task<ActionResult> DeleteCourse(
        Guid planId,
        Guid moduleId,
        Guid itemId,
        CancellationToken cancellationToken)
    {
        if (await ModifiablePlanAsync(planId, cancellationToken) is null) return NotFound();
        var item = await db.CurriculumCourses
            .FirstOrDefaultAsync(x =>
                x.Id == itemId &&
                x.CurriculumModuleId == moduleId &&
                x.CurriculumModule!.CurriculumPlanId == planId,
                cancellationToken);
        if (item is null) return NotFound();
        db.CurriculumCourses.Remove(item);
        return await SaveNoContentAsync(cancellationToken);
    }

    private IQueryable<CurriculumPlan> ScopedPlans()
    {
        var source = db.CurriculumPlans.AsQueryable();
        var collegeId = ScopedCollegeId();
        return collegeId.HasValue
            ? source.Where(x => x.Major!.CollegeId == collegeId.Value)
            : source;
    }

    private async Task<Major?> FindAccessibleMajorAsync(Guid majorId, CancellationToken cancellationToken)
    {
        var source = db.Majors.AsQueryable();
        var collegeId = ScopedCollegeId();
        if (collegeId.HasValue) source = source.Where(x => x.CollegeId == collegeId.Value);
        return await source.FirstOrDefaultAsync(x => x.Id == majorId, cancellationToken);
    }

    private async Task<CurriculumPlan?> EditablePlanAsync(
        Guid id,
        CancellationToken cancellationToken) =>
        await ScopedPlans().FirstOrDefaultAsync(x => x.Id == id, cancellationToken);

    private async Task<CurriculumPlan?> ModifiablePlanAsync(
        Guid id,
        CancellationToken cancellationToken) =>
        await ScopedPlans()
            .Include(x => x.Major)
            .FirstOrDefaultAsync(
                x => x.Id == id &&
                     (x.Status == CurriculumPlanStatus.Draft ||
                      x.Status == CurriculumPlanStatus.Published),
                cancellationToken);

    private Guid? ScopedCollegeId()
        => currentUserDataScope.Current.RestrictedCollegeId;

    private async Task<ActionResult> SaveCreatedAsync(Guid id, CancellationToken cancellationToken)
    {
        try
        {
            await db.SaveChangesAsync(cancellationToken);
            return Created(string.Empty, new { id });
        }
        catch (DbUpdateException)
        {
            return ConflictProblem("版本、模块编码或课程配置重复。");
        }
    }

    private async Task<ActionResult> SaveNoContentAsync(CancellationToken cancellationToken)
    {
        try
        {
            await db.SaveChangesAsync(cancellationToken);
            return NoContent();
        }
        catch (DbUpdateException)
        {
            return ConflictProblem("数据重复，或记录已被其他业务引用。");
        }
    }

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

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

public sealed record CurriculumPlanRequest(
    Guid MajorId,
    [Required, MaxLength(120)] string Name,
    [Required, MaxLength(30)] string Version,
    [Range(2000, 2200)] int EffectiveGrade,
    [Range(typeof(decimal), "0.1", "999")] decimal TotalCredits,
    [MaxLength(1000)] string? Description);

public sealed record CloneCurriculumPlanRequest(
    [Required, MaxLength(120)] string Name,
    [Required, MaxLength(30)] string Version,
    [Range(2000, 2200)] int EffectiveGrade);

public sealed record CurriculumModuleRequest(
    [Required, MaxLength(30)] string Code,
    [Required, MaxLength(100)] string Name,
    [Range(typeof(decimal), "0", "999")] decimal RequiredCredits,
    [Range(0, 9999)] int SortOrder);

public sealed record CurriculumCourseRequest(
    Guid CourseId,
    [Range(1, 20)] int RecommendedSemester,
    CurriculumCourseType Type,
    [MaxLength(500)] string? Notes);

public sealed record CurriculumCourseGroupImportRequest(
    [Range(1, 20)] int RecommendedSemester,
    CurriculumCourseType Type,
    [MaxLength(500)] string? Notes);
