using System.Globalization;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Caching;
using Jiaowu.Api.Infrastructure.Excel;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Jiaowu.Api.Controllers;

[ApiController]
[Authorize]
[Route("api/courses")]
public sealed class CoursesExcelController(
    AppDbContext db,
    ICurrentUserDataScope currentUserDataScope,
    IAppCache cache) : ControllerBase
{
    private const string WriteRoles =
        SystemRoles.SuperAdmin + "," +
        SystemRoles.AcademicAdmin + "," +
        SystemRoles.CollegeAdmin;

    private static readonly string[] Headers =
    [
        "课程编码", "课程名称", "英文名称", "开课学院编码", "课程分类编码",
        "课程性质", "学分", "总学时", "讲授学时", "实践学时",
        "考核方式", "课程简介", "启用状态", "排序"
    ];

    [HttpGet("template")]
    [Authorize(Roles = WriteRoles)]
    public IActionResult DownloadTemplate()
    {
        var bytes = ExcelWorkbookHelper.Create(
            "课程库导入",
            Headers,
            [],
            [
                "请勿修改第一行列名；每行填写一门课程，课程编码是全校唯一标识。",
                "开课学院编码、课程分类编码必须已在系统中存在；课程分类还必须处于启用状态。",
                "课程性质请填写：通识必修、专业必修、专业选修、通识选修或实践课程。",
                "考核方式请填写考试或考查；启用状态请填写是或否。",
                "课程编码已存在时更新课程，不存在时新增课程。",
                "学院管理员只能维护本学院的专业必修、专业选修和实践课程。",
                "讲授学时与实践学时之和不能超过总学时；整批任一行有误时均不会写入。"
            ]);
        return File(
            bytes,
            ExcelWorkbookHelper.ContentType,
            "课程库导入模板.xlsx");
    }

    [HttpGet("export")]
    public async Task<IActionResult> Export(
        string? keyword = null,
        Guid? collegeId = null,
        Guid? categoryId = null,
        CourseNature? nature = null,
        bool? isEnabled = null,
        CancellationToken cancellationToken = default)
    {
        var source = ApplyFilters(
            ScopedCourses().AsNoTracking(),
            keyword,
            collegeId,
            categoryId,
            nature,
            isEnabled);
        var courses = await source
            .Include(x => x.College)
            .Include(x => x.CourseCategory)
            .OrderBy(x => x.SortOrder)
            .ThenBy(x => x.Code)
            .ToListAsync(cancellationToken);
        var rows = courses
            .Select(x => Row(
                x.Code,
                x.Name,
                x.EnglishName,
                x.College!.Code,
                x.CourseCategory?.Code,
                NatureName(x.Nature),
                x.Credits,
                x.TotalHours,
                x.LectureHours,
                x.PracticeHours,
                AssessmentName(x.AssessmentMethod),
                x.Description,
                x.IsEnabled ? "是" : "否",
                x.SortOrder))
            .ToList();

        var bytes = ExcelWorkbookHelper.Create("课程库", Headers, rows);
        return File(
            bytes,
            ExcelWorkbookHelper.ContentType,
            $"课程库-{DateTime.Now:yyyyMMdd-HHmm}.xlsx");
    }

    [HttpPost("import")]
    [Authorize(Roles = WriteRoles)]
    [RequestSizeLimit(10 * 1024 * 1024)]
    public async Task<ActionResult<ExcelImportResult>> Import(
        IFormFile file,
        CancellationToken cancellationToken)
    {
        IReadOnlyList<ExcelRow> rows;
        try
        {
            rows = await ExcelWorkbookHelper.ReadAsync(
                file,
                Headers,
                cancellationToken);
        }
        catch (InvalidDataException exception)
        {
            return ValidationProblem(exception.Message);
        }

        if (rows.Count == 0)
            return ValidationProblem("Excel 中没有可导入的课程数据。");

        return await db.ExecuteInRetriableTransactionAsync<
            ActionResult<ExcelImportResult>>(
            async transaction =>
            {
                var errors = new List<string>();
                try
                {
                    var result = await ImportRowsAsync(
                        rows,
                        errors,
                        cancellationToken);
                    if (errors.Count > 0)
                    {
                        await transaction.RollbackAsync(cancellationToken);
                        return ImportValidationProblem(errors);
                    }

                    await db.SaveChangesAsync(cancellationToken);
                    await transaction.CommitAsync(cancellationToken);
                    await cache.RemoveByTagAsync(
                        AppCacheTags.Timetables,
                        cancellationToken);
                    return Ok(result);
                }
                catch (DbUpdateException)
                {
                    await transaction.RollbackAsync(cancellationToken);
                    return Conflict(new ProblemDetails
                    {
                        Title = "导入失败",
                        Detail = "存在重复课程编码或无效关联，未写入任何课程。",
                        Status = StatusCodes.Status409Conflict
                    });
                }
            },
            cancellationToken);
    }

    private async Task<ExcelImportResult> ImportRowsAsync(
        IReadOnlyList<ExcelRow> rows,
        List<string> errors,
        CancellationToken cancellationToken)
    {
        var existing = await db.Courses.ToDictionaryAsync(
            x => x.Code,
            StringComparer.OrdinalIgnoreCase,
            cancellationToken);
        var colleges = await db.Colleges.AsNoTracking().ToDictionaryAsync(
            x => x.Code,
            StringComparer.OrdinalIgnoreCase,
            cancellationToken);
        var categories = await db.CourseCategories.AsNoTracking().ToDictionaryAsync(
            x => x.Code,
            StringComparer.OrdinalIgnoreCase,
            cancellationToken);
        var importedCodes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        var created = 0;
        var updated = 0;

        foreach (var row in rows)
        {
            var code = Required(row, "课程编码", 40, errors);
            var name = Required(row, "课程名称", 100, errors);
            var englishName = Optional(row, "英文名称", 150, errors);
            var collegeCode = Required(row, "开课学院编码", 40, errors);
            var categoryCode = Required(row, "课程分类编码", 40, errors);
            var nature = ParseNature(row, errors);
            var credits = ParseDecimal(row, "学分", 0.1m, 99m, errors);
            var totalHours = ParseInt(row, "总学时", 1, 1000, errors);
            var lectureHours = ParseInt(row, "讲授学时", 0, 1000, errors);
            var practiceHours = ParseInt(row, "实践学时", 0, 1000, errors);
            var assessment = ParseAssessment(row, errors);
            var description = Optional(row, "课程简介", 1000, errors);
            var enabled = ParseEnabled(row, errors);
            var sortOrder = ParseInt(row, "排序", 0, int.MaxValue, errors);

            if (code is null || name is null || collegeCode is null ||
                categoryCode is null || nature is null || credits is null ||
                totalHours is null || lectureHours is null ||
                practiceHours is null || assessment is null ||
                enabled is null || sortOrder is null)
                continue;

            if (!importedCodes.Add(code))
            {
                errors.Add($"第 {row.RowNumber} 行：课程编码“{code}”在文件中重复。");
                continue;
            }
            if (!colleges.TryGetValue(collegeCode, out var college))
            {
                errors.Add($"第 {row.RowNumber} 行：开课学院编码“{collegeCode}”不存在。");
                continue;
            }
            if (!categories.TryGetValue(categoryCode, out var category))
            {
                errors.Add($"第 {row.RowNumber} 行：课程分类编码“{categoryCode}”不存在。");
                continue;
            }
            if (!category.IsEnabled)
            {
                errors.Add($"第 {row.RowNumber} 行：课程分类“{category.Name}”已停用。");
                continue;
            }
            if (lectureHours.Value + practiceHours.Value > totalHours.Value)
            {
                errors.Add(
                    $"第 {row.RowNumber} 行：讲授学时与实践学时之和不能超过总学时。");
                continue;
            }
            if (!CanManage(college.Id, nature.Value))
            {
                errors.Add(
                    $"第 {row.RowNumber} 行：无权维护学院“{college.Name}”的“{NatureName(nature.Value)}”课程。");
                continue;
            }

            var entity = existing.GetValueOrDefault(code);
            if (entity is not null && !CanManage(entity.CollegeId, entity.Nature))
            {
                errors.Add(
                    $"第 {row.RowNumber} 行：课程编码“{code}”已属于无权维护的课程。");
                continue;
            }

            if (entity is null)
            {
                entity = new Course
                {
                    Code = code,
                    Name = name,
                    CollegeId = college.Id
                };
                db.Courses.Add(entity);
                existing[code] = entity;
                created++;
            }
            else
            {
                updated++;
            }

            entity.Name = name;
            entity.EnglishName = englishName;
            entity.CollegeId = college.Id;
            entity.CourseCategoryId = category.Id;
            entity.Credits = credits.Value;
            entity.TotalHours = totalHours.Value;
            entity.LectureHours = lectureHours.Value;
            entity.PracticeHours = practiceHours.Value;
            entity.Nature = nature.Value;
            entity.AssessmentMethod = assessment.Value;
            entity.Description = description;
            entity.IsEnabled = enabled.Value;
            entity.SortOrder = sortOrder.Value;
        }

        return new(created, updated, rows.Count);
    }

    private IQueryable<Course> ScopedCourses()
    {
        var scope = currentUserDataScope.Current;
        var source = db.Courses.AsQueryable();
        if (scope.Scope == DataScope.All) return source;
        if (scope.Scope == DataScope.College)
        {
            return source.Where(x =>
                x.Nature == CourseNature.GeneralRequired ||
                x.Nature == CourseNature.GeneralElective ||
                x.CollegeId == scope.RestrictedCollegeId);
        }
        if (scope.Scope == DataScope.Class)
        {
            return source.Where(course => db.TeachingTasks.Any(task =>
                task.CourseId == course.Id &&
                task.Classes.Any(item =>
                    item.AdministrativeClass!.CounselorUserId == scope.UserId)));
        }

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

    private static IQueryable<Course> ApplyFilters(
        IQueryable<Course> source,
        string? keyword,
        Guid? collegeId,
        Guid? categoryId,
        CourseNature? nature,
        bool? isEnabled)
    {
        if (collegeId.HasValue)
            source = source.Where(x => x.CollegeId == collegeId.Value);
        if (categoryId.HasValue)
            source = source.Where(x => x.CourseCategoryId == categoryId.Value);
        if (nature.HasValue)
            source = source.Where(x => x.Nature == nature.Value);
        if (isEnabled.HasValue)
            source = source.Where(x => x.IsEnabled == isEnabled.Value);
        if (!string.IsNullOrWhiteSpace(keyword))
        {
            keyword = keyword.Trim();
            source = source.Where(x =>
                x.Code.Contains(keyword) ||
                x.Name.Contains(keyword) ||
                (x.EnglishName != null && x.EnglishName.Contains(keyword)));
        }
        return source;
    }

    private bool CanManage(Guid collegeId, CourseNature nature) =>
        CourseMaintenancePolicy.CanManage(
            currentUserDataScope.Current,
            collegeId,
            nature);

    private ActionResult ImportValidationProblem(IReadOnlyList<string> errors)
    {
        foreach (var error in errors.Take(50))
            ModelState.AddModelError("file", error);
        if (errors.Count > 50)
            ModelState.AddModelError("file", $"另有 {errors.Count - 50} 条错误未显示。");
        return ValidationProblem(ModelState);
    }

    private static string? Required(
        ExcelRow row,
        string header,
        int maximumLength,
        List<string> errors)
    {
        var value = Optional(row, header, maximumLength, errors);
        if (value is not null) return value;
        if (string.IsNullOrWhiteSpace(row[header]))
            errors.Add($"第 {row.RowNumber} 行：“{header}”不能为空。");
        return null;
    }

    private static string? Optional(
        ExcelRow row,
        string header,
        int maximumLength,
        List<string> errors)
    {
        var value = string.IsNullOrWhiteSpace(row[header])
            ? null
            : row[header].Trim();
        if (value is null || value.Length <= maximumLength) return value;
        errors.Add(
            $"第 {row.RowNumber} 行：“{header}”不能超过 {maximumLength} 个字符。");
        return null;
    }

    private static int? ParseInt(
        ExcelRow row,
        string header,
        int minimum,
        int maximum,
        List<string> errors)
    {
        if (int.TryParse(
                row[header],
                NumberStyles.Integer,
                CultureInfo.InvariantCulture,
                out var value) &&
            value >= minimum &&
            value <= maximum)
            return value;
        errors.Add(
            $"第 {row.RowNumber} 行：“{header}”必须是 {minimum} 至 {maximum} 的整数。");
        return null;
    }

    private static decimal? ParseDecimal(
        ExcelRow row,
        string header,
        decimal minimum,
        decimal maximum,
        List<string> errors)
    {
        if (decimal.TryParse(
                row[header],
                NumberStyles.Number,
                CultureInfo.InvariantCulture,
                out var value) &&
            value >= minimum &&
            value <= maximum)
            return value;
        errors.Add(
            $"第 {row.RowNumber} 行：“{header}”必须是 {minimum:0.##} 至 {maximum:0.##} 的数值。");
        return null;
    }

    private static CourseNature? ParseNature(ExcelRow row, List<string> errors)
    {
        var value = row["课程性质"];
        if (value == "通识必修" ||
            value.Equals(nameof(CourseNature.GeneralRequired), StringComparison.OrdinalIgnoreCase))
            return CourseNature.GeneralRequired;
        if (value == "专业必修" ||
            value.Equals(nameof(CourseNature.MajorRequired), StringComparison.OrdinalIgnoreCase))
            return CourseNature.MajorRequired;
        if (value == "专业选修" ||
            value.Equals(nameof(CourseNature.MajorElective), StringComparison.OrdinalIgnoreCase))
            return CourseNature.MajorElective;
        if (value == "通识选修" ||
            value.Equals(nameof(CourseNature.GeneralElective), StringComparison.OrdinalIgnoreCase))
            return CourseNature.GeneralElective;
        if (value is "实践" or "实践课程" ||
            value.Equals(nameof(CourseNature.Practice), StringComparison.OrdinalIgnoreCase))
            return CourseNature.Practice;
        errors.Add(
            $"第 {row.RowNumber} 行：“课程性质”请填写通识必修、专业必修、专业选修、通识选修或实践课程。");
        return null;
    }

    private static AssessmentMethod? ParseAssessment(
        ExcelRow row,
        List<string> errors)
    {
        var value = row["考核方式"];
        if (value == "考试" ||
            value.Equals(nameof(AssessmentMethod.Examination), StringComparison.OrdinalIgnoreCase))
            return AssessmentMethod.Examination;
        if (value == "考查" ||
            value.Equals(nameof(AssessmentMethod.Assessment), StringComparison.OrdinalIgnoreCase))
            return AssessmentMethod.Assessment;
        errors.Add($"第 {row.RowNumber} 行：“考核方式”请填写考试或考查。");
        return null;
    }

    private static bool? ParseEnabled(ExcelRow row, List<string> errors)
    {
        var value = row["启用状态"];
        if (value is "是" or "启用" or "1" ||
            value.Equals("true", StringComparison.OrdinalIgnoreCase))
            return true;
        if (value is "否" or "停用" or "0" ||
            value.Equals("false", StringComparison.OrdinalIgnoreCase))
            return false;
        errors.Add($"第 {row.RowNumber} 行：“启用状态”请填写是或否。");
        return null;
    }

    private static IReadOnlyList<object?> Row(params object?[] values) => values;

    private static string NatureName(CourseNature value) => value switch
    {
        CourseNature.GeneralRequired => "通识必修",
        CourseNature.MajorRequired => "专业必修",
        CourseNature.MajorElective => "专业选修",
        CourseNature.GeneralElective => "通识选修",
        _ => "实践课程"
    };

    private static string AssessmentName(AssessmentMethod value) =>
        value == AssessmentMethod.Examination ? "考试" : "考查";
}
