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(Roles = ReadRoles)]
[Route("api/personnel")]
public sealed class PersonnelExcelController(
    AppDbContext db,
    ICurrentUserDataScope currentUserDataScope,
    IAppCache cache) : ControllerBase
{
    private const string ReadRoles =
        SystemRoles.SuperAdmin + "," +
        SystemRoles.AcademicAdmin + "," +
        SystemRoles.CollegeAdmin + "," +
        SystemRoles.Counselor + "," +
        SystemRoles.Teacher + "," +
        SystemRoles.Student;

    private const string WriteRoles =
        SystemRoles.SuperAdmin + "," +
        SystemRoles.AcademicAdmin + "," +
        SystemRoles.CollegeAdmin;

    private const string ExportRoles =
        WriteRoles + "," + SystemRoles.Counselor;

    private static readonly string[] TeacherHeaders =
    [
        "工号", "姓名", "性别", "学院编码", "职称", "任职状态",
        "入职日期", "教师类别", "联系电话", "电子邮箱", "备注"
    ];

    private static readonly string[] StudentHeaders =
    [
        "学号", "姓名", "性别", "行政班编码", "入学年级", "入学日期",
        "学籍状态", "出生日期", "英文姓名", "证件号码", "国籍", "民族",
        "政治面貌", "籍贯", "户籍地址", "现居住地址", "邮政编码",
        "联系电话", "电子邮箱", "QQ", "微信", "紧急联系人", "与本人关系",
        "紧急联系电话", "特殊标记", "特殊情况说明", "个人简介", "备注"
    ];

    [HttpGet("{kind}/template")]
    [Authorize(Roles = WriteRoles)]
    public IActionResult DownloadTemplate(string kind)
    {
        if (!TryGetHeaders(kind, out var headers)) return NotFound();
        var bytes = ExcelWorkbookHelper.Create(
            "档案导入",
            headers,
            [],
            [
                "请勿修改第一行列名；每行填写一位人员。",
                kind.Equals("teachers", StringComparison.OrdinalIgnoreCase)
                    ? "工号是唯一标识；学院编码必须已存在。"
                    : "学号是唯一标识；行政班编码必须已存在。",
                "编号已存在时更新档案，不存在时新增档案。",
                "日期填写为 yyyy-MM-dd；不适用的可选字段可以留空。",
                "整批数据会先校验，任一行有误时均不会写入。"
            ]);
        return File(bytes, ExcelWorkbookHelper.ContentType,
            $"{RegistryName(kind)}导入模板.xlsx");
    }

    [HttpGet("{kind}/export")]
    [Authorize(Roles = ExportRoles)]
    public async Task<IActionResult> Export(
        string kind,
        [FromQuery] PersonnelQuery query,
        CancellationToken cancellationToken)
    {
        if (!TryGetHeaders(kind, out var headers)) return NotFound();
        IReadOnlyList<IReadOnlyList<object?>> rows;
        if (kind.Equals("teachers", StringComparison.OrdinalIgnoreCase))
        {
            var source = ApplyTeacherFilters(
                ApplyTeacherScope(db.Teachers.AsNoTracking()),
                query);
            rows = (await source.Include(x => x.College).OrderBy(x => x.TeacherNumber)
                    .ToListAsync(cancellationToken))
                .Select(x => Row(
                    x.TeacherNumber, x.Name, GenderName(x.Gender), x.College!.Code,
                    x.Title, TeacherStatusName(x.Status), x.HireDate,
                    x.IsExternal ? "外聘" : "校内", x.Phone, x.Email, x.Notes))
                .ToList();
        }
        else
        {
            var source = ApplyStudentFilters(
                ApplyStudentScope(db.Students.AsNoTracking()),
                query);
            rows = (await source.Include(x => x.AdministrativeClass)
                    .OrderBy(x => x.StudentNumber).ToListAsync(cancellationToken))
                .Select(x => Row(
                    x.StudentNumber, x.Name, GenderName(x.Gender),
                    x.AdministrativeClass!.Code, x.EnrollmentYear, x.EnrollmentDate,
                    StudentStatusName(x.Status), x.DateOfBirth, x.EnglishName,
                    x.IdCardNumber, x.Nationality, x.Ethnicity, x.PoliticalStatus,
                    x.NativePlace, x.HouseholdAddress, x.CurrentAddress, x.PostalCode,
                    x.Phone, x.Email, x.Qq, x.WeChat, x.EmergencyContactName,
                    x.EmergencyContactRelationship, x.EmergencyContactPhone,
                    x.SpecialTags, x.SpecialNeeds, x.Biography, x.Notes))
                .ToList();
        }

        var bytes = ExcelWorkbookHelper.Create(RegistryName(kind), headers, rows);
        return File(bytes, ExcelWorkbookHelper.ContentType,
            $"{RegistryName(kind)}-{DateTime.Now:yyyyMMdd-HHmm}.xlsx");
    }

    [HttpPost("{kind}/import")]
    [Authorize(Roles = WriteRoles)]
    [RequestSizeLimit(10 * 1024 * 1024)]
    public async Task<ActionResult<ExcelImportResult>> Import(
        string kind,
        IFormFile file,
        CancellationToken cancellationToken)
    {
        if (!TryGetHeaders(kind, out var headers)) return NotFound();
        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 = kind.Equals(
                        "teachers",
                        StringComparison.OrdinalIgnoreCase)
                        ? await ImportTeachersAsync(rows, errors, cancellationToken)
                        : await ImportStudentsAsync(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);
                    await cache.RemoveByTagAsync(
                        AppCacheTags.Analytics,
                        cancellationToken);
                    return Ok(result);
                }
                catch (DbUpdateException)
                {
                    await transaction.RollbackAsync(cancellationToken);
                    return Conflict(new ProblemDetails
                    {
                        Title = "导入失败",
                        Detail = "存在重复编号或无效关联，未写入任何档案。",
                        Status = StatusCodes.Status409Conflict
                    });
                }
            },
            cancellationToken);
    }

    private async Task<ExcelImportResult> ImportTeachersAsync(
        IReadOnlyList<ExcelRow> rows,
        List<string> errors,
        CancellationToken cancellationToken)
    {
        var existing = await db.Teachers.ToDictionaryAsync(x => x.TeacherNumber,
            StringComparer.OrdinalIgnoreCase, cancellationToken);
        var colleges = await db.Colleges.AsNoTracking().ToDictionaryAsync(x => x.Code,
            StringComparer.OrdinalIgnoreCase, cancellationToken);
        var created = 0;
        var updated = 0;
        foreach (var row in rows)
        {
            var number = Required(row, "工号", errors);
            var name = Required(row, "姓名", errors);
            var collegeCode = Required(row, "学院编码", errors);
            var gender = ParseGender(row, errors);
            var status = ParseTeacherStatus(row, errors);
            var hireDate = ParseDate(row, "入职日期", false, errors);
            var isExternal = ParseTeacherType(row, errors);
            if (number is null || name is null || collegeCode is null ||
                gender is null || status is null || isExternal is null) continue;
            if (!colleges.TryGetValue(collegeCode, out var college))
            {
                errors.Add($"第 {row.RowNumber} 行：学院编码“{collegeCode}”不存在。");
                continue;
            }
            if (!CanAccessCollege(college.Id))
            {
                errors.Add($"第 {row.RowNumber} 行：无权维护学院“{college.Name}”的教师。");
                continue;
            }
            var entity = existing.GetValueOrDefault(number);
            if (entity is not null && !CanAccessCollege(entity.CollegeId))
            {
                errors.Add($"第 {row.RowNumber} 行：工号“{number}”已属于无权维护的学院。");
                continue;
            }
            if (entity is null)
            {
                entity = new Teacher
                {
                    TeacherNumber = number,
                    Name = name,
                    CollegeId = college.Id
                };
                db.Teachers.Add(entity);
                existing[number] = entity;
                created++;
            }
            else updated++;
            entity.Name = name;
            entity.Gender = gender.Value;
            entity.CollegeId = college.Id;
            entity.Title = Optional(row, "职称");
            entity.Status = status.Value;
            entity.HireDate = hireDate;
            entity.IsExternal = isExternal.Value;
            entity.Phone = Optional(row, "联系电话");
            entity.Email = Optional(row, "电子邮箱");
            entity.Notes = Optional(row, "备注");
        }
        return new(created, updated, rows.Count);
    }

    private async Task<ExcelImportResult> ImportStudentsAsync(
        IReadOnlyList<ExcelRow> rows,
        List<string> errors,
        CancellationToken cancellationToken)
    {
        var existing = await db.Students.Include(x => x.AdministrativeClass)
            .ThenInclude(x => x!.Major)
            .ToDictionaryAsync(x => x.StudentNumber, StringComparer.OrdinalIgnoreCase,
                cancellationToken);
        var classes = await db.AdministrativeClasses.AsNoTracking().Include(x => x.Major)
            .ToDictionaryAsync(x => x.Code, StringComparer.OrdinalIgnoreCase,
                cancellationToken);
        var created = 0;
        var updated = 0;
        foreach (var row in rows)
        {
            var number = Required(row, "学号", errors);
            var name = Required(row, "姓名", errors);
            var classCode = Required(row, "行政班编码", errors);
            var gender = ParseGender(row, errors);
            var enrollmentYear = ParseInt(row, "入学年级", 2000, 2200, errors);
            var enrollmentDate = ParseDate(row, "入学日期", true, errors);
            var status = ParseStudentStatus(row, errors);
            var dateOfBirth = ParseDate(row, "出生日期", false, errors);
            if (number is null || name is null || classCode is null || gender is null ||
                enrollmentYear is null || enrollmentDate is null || status is null) continue;
            if (!classes.TryGetValue(classCode, out var targetClass))
            {
                errors.Add($"第 {row.RowNumber} 行：行政班编码“{classCode}”不存在。");
                continue;
            }
            var collegeId = targetClass.Major!.CollegeId;
            if (!CanAccessCollege(collegeId))
            {
                errors.Add($"第 {row.RowNumber} 行：无权维护行政班“{targetClass.Name}”的学生。");
                continue;
            }
            var entity = existing.GetValueOrDefault(number);
            if (entity is not null &&
                !CanAccessCollege(entity.AdministrativeClass!.Major!.CollegeId))
            {
                errors.Add($"第 {row.RowNumber} 行：学号“{number}”已属于无权维护的学院。");
                continue;
            }
            if (entity is null)
            {
                entity = new Student
                {
                    StudentNumber = number,
                    Name = name,
                    AdministrativeClassId = targetClass.Id,
                    EnrollmentDate = enrollmentDate.Value
                };
                db.Students.Add(entity);
                existing[number] = entity;
                created++;
            }
            else updated++;
            entity.Name = name;
            entity.Gender = gender.Value;
            entity.AdministrativeClassId = targetClass.Id;
            entity.EnrollmentYear = enrollmentYear.Value;
            entity.EnrollmentDate = enrollmentDate.Value;
            entity.Status = status.Value;
            entity.DateOfBirth = dateOfBirth;
            entity.EnglishName = Optional(row, "英文姓名");
            entity.IdCardNumber = Optional(row, "证件号码");
            entity.Nationality = Optional(row, "国籍");
            entity.Ethnicity = Optional(row, "民族");
            entity.PoliticalStatus = Optional(row, "政治面貌");
            entity.NativePlace = Optional(row, "籍贯");
            entity.HouseholdAddress = Optional(row, "户籍地址");
            entity.CurrentAddress = Optional(row, "现居住地址");
            entity.PostalCode = Optional(row, "邮政编码");
            entity.Phone = Optional(row, "联系电话");
            entity.Email = Optional(row, "电子邮箱");
            entity.Qq = Optional(row, "QQ");
            entity.WeChat = Optional(row, "微信");
            entity.EmergencyContactName = Optional(row, "紧急联系人");
            entity.EmergencyContactRelationship = Optional(row, "与本人关系");
            entity.EmergencyContactPhone = Optional(row, "紧急联系电话");
            entity.SpecialTags = Optional(row, "特殊标记");
            entity.SpecialNeeds = Optional(row, "特殊情况说明");
            entity.Biography = Optional(row, "个人简介");
            entity.Notes = Optional(row, "备注");
        }
        return new(created, updated, rows.Count);
    }

    private IQueryable<Teacher> ApplyTeacherFilters(
        IQueryable<Teacher> source,
        PersonnelQuery query)
    {
        if (query.CollegeId.HasValue)
            source = source.Where(x => x.CollegeId == query.CollegeId);
        if (query.TeacherStatus.HasValue)
            source = source.Where(x => x.Status == query.TeacherStatus);
        if (!string.IsNullOrWhiteSpace(query.Keyword))
        {
            var keyword = query.Keyword.Trim();
            source = source.Where(x =>
                x.TeacherNumber.Contains(keyword) || x.Name.Contains(keyword) ||
                (x.Title != null && x.Title.Contains(keyword)));
        }
        return source;
    }

    private IQueryable<Student> ApplyStudentFilters(
        IQueryable<Student> source,
        PersonnelQuery query)
    {
        if (query.CollegeId.HasValue)
            source = source.Where(x =>
                x.AdministrativeClass!.Major!.CollegeId == query.CollegeId);
        if (query.MajorId.HasValue)
            source = source.Where(x => x.AdministrativeClass!.MajorId == query.MajorId);
        if (query.ClassId.HasValue)
            source = source.Where(x => x.AdministrativeClassId == query.ClassId);
        if (query.EnrollmentYear.HasValue)
            source = source.Where(x => x.EnrollmentYear == query.EnrollmentYear);
        if (query.StudentStatus.HasValue)
            source = source.Where(x => x.Status == query.StudentStatus);
        if (!string.IsNullOrWhiteSpace(query.Keyword))
        {
            var keyword = query.Keyword.Trim();
            source = source.Where(x =>
                x.StudentNumber.Contains(keyword) || x.Name.Contains(keyword));
        }
        return source;
    }

    private IQueryable<Teacher> ApplyTeacherScope(IQueryable<Teacher> source)
    {
        var scope = currentUserDataScope.Current;
        if (scope.Scope == DataScope.All) return source;
        if (scope.Scope == DataScope.College)
            return source.Where(x => x.CollegeId == scope.RestrictedCollegeId);
        if (scope.Scope == DataScope.Class)
            return source.Where(teacher => db.TeachingTasks.Any(task =>
                task.Teachers.Any(item => item.TeacherId == teacher.Id) &&
                task.Classes.Any(item =>
                    item.AdministrativeClass!.CounselorUserId == scope.UserId)));
        var userId = scope.UserId;
        return scope.IsInRole(SystemRoles.Teacher)
            ? source.Where(x => x.UserId == userId)
            : source.Where(_ => false);
    }

    private IQueryable<Student> ApplyStudentScope(IQueryable<Student> source)
    {
        var scope = currentUserDataScope.Current;
        if (scope.Scope == DataScope.All) return source;
        if (scope.Scope == DataScope.College)
            return source.Where(x =>
                x.AdministrativeClass!.Major!.CollegeId == scope.RestrictedCollegeId);
        if (scope.Scope == DataScope.Class)
            return source.Where(x => x.AdministrativeClass!.CounselorUserId == scope.UserId);
        var userId = scope.UserId;
        var isTeacher = scope.IsInRole(SystemRoles.Teacher);
        var isStudent = scope.IsInRole(SystemRoles.Student);
        return source.Where(student =>
            isStudent && student.UserId == userId ||
            isTeacher && db.TeachingTasks.Any(task =>
                task.Teachers.Any(item => item.Teacher!.UserId == userId) &&
                task.Classes.Any(item =>
                    item.AdministrativeClassId == student.AdministrativeClassId)));
    }

    private bool CanAccessCollege(Guid collegeId) =>
        currentUserDataScope.Current.CanAccessCollege(collegeId);

    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 bool TryGetHeaders(string kind, out string[] headers)
    {
        if (kind.Equals("teachers", StringComparison.OrdinalIgnoreCase))
        {
            headers = TeacherHeaders;
            return true;
        }
        if (kind.Equals("students", StringComparison.OrdinalIgnoreCase))
        {
            headers = StudentHeaders;
            return true;
        }
        headers = [];
        return false;
    }

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

    private static string? Optional(ExcelRow row, string header) =>
        string.IsNullOrWhiteSpace(row[header]) ? null : row[header].Trim();

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

    private static DateOnly? ParseDate(
        ExcelRow row, string header, bool required, List<string> errors)
    {
        var value = row[header];
        if (string.IsNullOrWhiteSpace(value) && !required) return null;
        if (DateOnly.TryParse(value, out var date)) return date;
        errors.Add($"第 {row.RowNumber} 行：“{header}”必须是有效日期（如 2026-09-01）。");
        return null;
    }

    private static Gender? ParseGender(ExcelRow row, List<string> errors)
    {
        var value = row["性别"];
        if (string.IsNullOrWhiteSpace(value) || value is "未设置" or "未知" ||
            value.Equals("Unknown", StringComparison.OrdinalIgnoreCase)) return Gender.Unknown;
        if (value == "男" || value.Equals("Male", StringComparison.OrdinalIgnoreCase))
            return Gender.Male;
        if (value == "女" || value.Equals("Female", StringComparison.OrdinalIgnoreCase))
            return Gender.Female;
        errors.Add($"第 {row.RowNumber} 行：“性别”请填写男、女或未设置。");
        return null;
    }

    private static TeacherStatus? ParseTeacherStatus(ExcelRow row, List<string> errors)
    {
        var value = row["任职状态"];
        if (value == "在职" || value.Equals("Active", StringComparison.OrdinalIgnoreCase))
            return TeacherStatus.Active;
        if (value == "离岗" || value.Equals("OnLeave", StringComparison.OrdinalIgnoreCase))
            return TeacherStatus.OnLeave;
        if (value == "退休" || value.Equals("Retired", StringComparison.OrdinalIgnoreCase))
            return TeacherStatus.Retired;
        if (value == "离职" || value.Equals("Departed", StringComparison.OrdinalIgnoreCase))
            return TeacherStatus.Departed;
        errors.Add($"第 {row.RowNumber} 行：“任职状态”请填写在职、离岗、退休或离职。");
        return null;
    }

    private static StudentStatus? ParseStudentStatus(ExcelRow row, List<string> errors)
    {
        var value = row["学籍状态"];
        if (value == "在籍" || value.Equals("Active", StringComparison.OrdinalIgnoreCase))
            return StudentStatus.Active;
        if (value == "休学" || value.Equals("Suspended", StringComparison.OrdinalIgnoreCase))
            return StudentStatus.Suspended;
        if (value == "毕业" || value.Equals("Graduated", StringComparison.OrdinalIgnoreCase))
            return StudentStatus.Graduated;
        if (value == "退学" || value.Equals("Withdrawn", StringComparison.OrdinalIgnoreCase))
            return StudentStatus.Withdrawn;
        errors.Add($"第 {row.RowNumber} 行：“学籍状态”请填写在籍、休学、毕业或退学。");
        return null;
    }

    private static bool? ParseTeacherType(ExcelRow row, List<string> errors)
    {
        var value = row["教师类别"];
        if (value == "校内" || value == "否" || value == "0") return false;
        if (value == "外聘" || value == "是" || value == "1") return true;
        errors.Add($"第 {row.RowNumber} 行：“教师类别”请填写校内或外聘。");
        return null;
    }

    private static IReadOnlyList<object?> Row(params object?[] values) => values;
    private static string RegistryName(string kind) =>
        kind.Equals("teachers", StringComparison.OrdinalIgnoreCase) ? "教师档案" : "学生档案";
    private static string GenderName(Gender value) => value switch
    {
        Gender.Male => "男",
        Gender.Female => "女",
        _ => "未设置"
    };
    private static string TeacherStatusName(TeacherStatus value) => value switch
    {
        TeacherStatus.Active => "在职",
        TeacherStatus.OnLeave => "离岗",
        TeacherStatus.Retired => "退休",
        _ => "离职"
    };
    private static string StudentStatusName(StudentStatus value) => value switch
    {
        StudentStatus.Active => "在籍",
        StudentStatus.Suspended => "休学",
        StudentStatus.Graduated => "毕业",
        _ => "退学"
    };
}
