import os
import sys
import uuid
import time
import shutil
import threading
import random
from datetime import datetime, timedelta
import numpy as np
import cv2
import insightface
from insightface.app import FaceAnalysis
from PIL import Image, ImageDraw, ImageFont
from flask import Flask, render_template, request, send_file, jsonify
from concurrent.futures import ThreadPoolExecutor

# ============================================================
# CONFIGURATION - Adapted for /data/onlyface deployment
# ============================================================
BASE = "/data/onlyface"
FONT_DIR = "/usr/share/fonts/truetype/dejavu"
MODEL_DIR = os.path.join(BASE, "models")

sys.path.insert(0, BASE)

app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024
app.config["UPLOAD_FOLDER"] = os.path.join(BASE, "web", "uploads")
app.config["RESULT_FOLDER"] = os.path.join(BASE, "web", "results")
app.config["PAIRED_PIN"] = os.path.join(BASE, "pin")
app.config["PAIRED_POUT"] = os.path.join(BASE, "pout")
app.config["TEMPLATES_DIR"] = os.path.join(BASE, "templates")

for d in (app.config["UPLOAD_FOLDER"], app.config["RESULT_FOLDER"],
          app.config["PAIRED_PIN"], app.config["PAIRED_POUT"], app.config["TEMPLATES_DIR"]):
    os.makedirs(d, exist_ok=True)

PROVIDERS = ["CPUExecutionProvider"]

# ============================================================
# LOAD MODELS
# ============================================================
print("[Init] Loading FaceAnalysis (buffalo_l)...", flush=True)
face_app = FaceAnalysis(name="buffalo_l", root="/data/onlyface/models", providers=PROVIDERS)
face_app.prepare(ctx_id=-1, det_size=(640, 640))

print("[Init] Loading inswapper_128...", flush=True)
swapper = insightface.model_zoo.get_model(
    os.path.join(MODEL_DIR, "inswapper_128.onnx"), providers=PROVIDERS
)

print("[Init] Loading silueta...", flush=True)
_silueta_session = None

def _get_silueta():
    global _silueta_session
    if _silueta_session is None:
        import onnxruntime as ort
        path = os.path.join(MODEL_DIR, "silueta.onnx")
        if not os.path.exists(path):
            raise FileNotFoundError(f"silueta 模型不存在: {path}")
        _silueta_session = ort.InferenceSession(path, providers=["CPUExecutionProvider"])
    return _silueta_session

TEMPLATE_GIRL = os.path.join(app.config["TEMPLATES_DIR"], "05.jpg")

# Import local modules
from mrz import build_line1, build_line2
from bisnet import change_background

executor = ThreadPoolExecutor(max_workers=2)
tasks = {}
tasks_lock = threading.Lock()

print("[Init] Ready!", flush=True)


# ============================================================
# UTILS
# ============================================================
def gen_passport_no():
    return "MK" + "".join(str(random.randint(0, 9)) for _ in range(6))

def paired_name():
    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    uid = uuid.uuid4().hex[:6]
    return f"{ts}_{uid}"

def gen_random_date(start_year, end_year):
    y = random.randint(start_year, end_year)
    m = random.randint(1, 12)
    d = random.randint(1, 28)
    return datetime(y, m, d)

def fmt_date(dt):
    MONTHS_ABBR = ["JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"]
    return f"{dt.day:02d} {MONTHS_ABBR[dt.month-1]} {dt.year}"


# ============================================================
# RANDOM DATA (Female only)
# ============================================================
FEMALE_SURNAMES = ["EI", "SU", "KHIN", "MAY", "NU", "THAN", "THIDA", "NILAR", "MOE", "YIN", "HTWE", "WIN", "MYA"]
FEMALE_GIVEN = ["SU MYAT", "THIN", "ZAR", "THU", "YADANAR", "HTAY", "KHINE", "WAI", "EI", "MON", "THIRI", "AYE", "PHYU"]
BIRTH_PLACES = ["YANGON", "MANDALAY", "TAUNGGYI", "BAGO", "PATHEIN", "SITTWE",
                "MYITKYINA", "MAWLAMYINE", "MONYWA", "MEIKTILA", "MATMAN", "KYAINGTONG"]
AUTHORITIES = ["MOHA, KYAINGTONG", "MOHA, YANGON", "MOHA, MANDALAY"]

def random_passport_data():
    surname = random.choice(FEMALE_SURNAMES)
    given = random.choice(FEMALE_GIVEN)
    dob = gen_random_date(1980, 2005)
    issue = gen_random_date(2020, 2025)
    expiry = issue + timedelta(days=5 * 365)
    return {
        "passport_no": gen_passport_no(),
        "sex": "F",
        "surname": surname,
        "given_name": given,
        "dob": fmt_date(dob),
        "birth_place": random.choice(BIRTH_PLACES),
        "issue_date": fmt_date(issue),
        "expiry_date": fmt_date(expiry),
    }


# ============================================================
# MRZ (using local mrz module)
# ============================================================
def build_mrz(data):
    surname = (data.get("surname", "") or "").upper().replace(" ", "<")
    given = (data.get("given_name", "") or "").upper().replace(" ", "<")
    country = (data.get("country_code", "") or "MMR").upper()[:3]
    dtype = (data.get("type", "") or "PV").upper()

    pas = (data.get("passport_no", "") or "").upper().replace(" ", "").replace("<", "")
    if not pas:
        pas = gen_passport_no()

    def _parse(s):
        for f in ("%d %b %Y", "%d %B %Y", "%Y-%m-%d", "%d/%m/%Y", "%m/%d/%Y"):
            try:
                return datetime.strptime(s, f)
            except Exception:
                pass
        return None

    dob_dt = _parse(data.get("dob", "") or "") or datetime(2000, 2, 6)
    exp_dt = _parse(data.get("expiry_date", "") or "") or datetime(2030, 11, 23)
    sex = (data.get("sex", "") or "F").upper()[:1]
    if sex not in ("M", "F"):
        sex = "F"

    line1 = build_line1(surname, given, dtype, country)
    line2 = build_line2(pas[:8], dob_dt.strftime("%y%m%d"),
                        exp_dt.strftime("%y%m%d"), sex, country)
    return line1, line2


# ============================================================
# 证件照制作 (ICAO 35×45mm @ 300dpi) - using silueta
# ============================================================
ID_W, ID_H = 413, 531


def silueta_mask(image_bgr):
    """使用 silueta 模型做人像抠图，返回 0~1 float mask (h, w)。"""
    h, w = image_bgr.shape[:2]
    sess = _get_silueta()
    img_resized = cv2.resize(image_bgr, (320, 320))
    img_rgb = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
    mean = np.array([0.5, 0.5, 0.5], dtype=np.float32)
    std = np.array([1.0, 1.0, 1.0], dtype=np.float32)
    img_norm = (img_rgb - mean) / std
    img_t = img_norm.transpose(2, 0, 1)[None, ...].astype(np.float32)
    outs = sess.run(None, {"input.1": img_t})
    mask = outs[0][0, 0]
    mask = (mask - mask.min()) / (mask.max() - mask.min() + 1e-8)
    mask_full = cv2.resize(mask, (w, h))
    return mask_full


def make_id_photo(source_path, output_path, bg_color=(255, 255, 255),
                  out_w=413, out_h=531):
    """将任意大头照制作为护照标准 35x45mm 证件照 (413x531 @300dpi)。
    规则：白底纯色填充；脸居中上 1/3；肩部可见；正脸对齐。"""
    img = cv2.imread(source_path)
    if img is None:
        return "无法读取源图片"
    faces = face_app.get(img)
    if not faces:
        return "源图片未检测到人脸"
    face = max(faces, key=lambda f: f.det_score)
    x1, y1, x2, y2 = face.bbox.astype(int)
    fw, fh = x2 - x1, y2 - y1
    cx, cy = (x1 + x2) // 2, (y1 + y2) // 2

    # 标准比例：脸高占整图 0.5（顶部留 0.2 头顶，脸部 0.5，下方 0.3 肩膀）
    scale = (out_h * 0.5) / max(fh, 1)
    new_fw, new_fh = int(fw * scale), int(fh * scale)
    # 脸部中心 y：放在 out_h * 0.45 处（上部 45% 位置，符合护照照）
    target_cy = int(out_h * 0.45)
    target_cx = out_w // 2

    # 仿射变换矩阵：把源图脸区域 warp 到目标位置
    src_pts = np.float32([
        [cx - fw / 2, y1 - fh * 0.3],
        [cx + fw / 2, y1 - fh * 0.3],
        [cx - fw / 2, y2 + fh * 1.0],
    ])
    dst_pts = np.float32([
        [target_cx - new_fw / 2, target_cy - new_fh * 0.5 - int(new_fh * 0.15)],
        [target_cx + new_fw / 2, target_cy - new_fh * 0.5 - int(new_fh * 0.15)],
        [target_cx - new_fw / 2, target_cy - new_fh * 0.5 + int(new_fh * 1.5)],
    ])
    M = cv2.getAffineTransform(src_pts, dst_pts)
    warped = cv2.warpAffine(img, M, (out_w, out_h),
                            borderMode=cv2.BORDER_CONSTANT,
                            borderValue=bg_color)
    cv2.imwrite(output_path, warped)
    return ""


# ============================================================
# 换脸
# ============================================================
def crop_main_face_from_template(swapped_path, template_path, out_path=None):
    """从已换脸模板中裁切主头像（左下大图）。"""
    if out_path is None:
        out_path = swapped_path.replace(".png", "_face.jpg")
    img_bgr = cv2.imread(swapped_path)
    if img_bgr is None:
        raise RuntimeError("swapped 模板读取失败")
    faces = face_app.get(img_bgr)
    if not faces:
        raise RuntimeError("swapped 模板未检测到人脸")
    h, w = img_bgr.shape[:2]
    # 主头像在左半边，bbox 左上 x < w * 0.4
    left_faces = [f for f in faces if f.bbox[0] < w * 0.4]
    if not left_faces:
        left_faces = faces
    face = max(left_faces, key=lambda f: (f.bbox[2] - f.bbox[0]) * (f.bbox[3] - f.bbox[1]))
    x1, y1, x2, y2 = face.bbox.astype(int)
    fw, fh = x2 - x1, y2 - y1
    pad_x = int(fw * 0.3)
    pad_top = int(fh * 0.5)
    pad_bot = int(fh * 0.6)
    nx1 = max(0, x1 - pad_x)
    ny1 = max(0, y1 - pad_top)
    nx2 = min(w, x2 + pad_x)
    ny2 = min(h, y2 + pad_bot)
    crop_bgr = img_bgr[ny1:ny2, nx1:nx2]
    cv2.imwrite(out_path, crop_bgr, [int(cv2.IMWRITE_JPEG_QUALITY), 92])
    return out_path


def swap_faces(source_path, target_path, output_path):
    img_src = cv2.imread(source_path)
    img_dst = cv2.imread(target_path)
    if img_src is None:
        return "无法读取源图片"
    if img_dst is None:
        return "无法读取目标图片"
    faces_src = face_app.get(img_src)
    faces_dst = face_app.get(img_dst)
    if not faces_src:
        return "源图片未检测到人脸"
    if not faces_dst:
        return "目标图片未检测到人脸"
    src_face = max(faces_src, key=lambda f: f.det_score)
    result = img_dst.copy()
    for face in faces_dst:
        result = swapper.get(result, face, src_face, paste_back=True)
    cv2.imwrite(output_path, result)
    return ""


# ============================================================
# 护照图片生成
# ============================================================
PASS_W, PASS_H = 905, 630


def generate_passport_image(data, source_photo_path, output_path, base_image=None):
    """完整自绘缅甸护照：底色、边框、标题、字段、MRZ、水印。"""
    img = Image.new("RGB", (PASS_W, PASS_H), "#EBE5D9")
    draw = ImageDraw.Draw(img)

    def _font(name, size):
        try:
            return ImageFont.truetype(os.path.join(FONT_DIR, name), size)
        except Exception:
            return ImageFont.load_default()

    def _try(names, size):
        for n in names:
            p = os.path.join(FONT_DIR, n)
            if os.path.exists(p):
                try:
                    return ImageFont.truetype(p, size)
                except Exception:
                    pass
        return _font("DejaVuSans-Bold.ttf", size)

    title_f   = _try(["DejaVuSerif-Bold.ttf", "DejaVuSans-Bold.ttf"], 22)
    pass_f    = _try(["DejaVuSerif-Bold.ttf", "DejaVuSans-Bold.ttf"], 17)
    label_f   = _try(["DejaVuSerif.ttf",       "DejaVuSans.ttf"], 12)
    val_f     = _try(["DejaVuSerif-Bold.ttf",  "DejaVuSans-Bold.ttf"], 15)
    mrz_f     = _try(["DejaVuSansMono.ttf"], 20)
    tiny_f    = _try(["DejaVuSans.ttf"], 8)
    star_f    = _try(["DejaVuSans-Bold.ttf"], 38)

    # 底纹横线
    for i in range(0, PASS_H, 18):
        draw.line([0, i, PASS_W, i], fill="#f0ece0", width=1)
    # 双层边框
    draw.rectangle([4, 4, PASS_W - 5, PASS_H - 5], outline="#6B5B3D", width=4)
    draw.rectangle([10, 10, PASS_W - 11, PASS_H - 11], outline="#8B7B5D", width=1)

    # 左上国徽
    ex, ey = 30, 30
    ew, eh = 70, 90
    draw.ellipse([ex, ey, ex + ew, ey + eh], outline="#6B5B3D", width=1)
    draw.text((ex + ew // 2, ey + 28), "★", fill="#C4A35A", font=star_f, anchor="mm")
    draw.text((ex + ew // 2, ey + 60), "MYANMAR", fill="#6B5B3D", font=tiny_f, anchor="mt")

    # 标题 + PASSPORT
    draw.text((PASS_W // 2, 30), "REPUBLIC OF THE UNION OF MYANMAR",
              fill="#1a1a1a", font=title_f, anchor="mt")
    draw.text((PASS_W // 2, 66), "P A S S P O R T",
              fill="#6B0000", font=pass_f, anchor="mt")
    draw.rectangle([PASS_W // 2 - 70, 82, PASS_W // 2 + 70, 92],
                   outline="#C4A35A", width=1)

    # 右上 Passport No
    pas = data.get("passport_no", "") or "MK000000"
    dtype = (data.get("type", "") or "PV").upper()
    ccode = (data.get("country_code", "") or "MMR").upper()
    draw.text((PASS_W - 28, 28), f"Passport No  {pas}", fill="#1a1a1a",
              font=label_f, anchor="rt")
    draw.text((PASS_W - 28, 48), f"Type  {dtype}    Code  {ccode}", fill="#555",
              font=tiny_f, anchor="rt")

    # 照片区（左下，35×45mm 比例 413×531，缩放到 175×230）
    px, py = 30, 220
    pw, ph = 175, 230
    draw.rectangle([px - 3, py - 3, px + pw + 3, py + ph + 3],
                   outline="#8B7B5D", width=1)
    photo_used = None
    if source_photo_path and os.path.exists(source_photo_path):
        photo_used = source_photo_path
    elif base_image and os.path.exists(base_image):
        photo_used = base_image
    if photo_used:
        face_img = Image.open(photo_used).convert("RGB")
        fw, fh = face_img.size
        ratio = pw / ph
        cur = fw / fh
        if cur > ratio:
            nfw = int(fh * ratio)
            face_img = face_img.crop(((fw - nfw) // 2, 0, (fw + nfw) // 2, fh))
        else:
            nfh = int(fw / ratio)
            face_img = face_img.crop((0, (fh - nfh) // 2, fw, (fh + nfh) // 2))
        face_img = face_img.resize((pw, ph), Image.LANCZOS)
        img.paste(face_img, (px, py))
    else:
        draw.text((px + pw // 2, py + ph // 2), "PHOTO\n35x45",
                  fill="#aaa", font=label_f, anchor="mm")

    return _finalize(img, data, output_path, label_f, val_f, mrz_f, tiny_f)


def _finalize(img, data, output_path, label_f, val_f, mrz_f, tiny_f):
    draw = ImageDraw.Draw(img)
    surname    = data.get("surname", "") or ""
    given_name = data.get("given_name", "") or ""
    nationality= data.get("nationality", "") or "MYANMAR"
    dob        = data.get("dob", "") or ""
    sex        = data.get("sex", "") or "F"
    issue      = data.get("issue_date", "") or ""
    expiry     = data.get("expiry_date", "") or ""
    birthplace = data.get("birth_place", "") or ""
    authority  = data.get("authority", "") or "MOHA, KYAINGTONG"

    c_l1, c_v1 = 230, 230
    c_l2, c_v2 = 555, 555
    rows = [
        (110, "Surname",        surname,    "Given Name",   given_name),
        (148, "Nationality",    nationality, "",            ""),
        (186, "Date of birth",  dob,         "Sex",         sex),
        (224, "Place of birth", birthplace,  "Authority",   authority),
        (262, "Date of issue",  issue,       "Date of expiry", expiry),
    ]
    for y, l1, v1, l2, v2 in rows:
        draw.text((c_l1, y - 13), l1, fill="#666", font=tiny_f)
        draw.text((c_v1, y), v1, fill="#111", font=val_f)
        if l2:
            draw.text((c_l2, y - 13), l2, fill="#666", font=tiny_f)
            draw.text((c_v2, y), v2, fill="#111", font=val_f)

    # 签名
    sig_y = 305
    draw.text((c_l1, sig_y - 13), "Holder's signature", fill="#666", font=tiny_f)
    draw.line([c_v1, sig_y + 22, c_v1 + 200, sig_y + 22], fill="#aaa", width=1)

    # MRZ
    line1, line2 = build_mrz(data)
    mrz_top = 480
    draw.rectangle([18, mrz_top, PASS_W - 18, mrz_top + 56],
                   fill="#FCFAF5", outline="#8B7B5D", width=2)
    draw.text((25, mrz_top + 6), line1, fill="#111", font=mrz_f)
    draw.text((25, mrz_top + 30), line2, fill="#111", font=mrz_f)

    # 水印
    try:
        wm_f = ImageFont.truetype(os.path.join(FONT_DIR, "DejaVuSans-Bold.ttf"), 22)
    except Exception:
        wm_f = ImageFont.load_default()
    draw.text((PASS_W - 60, 100), "MYANMAR", fill="#f0ece0", font=wm_f, anchor="rt")

    img.save(output_path, dpi=(300, 300))
    return output_path


# ============================================================
# ROUTES
# ============================================================
@app.route("/")
def index():
    return render_template("index.html")


@app.route("/random_passport_data")
def random_data():
    return jsonify({"ok": True, **random_passport_data()})


@app.route("/compute_mrz", methods=["POST"])
def compute_mrz():
    data = request.get_json(force=True) or {}
    line1, line2 = build_mrz(data)
    return jsonify({"ok": True, "line1": line1, "line2": line2})


@app.route("/generate_passport", methods=["POST"])
def gen_passport():
    if "source" not in request.files:
        return jsonify({"ok": False, "error": "请上传大头照"}), 400
    sf = request.files["source"]
    if sf.filename == "":
        return jsonify({"ok": False, "error": "请选择源图片"}), 400

    data = {}
    for key in ["type", "country_code", "passport_no", "surname", "given_name",
                "nationality", "dob", "sex", "issue_date", "expiry_date",
                "birth_place", "authority"]:
        data[key] = (request.form.get(key, "") or "").strip()

    # 固定项
    if not data["type"]: data["type"] = "PV"
    if not data["country_code"]: data["country_code"] = "MMR"
    if not data["nationality"]: data["nationality"] = "MYANMAR"
    if not data["authority"]: data["authority"] = "MOHA, KYAINGTONG"
    data["sex"] = "F"

    rnd = random_passport_data()
    if not data["passport_no"]: data["passport_no"] = rnd["passport_no"]
    if not data["dob"]:         data["dob"] = rnd["dob"]
    if not data["birth_place"]: data["birth_place"] = rnd["birth_place"]
    if not data["issue_date"]:  data["issue_date"] = rnd["issue_date"]
    if not data["expiry_date"]: data["expiry_date"] = rnd["expiry_date"]
    if not data["surname"]:     data["surname"] = rnd["surname"]
    if not data["given_name"]:  data["given_name"] = rnd["given_name"]

    # 保存源图
    src_ext = os.path.splitext(sf.filename)[1] or ".jpg"
    src_name = f"src_{uuid.uuid4().hex}{src_ext}"
    src_path = os.path.join(app.config["UPLOAD_FOLDER"], src_name)
    sf.save(src_path)

    pair_name = paired_name()
    paired_src = os.path.join(app.config["PAIRED_PIN"], f"{pair_name}{src_ext}")
    shutil.copy2(src_path, paired_src)
    print(f"[Paired] 源照 → {paired_src}", flush=True)

    # Step 1: 制作 413×531 证件照（silueta 抠图+换底+几何校正）
    id_name = f"id_{uuid.uuid4().hex}.jpg"
    id_path = os.path.join(app.config["UPLOAD_FOLDER"], id_name)
    err = make_id_photo(src_path, id_path)
    if err:
        return jsonify({"ok": False, "error": f"制证失败: {err}"}), 500

    # Step 2: 证件照脸 → 模板双头像
    swapped_name = f"swapped_{uuid.uuid4().hex}.png"
    swapped_path = os.path.join(app.config["UPLOAD_FOLDER"], swapped_name)
    err = swap_faces(id_path, TEMPLATE_GIRL, swapped_path)
    if err:
        return jsonify({"ok": False, "error": f"换脸失败: {err}"}), 500

    out_name = f"passport_{uuid.uuid4().hex}.png"
    out_path = os.path.join(app.config["RESULT_FOLDER"], out_name)
    task_id = out_name

    with tasks_lock:
        tasks[task_id] = {"status": "processing", "mode": "passport"}

    def do_gen():
        print(f"[Passport] {task_id} started", flush=True)
        try:
            face_crop = crop_main_face_from_template(swapped_path, TEMPLATE_GIRL)
            generate_passport_image(data, face_crop, out_path, base_image=swapped_path)
            print(f"[Passport] {task_id} generated", flush=True)
        except Exception as e:
            import traceback
            print(f"[Passport] {task_id} ERROR: {e}", flush=True)
            traceback.print_exc()
            with tasks_lock:
                tasks[task_id] = {"status": "error", "error": str(e)}
            return
        result_url = f"/result/{out_name}"
        with tasks_lock:
            paired_out = os.path.join(app.config["PAIRED_POUT"], f"{pair_name}.png")
            shutil.copy2(out_path, paired_out)
            print(f"[Paired] 护照 → {paired_out}", flush=True)
            tasks[task_id] = {
                "status": "done",
                "result_url": result_url,
                "mode": "passport",
                "paired_name": pair_name,
            }
        print(f"[Passport] {task_id} done", flush=True)
        for p in [src_path, swapped_path]:
            try: os.remove(p)
            except OSError: pass

    executor.submit(do_gen)
    return jsonify({"ok": True, "task_id": task_id})


@app.route("/make_id_photo", methods=["POST"])
def make_id_photo_api():
    if "source" not in request.files:
        return jsonify({"ok": False, "error": "请上传大头照"}), 400
    sf = request.files["source"]
    if sf.filename == "":
        return jsonify({"ok": False, "error": "请选择源图片"}), 400
    bg = (request.form.get("bg", "white") or "white").lower()
    bg_map = {"white": (255, 255, 255), "blue": (214, 230, 242),
              "red": (220, 60, 60), "gray": (240, 240, 240)}
    bg_color = bg_map.get(bg, (255, 255, 255))

    src_ext = os.path.splitext(sf.filename)[1] or ".jpg"
    src_name = f"src_{uuid.uuid4().hex}{src_ext}"
    src_path = os.path.join(app.config["UPLOAD_FOLDER"], src_name)
    sf.save(src_path)

    id_name = f"id_{uuid.uuid4().hex}.jpg"
    id_path = os.path.join(app.config["UPLOAD_FOLDER"], id_name)
    err = make_id_photo(src_path, id_path, bg_color=bg_color)
    if err:
        return jsonify({"ok": False, "error": err}), 500
    try: os.remove(src_path)
    except OSError: pass
    return jsonify({"ok": True, "result_url": f"/result/{id_name}",
                    "filename": id_name})


@app.route("/smart_crop_source", methods=["POST"])
def smart_crop_source():
    if "source" not in request.files:
        return jsonify({"ok": False, "error": "请上传大头照"}), 400
    sf = request.files["source"]
    if sf.filename == "":
        return jsonify({"ok": False, "error": "请选择源图片"}), 400

    src_ext = os.path.splitext(sf.filename)[1] or ".jpg"
    src_name = f"src_{uuid.uuid4().hex}{src_ext}"
    src_path = os.path.join(app.config["UPLOAD_FOLDER"], src_name)
    sf.save(src_path)

    # Use bisnet.change_background for simple background replacement
    out_name = f"crop_{uuid.uuid4().hex}.jpg"
    out_path = os.path.join(app.config["UPLOAD_FOLDER"], out_name)
    err = change_background(src_path, out_path, bg_color=(255, 255, 255))
    if err:
        shutil.copy2(src_path, out_path)
    try: os.remove(src_path)
    except OSError: pass
    return jsonify({"ok": True, "result_url": f"/result/{out_name}",
                    "filename": out_name})


@app.route("/status/<task_id>")
def task_status(task_id):
    with tasks_lock:
        task = tasks.get(task_id)
    if task is None:
        return jsonify({"ok": False, "error": "任务不存在"}), 404
    return jsonify(task)


@app.route("/result/<filename>")
def download_result(filename):
    for folder in (app.config["RESULT_FOLDER"], app.config["UPLOAD_FOLDER"]):
        path = os.path.join(folder, filename)
        if os.path.exists(path):
            mime = "image/jpeg" if filename.lower().endswith((".jpg", ".jpeg")) else "image/png"
            return send_file(path, mimetype=mime)
    return "文件不存在", 404


def cleanup_old_files(folder, max_age_seconds=3600):
    now = time.time()
    for fname in os.listdir(folder):
        fpath = os.path.join(folder, fname)
        if os.path.isfile(fpath) and (now - os.path.getmtime(fpath) > max_age_seconds):
            try: os.remove(fpath)
            except OSError: pass


@app.route("/cleanup", methods=["POST"])
def cleanup():
    cleanup_old_files(app.config["UPLOAD_FOLDER"], 1800)
    cleanup_old_files(app.config["RESULT_FOLDER"], 1800)
    with tasks_lock:
        tasks.clear()
    return jsonify({"ok": True})


@app.route("/upload_synced", methods=["POST"])
def upload_synced():
    """接收前端上传的护照图片，保存到服务器供查看（参考站设计）"""
    if "file" not in request.files:
        return jsonify({"ok": False, "error": "没有可同步的结果"}), 400
    f = request.files["file"]
    if f.filename == "":
        return jsonify({"ok": False, "error": "文件名为空"}), 400
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    save_name = f"passport_sync_{timestamp}.png"
    save_dir = os.path.join(BASE, "synced_passports")
    os.makedirs(save_dir, exist_ok=True)
    save_path = os.path.join(save_dir, save_name)
    f.save(save_path)
    result_path = os.path.join(app.config["RESULT_FOLDER"], save_name)
    shutil.copy2(save_path, result_path)
    return jsonify({"ok": True, "path": save_path, "url": f"/result/{save_name}"})


@app.route("/sync_to_server", methods=["POST"])
def sync_to_server():
    """接收前端上传的护照图片，保存到服务器固定位置供查看"""
    if "image" not in request.files:
        return jsonify({"ok": False, "error": "未收到图片文件"}), 400

    f = request.files["image"]
    if f.filename == "":
        return jsonify({"ok": False, "error": "文件名为空"}), 400

    # 保存到固定目录，使用时间戳命名
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    save_name = f"passport_sync_{timestamp}.png"
    save_dir = os.path.join(BASE, "synced_passports")
    os.makedirs(save_dir, exist_ok=True)
    save_path = os.path.join(save_dir, save_name)

    f.save(save_path)

    # 返回可访问的 URL（通过 /result/ 路由或直接文件路径）
    # 为了方便查看，也复制一份到 results 目录
    result_path = os.path.join(app.config["RESULT_FOLDER"], save_name)
    shutil.copy2(save_path, result_path)

    return jsonify({
        "ok": True,
        "url": f"/result/{save_name}",
        "saved_path": save_path
    })


if __name__ == "__main__":
    cleanup_old_files(app.config["UPLOAD_FOLDER"], 3600)
    cleanup_old_files(app.config["RESULT_FOLDER"], 3600)
    app.run(host="0.0.0.0", port=7862, debug=False, threaded=True)