# -*- coding: utf-8 -*-
"""本轮三项：
 1) 数据包管理：去掉顶部「下载数据包」按钮（它导出的是“当前选中会议”，容易导错），只保留每行下载
 2) （说明性，无代码）导错包的根因即上面那个按钮；行内下载按包自身会议导出
 3) 任免表照片同步：包内 cadres 增加 photo（data URI），PAD 任免审批表右上照片格真实渲染
"""
import shutil, time

BK = time.strftime('%Y%m%d%H%M%S')
A = '/data/www/files/rmsys-app/backend/src/Rmsys.Api/wwwroot/admin.html'
EXP = '/data/www/files/rmsys-app/backend/src/Rmsys.Infrastructure/PackageExporter.cs'
API = '/data/www/files/rmsys-app/backend/src/Rmsys.Api/Endpoints/AdminEndpoints.cs'
TOOLS = '/data/www/files/rmsys-app/backend/src/Rmsys.Tools/Program.cs'
KT = '/data/www/files/rmsys-app/android/app/src/main/java/cn/haidong/zzb/rmsys/PackageBridge.kt'
PADS = ['/data/www/files/rmsys-app/android/app/src/main/assets/web/pad.html',
        '/data/www/files/rmsys-app/backend/src/Rmsys.Api/wwwroot/pad.html']

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

# ============ 1) 去掉顶部「下载数据包」按钮 ============
s = open(A, encoding='utf-8').read(); bk(A)
old_btn = """   + '<button class="btn" onclick="downloadPkg(ROLE.meeting||1)">下载数据包</button>'\n"""
assert s.count(old_btn) == 1, "顶部下载按钮未命中"
s = s.replace(old_btn, "", 1)
# 列表头补一句说明：下载按列表行所属会议导出
old_hint = """   + '<button class="btn pri" onclick="genPkg()">生成数据包</button>'"""
new_hint = """   + '<button class="btn pri" onclick="genPkg()">生成数据包</button>'
   + '<span style="font-size:12.5px;color:var(--ink3);margin-left:4px;">下载请用每行右侧「下载」（按该包所属会议导出）</span>'"""
assert s.count(old_hint) == 1
s = s.replace(old_hint, new_hint, 1)
open(A, 'w', encoding='utf-8').write(s)
print("✓ 后台：顶部「下载数据包」按钮已去掉，保留行内下载")

# ============ 3a) 导出器：把照片写成 data URI ============
s = open(EXP, encoding='utf-8').read(); bk(EXP)
old_prop = """public class PackageExporter"""
new_prop = """public class PackageExporter"""
# 1) 增加 PhotoRoot 属性（放在构造器后不易定位，改为在类内首个方法前插入属性）
anchor_ctor = "    public PackageExporter(RmsysDbContext db)"
assert s.count(anchor_ctor) == 1
# 找到构造器块结尾后插入属性
i = s.index(anchor_ctor)
j = s.index("\n    }\n", i) + len("\n    }\n")
s = s[:j] + """
    /// <summary>
    /// 照片根目录（后端 App_Data 的绝对路径）。设置后会把干部的证件照读成
    /// data:image/...;base64 写进数据包的 cadres.photo，PAD 端据此直接渲染，
    /// 否则只写 hasPhoto 标记（PAD 照片格留空）。
    /// </summary>
    public string? PhotoRoot { get; set; }

    private string? PhotoDataUri(string? photoPath)
    {
        if (string.IsNullOrWhiteSpace(PhotoRoot) || string.IsNullOrWhiteSpace(photoPath)) return null;
        try
        {
            var full = Path.Combine(PhotoRoot!, photoPath.Replace('/', Path.DirectorySeparatorChar));
            if (!File.Exists(full)) return null;
            var bytes = File.ReadAllBytes(full);
            if (bytes.Length == 0 || bytes.Length > 4 * 1024 * 1024) return null;   // 超过 4MB 不塞包
            var mime = bytes.Length > 8 && bytes[0] == 0x89 && bytes[1] == 0x50 ? "image/png"
                     : bytes.Length > 3 && bytes[0] == 0xFF && bytes[1] == 0xD8 ? "image/jpeg" : "image/png";
            return "data:" + mime + ";base64," + Convert.ToBase64String(bytes);
        }
        catch { return null; }
    }
""" + s[j:]

# 2) 建表加 photo 列
old_tbl = """  result text, vote text, hasPhoto integer);"""
new_tbl = """  result text, vote text, hasPhoto integer, photo text);"""
assert s.count(old_tbl) == 1
s = s.replace(old_tbl, new_tbl, 1)

# 3) 插入语句加 photo
old_cols = """                result,vote,hasPhoto)
                values($id,$dc,$dn,$sn,$iss,$nm,$sex,$birth,$nat,$np,$bp,$pd,$wd,$h,$tp,$ex,
                       $efs,$efm,$efl,$efd,$eis,$eim,$eil,$eid,$cur,$prop,$dis,$rw,$exm,$ar,
                       $kw,$perf,$care,$sug,$res,$vote,$hp)\","""
new_cols = """                result,vote,hasPhoto,photo)
                values($id,$dc,$dn,$sn,$iss,$nm,$sex,$birth,$nat,$np,$bp,$pd,$wd,$h,$tp,$ex,
                       $efs,$efm,$efl,$efd,$eis,$eim,$eil,$eid,$cur,$prop,$dis,$rw,$exm,$ar,
                       $kw,$perf,$care,$sug,$res,$vote,$hp,$ph)\","""
assert s.count(old_cols) == 1
s = s.replace(old_cols, new_cols, 1)

old_val = """                ("$res", mc.Result), ("$vote", mc.Vote), ("$hp", string.IsNullOrEmpty(c.PhotoPath) ? 0 : 1));"""
new_val = """                ("$res", mc.Result), ("$vote", mc.Vote), ("$hp", string.IsNullOrEmpty(c.PhotoPath) ? 0 : 1),
                ("$ph", PhotoDataUri(c.PhotoPath)));"""
assert s.count(old_val) == 1
s = s.replace(old_val, new_val, 1)
open(EXP, 'w', encoding='utf-8').write(s)
print("✓ 导出器：cadres.photo（data URI）+ PhotoRoot")

# ============ 3b) 两个导出入口设置 PhotoRoot ============
s = open(API, encoding='utf-8').read(); bk(API)
old = """                var r = await new PackageExporter(db).ExportEncryptedAsync(meetingId, path, "admin");"""
new = """                var r = await new PackageExporter(db)
                {
                    PhotoRoot = Path.Combine(env.ContentRootPath, "App_Data")   // 证件照随包下发
                }.ExportEncryptedAsync(meetingId, path, "admin");"""
assert s.count(old) == 1
s = s.replace(old, new, 1)
open(API, 'w', encoding='utf-8').write(s)
print("✓ 接口导出：PhotoRoot 已设置")

s = open(TOOLS, encoding='utf-8').read(); bk(TOOLS)
old = """        var exp = new PackageExporter(db);
        var r = plain ? await exp.ExportAsync(meetingId, outPath, "cli")
                      : await exp.ExportEncryptedAsync(meetingId, outPath, "cli");"""
new = """        var exp = new PackageExporter(db)
        {
            PhotoRoot = Path.Combine(Directory.GetCurrentDirectory(), "src", "Rmsys.Api", "App_Data")
        };
        var r = plain ? await exp.ExportAsync(meetingId, outPath, "cli")
                      : await exp.ExportEncryptedAsync(meetingId, outPath, "cli");"""
assert s.count(old) == 1
s = s.replace(old, new, 1)
open(TOOLS, 'w', encoding='utf-8').write(s)
print("✓ 命令行导出：PhotoRoot 已设置")

# ============ 3c) Kotlin：读 photo 列并下发到页面 ============
s = open(KT, encoding='utf-8').read(); bk(KT)
old_sel = """                          rewards, exams, appointReason, kw, perf, care, suggest, result, vote
                   from cadres order by sortNo\"\"\", null"""
new_sel = """                          rewards, exams, appointReason, kw, perf, care, suggest, result, vote,
                          hasPhoto, photo
                   from cadres order by sortNo\"\"\", null"""
assert s.count(old_sel) == 1
s = s.replace(old_sel, new_sel, 1)
old_put = """                    o.put("vote", cur.str(33))"""
new_put = """                    o.put("vote", cur.str(33))
                    // 证件照：包里是 data:image/...;base64 字符串，页面直接当 img.src 用
                    val photo = cur.str(35) ?: ""
                    o.put("photo", photo)"""
assert s.count(old_put) == 1
s = s.replace(old_put, new_put, 1)
open(KT, 'w', encoding='utf-8').write(s)
print("✓ Kotlin：bootstrap 下发 cadres[].photo")

# ============ 3d) pad.html：照片格渲染 ============
OLD_CELL = """   + '<td class="photo" rowspan="4"></td></tr>';"""
NEW_CELL = """   + '<td class="photo" rowspan="4">'+(c.photo?('<img src="'+esc(c.photo)+'" alt="证件照">'):'')+'</td></tr>';"""
OLD_CSS = """.t30 td.photo{padding:0;background:#fff;}"""
NEW_CSS = """.t30 td.photo{padding:0;background:#fff;}
.t30 td.photo img{width:100%;height:100%;object-fit:cover;display:block;}"""
for p in PADS:
    t = open(p, encoding='utf-8').read(); bk(p)
    assert t.count(OLD_CELL) == 1, p
    assert t.count(OLD_CSS) == 1, p
    t = t.replace(OLD_CELL, NEW_CELL, 1).replace(OLD_CSS, NEW_CSS, 1)
    open(p, 'w', encoding='utf-8').write(t)
    print("✓ PAD 照片渲染：", p.split('/')[-3])

print("\n备份后缀 .bakP." + BK)
