# -*- coding: utf-8 -*-
"""把后台「生成数据包 / 销毁」做成真实实现
 后端：POST /api/admin/packages/generate（落库 + 真导出加密包）、POST /api/admin/packages/{id}/destroy（真删文件 + 置销毁）
 前端：doGenPkg / doDestroy 改为调接口，列表加 pid 字段
"""
import shutil, time

BK = time.strftime('%Y%m%d%H%M%S')
API = '/data/www/files/rmsys-app/backend/src/Rmsys.Api/Endpoints/AdminEndpoints.cs'
A = '/data/www/files/rmsys-app/backend/src/Rmsys.Api/wwwroot/admin.html'

def bk(p): shutil.copy2(p, p + '.bakG.' + BK)

# ==================== 后端 ====================
s = open(API, encoding='utf-8').read(); bk(API)

# 1) 数据包列表补数值主键（销毁接口要按 Id 操作）
old = """                    id = p.PkgNo,
                    meeting = p.MeetingId,"""
new = """                    id = p.PkgNo,
                    pid = p.Id,
                    meeting = p.MeetingId,"""
assert s.count(old) == 1
s = s.replace(old, new, 1)

# 2) 生成 / 销毁两个接口，插在导出接口之后
anchor = """        }).RequireAuthorization();

        // ---------- 只校验不下载 ----------"""
assert s.count(anchor) == 1
endpoints = """        }).RequireAuthorization();

        // ---------- 生成数据包（真实落库 + 真导出；后台「生成数据包」按钮调这里）----------
        app.MapPost("/api/admin/packages/generate", async (
            GeneratePkgReq req, ClaimsPrincipal p, RmsysDbContext db, IWebHostEnvironment env) =>
        {
            try
            {
                var sc = await Auth.ScopeGuard.ResolveAsync(p, db);
                if (!sc.IsAll)
                    return Results.Json(new { ok = false, message = "「本处室」账号不能生成数据包（包内含全部上会干部材料）" }, statusCode: 403);
                if (!sc.CanWrite) return Auth.ScopeGuard.ReadOnlyDenied();

                var meeting = await db.Meetings.FirstOrDefaultAsync(x => x.Id == req.meetingId && x.IsDeleted == 0);
                if (meeting == null) return Results.Ok(new { ok = false, message = "会议不存在" });
                var deny = Auth.MeetingGuard.CheckWritable(meeting);
                if (deny != null) return Results.Json(new { ok = false, message = deny }, statusCode: 409);

                if (req.matOrder == null || req.matOrder.Count == 0)
                    return Results.Ok(new { ok = false, message = "请至少选择一种材料类型" });
                var bad = req.matOrder.Where(t => !MaterialType.All.Contains(t)).ToList();
                if (bad.Count > 0)
                    return Results.Ok(new { ok = false, message = "材料类型非法：" + string.Join("、", bad) });
                if (req.encrypt == "sm4_pwd")
                    return Results.Ok(new { ok = false, message = "口令模式尚未在 PAD 端实现校验，请选择「国密SM4（不加口令）」" });

                // 包号：PK + 会议日期 + 两位序号，自动避让重号
                var day = (meeting.HeldAt ?? DateTime.Now).ToString("yyyyMMdd");
                var seq = await db.DataPackages.CountAsync(x => x.MeetingId == meeting.Id && x.PkgNo.StartsWith("PK" + day));
                var pkgNo = $"PK{day}-{seq + 1:00}";
                while (await db.DataPackages.AnyAsync(x => x.PkgNo == pkgNo))
                    pkgNo = $"PK{day}-{++seq + 1:00}";

                DateTime? exp = null;
                if (!string.IsNullOrWhiteSpace(req.expireAt))
                {
                    string[] fmts = { "yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm:ss", "yyyy/MM/dd HH:mm" };
                    if (DateTime.TryParseExact(req.expireAt!.Trim(), fmts, null,
                            System.Globalization.DateTimeStyles.None, out var dt)) exp = dt;
                    else if (DateTime.TryParse(req.expireAt, out var dt2)) exp = dt2;
                }
                if (exp.HasValue && exp.Value <= DateTime.Now)
                    return Results.Ok(new { ok = false, message = "有效期必须晚于当前时间，否则 PAD 会拒绝加载" });

                var pkg = new DataPackage
                {
                    MeetingId = meeting.Id,
                    PkgNo = pkgNo,
                    Encrypt = string.IsNullOrWhiteSpace(req.encrypt) ? "sm4" : req.encrypt!,
                    ExpireAt = exp,
                    ClearPolicy = string.IsNullOrWhiteSpace(req.clearPolicy) ? "expire" : req.clearPolicy!,
                    // 水印默认「密级+星标」，PAD 端会自动补设备号与当天日期
                    Watermark = string.IsNullOrWhiteSpace(req.watermark)
                        ? (meeting.Secret + (meeting.SecretStars ?? ""))
                        : req.watermark,
                    FontLevel = string.IsNullOrWhiteSpace(req.fontLevel) ? "big" : req.fontLevel!,
                    AllowFontChange = req.allowFontChange ?? 1,
                    Status = PackageStatus.Created,
                    CreatedBy = p.FindFirst(ClaimTypes.Name)?.Value ?? "—",
                    CreatedAt = DateTime.Now
                };
                db.DataPackages.Add(pkg);
                await db.SaveChangesAsync();

                // 下发清单（全 5 类都建行，勾选的置 1，保证后续可改）
                var sort = 0;
                foreach (var t in MaterialType.All)
                    db.PackageItems.Add(new PackageItem
                    {
                        PackageId = pkg.Id, MaterialType = t,
                        Enabled = req.matOrder.Contains(t) ? 1 : 0, Sort = ++sort
                    });

                // 绑定设备
                if (req.padNos != null && req.padNos.Count > 0)
                {
                    var pads = await db.PadDevices.Where(x => req.padNos.Contains(x.PadNo)).ToListAsync();
                    foreach (var pd in pads)
                        db.PackageDevices.Add(new PackageDevice { PackageId = pkg.Id, PadId = pd.Id });
                }
                await db.SaveChangesAsync();

                // 真导出（加密包落盘在 App_Data/packages/）
                var dir = Path.Combine(env.ContentRootPath, "App_Data", "packages");
                Directory.CreateDirectory(dir);
                var fileName = pkgNo + ".pkg";
                var path = Path.Combine(dir, fileName);
                var r = await new PackageExporter(db)
                {
                    PhotoRoot = Path.Combine(env.ContentRootPath, "App_Data")
                }.ExportEncryptedAsync(meeting.Id, path, "admin");

                pkg.FilePath = "App_Data/packages/" + fileName;
                pkg.FileSize = r.Bytes;
                pkg.Status = PackageStatus.Delivered;
                await db.SaveChangesAsync();

                await AuditRead(db, p, "生成数据包", pkgNo,
                    $"{fileName}（{r.Bytes / 1024} KB，{r.Cadres} 人，有效期 {exp:yyyy-MM-dd HH:mm}）");

                return Results.Ok(new
                {
                    ok = true,
                    pkgNo,
                    size = r.Bytes,
                    sizeText = (r.Bytes / 1024.0).ToString("F1") + " KB",
                    cadres = r.Cadres,
                    exp = exp?.ToString("yyyy-MM-dd HH:mm") ?? "",
                    pads = req.padNos?.Count ?? 0,
                    message = $"已生成 {pkgNo}（{r.Bytes / 1024} KB，{r.Cadres} 人），可在列表右侧下载"
                });
            }
            catch (Exception e)
            {
                return Results.Problem("生成数据包失败：" + e.Message);
            }
        }).RequireAuthorization();

        // ---------- 销毁数据包（真实：删除导出文件 + 置为已销毁）----------
        app.MapPost("/api/admin/packages/{pkgId:int}/destroy", async (
            int pkgId, ClaimsPrincipal p, RmsysDbContext db, IWebHostEnvironment env) =>
        {
            var sc = await Auth.ScopeGuard.ResolveAsync(p, db);
            if (!sc.IsAll) return Results.Json(new { ok = false, message = "无权销毁数据包" }, statusCode: 403);
            if (!sc.CanWrite) return Auth.ScopeGuard.ReadOnlyDenied();

            var pkg = await db.DataPackages.FirstOrDefaultAsync(x => x.Id == pkgId);
            if (pkg == null) return Results.Ok(new { ok = false, message = "数据包不存在" });
            if (pkg.Status == PackageStatus.Destroyed)
                return Results.Ok(new { ok = true, message = "该包已销毁，无需重复操作" });

            var deleted = false;
            if (!string.IsNullOrWhiteSpace(pkg.FilePath))
            {
                var full = Path.Combine(env.ContentRootPath, pkg.FilePath!.Replace('/', Path.DirectorySeparatorChar));
                try { if (File.Exists(full)) { File.Delete(full); deleted = true; } } catch { /* 删不掉也不阻断状态置位 */ }
            }
            pkg.Status = PackageStatus.Destroyed;
            await db.SaveChangesAsync();
            await AuditRead(db, p, "销毁数据包", pkg.PkgNo,
                deleted ? "已删除导出文件并置为已销毁" : "已置为已销毁（导出文件此前不存在）");
            return Results.Ok(new { ok = true, message = "已销毁 " + pkg.PkgNo + (deleted ? "（导出文件已删除）" : "") });
        }).RequireAuthorization();

        // ---------- 只校验不下载 ----------"""
s = s.replace(anchor, endpoints, 1)

# 3) 请求体 record（放在文件末尾命名空间内）
if 'GeneratePkgReq' in s and 'public record GeneratePkgReq' not in s:
    s = s.rstrip() + """

/// <summary>生成数据包请求体（后台「生成数据包」表单）</summary>
public record GeneratePkgReq(
    int meetingId,
    List<string>? matOrder,
    string? expireAt,
    List<string>? padNos,
    string? encrypt,
    string? clearPolicy,
    string? watermark,
    string? fontLevel,
    int? allowFontChange);
"""
open(API, 'w', encoding='utf-8').write(s)
print("✓ 后端：生成数据包 / 销毁数据包 接口 + GeneratePkgReq + 列表 pid")
