#!/usr/bin/env python3
import argparse
import base64
import json
import os
import re
import sys


def b64_decode_flexible(s):
    t = "".join(s.strip().split())
    pad = (-len(t)) % 4
    t = t + ("=" * pad)
    try:
        return base64.b64decode(t).decode("utf-8", errors="replace")
    except Exception:
        return base64.urlsafe_b64decode(t).decode("utf-8", errors="replace")


def b64_encode_std(s):
    return base64.b64encode(s.encode("utf-8")).decode("ascii")


def normalize_vmess_payload(payload):
    clean = "".join(payload.strip().split())
    pad = (-len(clean)) % 4
    return clean + ("=" * pad)


def update_vmess_line(line, domain, name_rx=None):
    stripped = line.strip()
    if not stripped.startswith("vmess://"):
        return line, False, None

    payload = stripped[len("vmess://") :]
    try:
        body = b64_decode_flexible(normalize_vmess_payload(payload))
        obj = json.loads(body)
    except Exception as e:
        return line, False, f"decode_error: {e}"

    ps = str(obj.get("ps", ""))
    if name_rx is not None and not name_rx.search(ps):
        return line, False, None

    if str(obj.get("add", "")).strip() == domain:
        return line, False, None

    obj["add"] = domain
    encoded = b64_encode_std(json.dumps(obj, ensure_ascii=False, separators=(",", ":")))
    return f"vmess://{encoded}", True, None


def read_domain(args):
    if args.domain:
        return args.domain.strip()
    if not args.domain_file:
        raise ValueError("must provide --domain or --domain-file")
    with open(args.domain_file, "r", encoding="utf-8") as f:
        return f.read().strip()


def main():
    ap = argparse.ArgumentParser(description="Update vmess:// links by replacing add field")
    ap.add_argument("--input", required=True, help="Input subscription file")
    ap.add_argument("--output", required=True, help="Output subscription file")
    ap.add_argument("--domain", default="", help="Target domain to set as add")
    ap.add_argument("--domain-file", default="./runtime/current_domain.txt", help="Domain file path")
    ap.add_argument("--name-regex", default="", help="Only update nodes whose ps matches regex")
    ap.add_argument(
        "--subscription-base64",
        action="store_true",
        help="Input/output file is base64-encoded full subscription content",
    )
    args = ap.parse_args()

    domain = read_domain(args)
    if not domain:
        print(json.dumps({"status": "error", "error": "empty domain"}, ensure_ascii=True), file=sys.stderr)
        sys.exit(1)

    name_rx = re.compile(args.name_regex) if args.name_regex else None

    with open(args.input, "r", encoding="utf-8") as f:
        raw_input = f.read()

    content = raw_input
    if args.subscription_base64:
        content = b64_decode_flexible(raw_input)

    lines = content.splitlines()
    out_lines = []
    total_vmess = 0
    updated = 0
    errors = 0

    for line in lines:
        if line.strip().startswith("vmess://"):
            total_vmess += 1
        new_line, changed, err = update_vmess_line(line, domain, name_rx=name_rx)
        if changed:
            updated += 1
        if err:
            errors += 1
        out_lines.append(new_line)

    out_text = "\n".join(out_lines)
    if content.endswith("\n"):
        out_text += "\n"

    write_text = out_text
    if args.subscription_base64:
        write_text = b64_encode_std(out_text)

    os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True)
    with open(args.output, "w", encoding="utf-8") as f:
        f.write(write_text)

    result = {
        "status": "ok",
        "domain": domain,
        "total_vmess": total_vmess,
        "updated": updated,
        "errors": errors,
        "output": args.output,
    }
    print(json.dumps(result, ensure_ascii=True))


if __name__ == "__main__":
    main()
