#!/usr/bin/env python3
"""Generate fake crash JSON files (java + native) for batch upload testing."""
from __future__ import annotations

import json
import random
import uuid
from datetime import datetime, timezone
from pathlib import Path

OUT_DIR = Path(__file__).resolve().parent / "test_reports"
JAVA_COUNT = 35
NATIVE_COUNT = 30
BASE_MS = int(datetime(2026, 5, 1, 12, 0, tzinfo=timezone.utc).timestamp() * 1000)
GENERATION_SEED = 20260523

DEVICES = [
    ("Samsung", "samsung", "Galaxy Tab A9+", "gta9pwifi", 34, "14"),
    ("Lenovo", "Lenovo", "Tab P12", "TB370FU", 34, "14"),
    ("Xiaomi", "Xiaomi", "Redmi Pad SE", "xun", 33, "13"),
    ("Doogee", "DOOGEE", "Tab G6 Max", "TabG6Max", 34, "14"),
    ("Huawei", "HUAWEI", "MatePad 11", "BAH3", 31, "12"),
    ("Amazon", "Amazon", "Fire HD 10", "trona", 30, "11"),
    ("Google", "google", "Pixel Tablet", "tangorpro", 34, "14"),
    ("OnePlus", "OnePlus", "Pad Go", "OPD2303", 33, "13"),
    ("Realme", "realme", "Pad 2", "RMX2201", 33, "13"),
    ("Nokia", "HMD Global", "T21", "RON", 33, "13"),
    ("TCL", "TCL", "Tab 10 Gen2", "9081G", 31, "12"),
    ("Vivo", "vivo", "Pad Air", "PA2353", 34, "14"),
    ("Oppo", "OPPO", "Pad Neo", "OPD2301", 33, "13"),
    ("Asus", "asus", "ZenPad 10", "P00C", 29, "10"),
    ("Acer", "Acer", "Iconia Tab P10", "acer_p10", 31, "12"),
    ("Motorola", "motorola", "Tab G70", "scout", 33, "13"),
    ("Honor", "HONOR", "Pad X8a", "HEY2", 34, "14"),
]

JAVA_TEMPLATES = [
    {
        "fingerprint": "a1b2c3d4e5f6npe01",
        "scenario_weights": ("cast_session", "cast_session", "app_runtime"),
        "process": "main",
        "activity": "ScreenCastActivity",
        "java": {
            "thread": "main",
            "exception": "java.lang.NullPointerException",
            "message": "Attempt to invoke virtual method on a null object reference",
            "stack_frames": [
                "at com.foxx.androidcast.sender.ScreenCastService.onCreate(ScreenCastService.java:118)",
                "at android.app.ActivityThread.handleCreateService(ActivityThread.java:5148)",
            ],
        },
    },
    {
        "fingerprint": "a1b2c3d4e5f6npe01",
        "scenario_weights": ("cast_session", "cast_session"),
        "process": "main",
        "activity": "CastSettingsActivity",
        "java": {
            "thread": "main",
            "exception": "java.lang.NullPointerException",
            "message": "encoderSurface was null when starting capture",
            "stack_frames": [
                "at com.foxx.androidcast.sender.ScreenCastService.startCapture(ScreenCastService.java:412)",
                "at com.foxx.androidcast.sender.ScreenCastService.onStartCommand(ScreenCastService.java:205)",
            ],
        },
    },
    {
        "fingerprint": "b2c3d4e5f6a01codec",
        "scenario_weights": ("cast_session", "cast_session"),
        "process": "main",
        "activity": "ScreenCastActivity",
        "java": {
            "thread": "EncoderThread-1",
            "exception": "java.lang.IllegalStateException",
            "message": "MediaCodec codec is released already",
            "stack_frames": [
                "at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)",
                "at com.foxx.androidcast.encoder.H264Encoder.drain(H264Encoder.java:88)",
            ],
        },
    },
    {
        "fingerprint": "c3d4e5f6a01b02oom",
        "scenario_weights": ("cast_session", "app_runtime"),
        "process": "main",
        "activity": "MainActivity",
        "java": {
            "thread": "pool-3-thread-2",
            "exception": "java.lang.OutOfMemoryError",
            "message": "Failed to allocate a 8294400 byte allocation with 524288 free bytes",
            "stack_frames": [
                "at android.graphics.Bitmap.createBitmap(Bitmap.java:1234)",
                "at com.foxx.androidcast.capture.FrameRingBuffer.push(FrameRingBuffer.java:56)",
            ],
        },
    },
    {
        "fingerprint": "d4e5f6a01b02c03net",
        "scenario_weights": ("app_runtime", "cast_session", "app_runtime"),
        "process": "main",
        "activity": "MainActivity",
        "java": {
            "thread": "UdpSender-1",
            "exception": "java.net.SocketTimeoutException",
            "message": "connect timed out",
            "stack_frames": [
                "at com.foxx.androidcast.network.udp.UdpCastTransport.send(UdpCastTransport.java:142)",
                "at com.foxx.androidcast.sender.ScreenCastService.flushStats(ScreenCastService.java:455)",
            ],
        },
    },
    {
        "fingerprint": "f6a01b02c03d04e05perm",
        "scenario_weights": ("app_runtime", "app_runtime"),
        "process": "main",
        "activity": "MainActivity",
        "java": {
            "thread": "main",
            "exception": "java.lang.SecurityException",
            "message": "MediaProjection requires FOREGROUND_SERVICE_MEDIA_PROJECTION",
            "stack_frames": [
                "at com.foxx.androidcast.sender.ScreenCastService.acquireProjection(ScreenCastService.java:167)",
                "at com.foxx.androidcast.ui.MainActivity.startCast(MainActivity.java:89)",
            ],
        },
    },
]

NATIVE_TEMPLATES = [
    {
        "fingerprint": "e5f6a01b02c03d04nat",
        "scenario_weights": ("cast_session", "cast_session"),
        "process": "main",
        "activity": "ScreenCastActivity",
        "native": {
            "signal": "SIGSEGV",
            "backtrace": [
                "#00 pc 00012ab4  /data/app/.../lib/arm64/libandroidcast_crash_hook.so (crash_hook_backtrace+64)",
                "#01 pc 0003f210  /data/app/.../lib/arm64/libandroidcast_jni.so (Java_com_foxx_androidcast_native_CrashHook_notify+28)",
                "#02 pc 00021c44  /system/lib64/libart.so (art_quick_generic_jni_trampoline+148)",
            ],
        },
    },
    {
        "fingerprint": "e5f6a01b02c03d04nat",
        "scenario_weights": ("cast_session", "cast_session"),
        "process": ":crashwatcher",
        "activity": "ScreenCastActivity",
        "native": {
            "signal": "SIGSEGV",
            "backtrace": [
                "#00 pc 00008f20  /data/app/.../lib/arm64/libandroidcast_crash_hook.so (unwind_stub+32)",
                "#01 pc 00011a04  /data/app/.../lib/arm64/libandroidcast_crash_hook.so (native_stub_write+96)",
                "#02 pc 0002bc10  /apex/com.android.runtime/lib64/bionic/libc.so (__restore_rt)",
            ],
        },
    },
    {
        "fingerprint": "nat_stfg_omx01",
        "scenario_weights": ("cast_session", "cast_session"),
        "process": "main",
        "activity": "ScreenCastActivity",
        "native": {
            "signal": "SIGSEGV",
            "backtrace": [
                "#00 pc 000a4120  /system/lib64/libstagefright.so (android::MediaCodec::onMessageReceived+312)",
                "#01 pc 0004e8c4  /system/lib64/libhidlbase.so (android::hardware::media::omx::V1_0::IOmxNode::dispatchMessage+68)",
                "#02 pc 0001d330  /data/app/.../lib/arm64/libandroidcast_encoder.so (AvcEncoder::drainNAL+44)",
            ],
        },
    },
    {
        "fingerprint": "nat_abort01",
        "scenario_weights": ("cast_session", "app_runtime"),
        "process": "main",
        "activity": "ScreenCastActivity",
        "native": {
            "signal": "SIGABRT",
            "backtrace": [
                "#00 pc 0008c4bc  /apex/com.android.runtime/lib64/bionic/libc.so (abort+164)",
                "#01 pc 00005120  /data/app/.../lib/arm64/libandroidcast_crash_hook.so (crash_hook_abort+48)",
                "#02 pc 0003a8f0  /data/app/.../lib/arm64/libandroidcast_jni.so (jni_fatal_callback+20)",
            ],
        },
    },
    {
        "fingerprint": "nat_bus_frb02",
        "scenario_weights": ("cast_session", "cast_session"),
        "process": "main",
        "activity": "ScreenCastActivity",
        "native": {
            "signal": "SIGBUS",
            "backtrace": [
                "#00 pc 0000c210  /data/app/.../lib/arm64/libandroidcast_capture.so (FrameRingBuffer::slot_at+28)",
                "#01 pc 0000d884  /data/app/.../lib/arm64/libandroidcast_capture.so (FrameRingBuffer::push_copy+112)",
                "#02 pc 0002f1a0  /data/app/.../lib/arm64/libandroidcast_jni.so (native_on_frame+64)",
            ],
        },
    },
    {
        "fingerprint": "nat_fpe_swr03",
        "scenario_weights": ("cast_session",),
        "process": "main",
        "activity": "ScreenCastActivity",
        "native": {
            "signal": "SIGFPE",
            "backtrace": [
                "#00 pc 0006b2c4  /vendor/lib64/libswresample.so (resample_internal+196)",
                "#01 pc 0004a018  /data/app/.../lib/arm64/libandroidcast_audio.so (AudioResampler::convert+80)",
                "#02 pc 0001ac44  /data/app/.../lib/arm64/libandroidcast_jni.so (native_audio_tick+36)",
            ],
        },
    },
    {
        "fingerprint": "nat_trap_jni05",
        "scenario_weights": ("app_runtime", "cast_session"),
        "process": ":crashwatcher",
        "activity": "MainActivity",
        "native": {
            "signal": "SIGTRAP",
            "backtrace": [
                "#00 pc 00002d10  /data/app/.../lib/arm64/libandroidcast_crash_hook.so (debug_trap_handler+16)",
                "#01 pc 00019c80  /system/lib64/libart.so (art::Thread::DumpStack+320)",
                "#02 pc 0000e4a8  /data/app/.../lib/arm64/libandroidcast_jni.so (CrashHook::selfTest+24)",
            ],
        },
    },
    {
        "fingerprint": "nat_ill_insn06",
        "scenario_weights": ("cast_session", "cast_session"),
        "process": "main",
        "activity": "ScreenCastActivity",
        "native": {
            "signal": "SIGILL",
            "backtrace": [
                "#00 pc 00004580  /data/app/.../lib/arm64/libh264_neon.so (neon_idct_invalid+8)",
                "#01 pc 00012f40  /data/app/.../lib/arm64/libandroidcast_encoder.so (H264Encoder::decode_slice+128)",
                "#02 pc 00008c00  /data/app/.../lib/arm64/libandroidcast_jni.so (encoder_worker+200)",
            ],
        },
    },
]

APP_VERSIONS = [
    ("0.1.0", 100, "deadbeef"),
    ("0.1.1", 101, "cafebabe"),
    ("0.2.0-dev", 120, "a1b2c3d4"),
]


def pick(seq):
    return random.choice(seq)


def build_report(index: int, kind: str, tpl: dict) -> dict:
    mfr, brand, model, device, sdk, release = pick(DEVICES)
    ver_name, ver_code, git = pick(APP_VERSIONS)
    scenario = pick(tpl["scenario_weights"])
    ts = BASE_MS + index * 29 * 60 * 1000 + random.randint(0, 3600000)
    slug = model.lower().replace(" ", "_")[:12]
    report_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, f"fake-{kind}-{index}-{slug}"))

    payload = {
        "schema_version": 1,
        "report_id": report_id,
        "generated_at_epoch_ms": ts,
        "crash_type": "java" if kind == "java" else "native",
        "scenario": scenario,
        "process": tpl["process"],
        "report_file": f"crash_{datetime.fromtimestamp(ts / 1000, tz=timezone.utc).strftime('%Y%m%d_%H%M%S')}.json",
        "fingerprint": tpl["fingerprint"],
        "runtime_context": {
            "app_started_epoch_ms": ts - random.randint(60_000, 3_600_000),
            "last_activity": tpl["activity"],
            "sender_cast_active": scenario == "cast_session",
            "receiver_cast_active": False,
            "cast_active": scenario == "cast_session",
            "session_stats_active": scenario == "cast_session" and random.random() > 0.4,
            "last_session_file": f"session_send_{datetime.fromtimestamp(ts / 1000, tz=timezone.utc).strftime('%Y%m%d_%H%M%S')}.json",
        },
        "device": {
            "manufacturer": mfr,
            "brand": brand,
            "model": model,
            "device": device,
            "product": device,
            "sdk_int": sdk,
            "release": release,
            "abis": ["arm64-v8a", "armeabi-v7a"] if sdk >= 21 else ["armeabi-v7a"],
        },
        "app": {
            "package": "com.foxx.androidcast",
            "version_name": ver_name,
            "version_code": ver_code,
        },
        "build": {
            "debug": ver_name.endswith("-dev") or random.random() > 0.6,
            "git_commit": git,
        },
    }
    if kind == "java":
        payload["java"] = dict(tpl["java"])
    else:
        payload["native"] = {
            "signal": tpl["native"]["signal"],
            "backtrace": list(tpl["native"]["backtrace"]),
        }
    return payload


def main() -> None:
    random.seed(GENERATION_SEED)
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    for old in OUT_DIR.glob("crash*.json"):
        old.unlink()

    for i in range(JAVA_COUNT):
        tpl = pick(JAVA_TEMPLATES)
        path = OUT_DIR / f"crash_java_{i + 1:03d}.json"
        path.write_text(
            json.dumps(build_report(i, "java", tpl), indent=2, ensure_ascii=False) + "\n",
            encoding="utf-8",
        )

    for i in range(NATIVE_COUNT):
        tpl = pick(NATIVE_TEMPLATES)
        path = OUT_DIR / f"crash_native_{i + 1:03d}.json"
        path.write_text(
            json.dumps(build_report(i, "native", tpl), indent=2, ensure_ascii=False) + "\n",
            encoding="utf-8",
        )

    total = JAVA_COUNT + NATIVE_COUNT
    print(f"Wrote {total} reports to {OUT_DIR} ({JAVA_COUNT} java, {NATIVE_COUNT} native)")


if __name__ == "__main__":
    main()
