#!/usr/bin/env bash
export THDir=/home/user/ほしゅつーる/
#CurrentThreadNum=1778038443 #偽番でエラー起こす用
#CurrentThreadURL="https://mevius.5ch.io/test/read.cgi/internet/1778926030/"
#CurrentDatURL="https://mevius.5ch.io/internet/dat/1778926030"

# ADB/phone preparation is disabled; the safe path is Playwright-only.

set -e
trap 'printf "ERROR: %s\nLine:%s\ncommand: %s\n" "$0" "$LINENO" "$BASH_COMMAND" > "${THDir}/LastErr"' ERR
# Start each run with a clean error marker; ERR trap rewrites it on failure.
: > "${THDir}/LastErr"

if [ -z "$1" ]; then
    HPMode="1"
else
    HPMode="$1"
fi

if [ "$HPMode" != "HtP" ]; then
    PW_Time=$(( LastUnix + 3285 ))

    while [ "$(date +%s)" -lt "$PW_Time" ]; do
        echo "待機中... あと $(( PW_Time - $(date +%s) )) 秒"
        sleep 0.05
    done
fi

if [ "$HPMode" = "HtP" ]; then
    PostMsg="ほしゅ対象発見！"
else
    PostMsg="ききかんりー"
fi

case "$HPMode" in

1|HtP)

PortFile="${THDir}/.last_hosyu_port"
LauncherPidFile="${THDir}/.last_hosyu_launcher_pid"

if [ -f "$LauncherPidFile" ]; then
    LAST_LAUNCHER_PID=$(cat "$LauncherPidFile" 2>/dev/null || :)
    if [ -n "$LAST_LAUNCHER_PID" ]; then
        kill "$LAST_LAUNCHER_PID" 2>/dev/null || :
        sleep 0.2
        if kill -0 "$LAST_LAUNCHER_PID" 2>/dev/null; then
            kill -9 "$LAST_LAUNCHER_PID" 2>/dev/null || :
        fi
    fi
    rm -f "$LauncherPidFile"
fi

if [ -f "$PortFile" ]; then
    LAST_PORT=$(cat "$PortFile")
    if [ -n "$LAST_PORT" ]; then
        pkill -f "remote-debugging-port=$LAST_PORT" || :
    fi
    rm -f "$PortFile"
fi

RunStamp="$(date +%Y%m%d_%H%M%S)_$$"
PythonMainLog="${THDir}/python_main_${RunStamp}.log"

export CurrentThreadURL
export CurrentDatURL
export CurrentThreadNum
export PostMsg
export THDir
export HPMode
export LastUnix

echo "before pythonrun:$(date "+%Y-%m-%d %H:%M:%S")" >>"$THDir/Log2"

set +e
tmpRESULT=$(python3 - << 'EOF' 2>>"$PythonMainLog"
import os
import sys
import time
import hashlib
import asyncio
import traceback
import subprocess
from pathlib import Path
from playwright.async_api import async_playwright

th_dir = os.environ.get("THDir", "/home/user/ほしゅつーる/")
log2 = Path(th_dir) / "Log2"
auto_recovery_log = Path(th_dir) / "auto_recovery.log"

try:
    max_playwright_attempts = max(
        1,
        min(3, int(os.environ.get("HOSYU_PLAYWRIGHT_ATTEMPTS", "2") or "2")),
    )
except ValueError:
    max_playwright_attempts = 2

def log(msg):
    with log2.open("a", encoding="utf-8") as f:
        f.write(f"{msg}:{time.strftime('%Y-%m-%d %H:%M:%S')}\n")

def recovery_log(msg):
    with auto_recovery_log.open("a", encoding="utf-8") as f:
        f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} {msg}\n")

async def wait_until(ts):
    last_remain = None
    while time.time() < ts:
        remain = max(0, int(ts - time.time()))
        if remain != last_remain:
            print(f"待機中... あと {remain} 秒", file=sys.stderr)
            last_remain = remain
        await asyncio.sleep(0.05)

def md5_current_dat_sync():
    current_path = Path(th_dir) / "CurrentThreadNum.dat"
    old_md5 = hashlib.md5(current_path.read_bytes()).hexdigest()

    dat_url = os.environ.get("CurrentDatURL")
    if not dat_url:
        raise RuntimeError("CurrentDatURL is empty")

    result = subprocess.run(
        ["curl", "-s", "-f", "-L", dat_url],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        check=False,
    )

    if result.returncode != 0:
        err = result.stderr.decode("utf-8", "ignore")
        raise RuntimeError(f"curl failed: code={result.returncode} stderr={err}")

    new_bytes = result.stdout.decode("cp932", "ignore").encode("utf-8")
    new_md5 = hashlib.md5(new_bytes).hexdigest()

    return old_md5, new_md5

def can_check_current_dat():
    return bool(os.environ.get("CurrentDatURL")) and (Path(th_dir) / "CurrentThreadNum.dat").exists()

async def dat_changed_safely(reason):
    if not can_check_current_dat():
        recovery_log(f"{reason}: dat check skipped")
        return False

    try:
        old_md5, new_md5 = await asyncio.to_thread(md5_current_dat_sync)
    except Exception as exc:
        recovery_log(f"{reason}: dat check failed: {exc!r}")
        return False

    if old_md5 != new_md5:
        recovery_log(f"{reason}: dat changed; treating direct attempt as done")
        return True

    return False

async def close_browser_safely(browser):
    if browser is None:
        return

    try:
        await browser.close()
    except Exception as exc:
        recovery_log(f"browser close failed: {exc!r}")

async def playwright_post_once(p, attempt, thread_url, post_msg, hp_mode, last_unix):
    browser = None
    submit_attempted = False

    if hp_mode != "HtP":
        # Keep Chromium closed during the scheduled wait; stale pages have been
        # closing before the submit step.
        await wait_until(last_unix + 3296)

    try:
        browser = await p.chromium.launch(
            headless=True,
            args=[
                "--no-sandbox",
                "--disable-dev-shm-usage",
                "--disable-gpu",
                "--disable-background-networking",
                "--disable-component-update",
                "--disable-sync",
                "--disable-features=MediaRouter",
                "--disable-default-apps",
                "--noerrdialogs",
            ],
        )

        context = await browser.new_context(viewport={"width": 1280, "height": 1024})
        page = await context.new_page()

        log("before python1")

        target_url = thread_url.rstrip("/") + "/l1"
        await page.goto(target_url, wait_until="domcontentloaded", timeout=30000)

        submit_btn = page.locator('input[name="submit"]').first
        await submit_btn.wait_for(state="visible", timeout=10000)

        await page.fill('input[name="FROM"]', "ほしゅ")
        await page.fill('input[name="mail"]', "sage")
        await page.fill('textarea[name="MESSAGE"]', post_msg)
        await page.wait_for_timeout(500)

        log("affter python1")

        if hp_mode != "HtP":
            pw_time = last_unix + 3296

            with log2.open("a", encoding="utf-8") as f:
                f.write(f"LastUnix:{last_unix}\n")
                f.write(f"PW_Time:{pw_time}\n")

            old_md5, new_md5 = await asyncio.to_thread(md5_current_dat_sync)

            if old_md5 != new_md5:
                return "changed"

        log("before python2")

        submit_btn = page.locator('input[name="submit"]').first
        await submit_btn.wait_for(state="visible", timeout=10000)

        try:
            submit_attempted = True
            await submit_btn.click(timeout=15000)
        except Exception:
            traceback.print_exc()
            if await dat_changed_safely(f"attempt={attempt} submit click exception"):
                return "pass"
            recovery_log(f"attempt={attempt} submit click exception; no fallback click to avoid duplicate post")
            return "fail_after_submit"

        final_btn_selector = 'input[type="submit"][value*="承諾"]'
        try:
            await page.wait_for_selector(final_btn_selector, timeout=13000)
            await page.click(final_btn_selector)
        except Exception:
            pass

        success = False
        for _ in range(26):
            visible_text = await page.inner_text("body")
            if "書きこみが終わりました" in visible_text:
                success = True
                break
            await asyncio.sleep(0.5)

        try:
            await page.screenshot(path=f"{th_dir}/screenshot.png")
        except Exception as exc:
            recovery_log(f"attempt={attempt} screenshot failed: {exc!r}")

        log("affter python2")

        if success:
            return "pass"

        if await dat_changed_safely(f"attempt={attempt} after submit"):
            return "pass"

        recovery_log(f"attempt={attempt} failed after submit; no retry to avoid duplicate post")
        return "fail_after_submit"

    except Exception:
        if submit_attempted:
            traceback.print_exc()
            if await dat_changed_safely(f"attempt={attempt} exception after submit"):
                return "pass"
            recovery_log(f"attempt={attempt} exception after submit; no retry to avoid duplicate post")
            return "fail_after_submit"

        raise

    finally:
        await close_browser_safely(browser)

async def run():
    thread_url = os.environ.get("CurrentThreadURL")
    post_msg = os.environ.get("PostMsg")
    hp_mode = os.environ.get("HPMode", "1")
    last_unix = int(os.environ.get("LastUnix", "0") or "0")

    if not thread_url:
        raise RuntimeError("CurrentThreadURL is empty")
    if post_msg is None:
        raise RuntimeError("PostMsg is empty")

    async with async_playwright() as p:
        for attempt in range(1, max_playwright_attempts + 1):
            try:
                result = await playwright_post_once(
                    p,
                    attempt,
                    thread_url,
                    post_msg,
                    hp_mode,
                    last_unix,
                )

                print(result)
                return

            except Exception as exc:
                recovery_log(f"attempt={attempt} failed before submit: {exc!r}")

                if attempt < max_playwright_attempts:
                    recovery_log(f"attempt={attempt} retrying with fresh browser")
                    await asyncio.sleep(min(5, attempt * 2))

        recovery_log("all playwright attempts failed before submit; no adb/2chMate fallback")
        print("fail")

if __name__ == "__main__":
    try:
        asyncio.run(run())
    except Exception:
        traceback.print_exc()
        print("fail")
EOF
)
PY_STATUS=$?
set -e

if [ "$PY_STATUS" -ne 0 ] || [ -z "$tmpRESULT" ]; then
    tmpRESULT="fail"
fi

echo "tmpRESULT:$tmpRESULT $(date "+%Y-%m-%d %H:%M:%S")" >>"$THDir/Log2"

archive_recovered_auto_recovery_log() {
    if [ ! -s "$THDir/auto_recovery.log" ]; then
        return 0
    fi

    recovery_archive="$THDir/auto_recovery_recovered_$(date +%Y%m%d_%H%M%S).log"
    if cp "$THDir/auto_recovery.log" "$recovery_archive" 2>/dev/null; then
        printf "%s recovered tmpRESULT:%s; archived stale recovery markers to %s\n" \
            "$(date "+%Y-%m-%d %H:%M:%S")" "$tmpRESULT" "${recovery_archive##*/}" \
            > "$THDir/auto_recovery.log" || :
    else
        printf "%s recovered tmpRESULT:%s; failed to archive stale recovery markers\n" \
            "$(date "+%Y-%m-%d %H:%M:%S")" "$tmpRESULT" \
            >> "$THDir/auto_recovery.log" || :
    fi

    return 0
}

if [ "$tmpRESULT" = "changed" ]; then
    archive_recovered_auto_recovery_log || :
    echo setumei >> "$THDir/setumei"
    exit 0
fi

if [ "$HPMode" = "HtP" ]; then
    echo "$CurrentThreadNum" > "$THDir/LHtPNum"
fi

echo "$tmpRESULT" > "$THDir/restemp"

if [ "$tmpRESULT" = "fail_after_submit" ]; then
    exit 1
fi

if [ "$tmpRESULT" = "pass" ]; then
    archive_recovered_auto_recovery_log || :
    exit 0
fi

echo "$(date "+%Y-%m-%d %H:%M:%S") tmpRESULT:$tmpRESULT; no adb/2chMate fallback" >> "$THDir/auto_recovery.log"
exit 1

;;

tmp)

echo "$(date "+%Y-%m-%d %H:%M:%S") tmp mode disabled; no adb/2chMate fallback" >> "$THDir/auto_recovery.log"
echo fail > "$THDir/restemp"
exit 1

if [ "$(adb shell dumpsys power | grep "mWakefulness=" | cut -d'=' -f2 | tr -d ' ')" = "Dozing" ]; then
    adb shell input keyevent 26
fi

sleep 0.5

if [ "$(adb shell dumpsys window policy | grep -E "showing=.*" | cut -d'=' -f2 | tr -d ' ')" = "true" ]; then
    adb shell input keyevent 82
fi

adb shell am force-stop jp.co.airfront.android.a2chMate

adb shell "echo '[{\"url\":\"$CurrentThreadURL\",\"name\":\"ほしゅo\",\"mail\":\"sage\",\"body\":\"$PostMsg\",\"updated\":1722173726358,\"posted\":0,\"title\":\"\",\"sourceUrl\":null,\"oekaki\":null,\"confirmed\":null,\"targetTitle\":\"\"}]' | su -c 'cat > /data/data/jp.co.airfront.android.a2chMate/files/postDataList.json'"

sleep 1

adb shell am start -n jp.co.airfront.android.a2chMate/jp.syoboi.a2chMate.activity.ResEditActivity\\\$Dialog -d "$CurrentThreadURL"

sleep 1

if [ "$HPMode" != "HtP" ]; then
    PW_Time=$(( LastUnix + 3298 ))

    while [ "$(date +%s)" -lt "$PW_Time" ]; do
        echo "待機中... あと $(( PW_Time - $(date +%s) )) 秒"
        sleep 0.05
    done

    md5_old=$(md5sum "$THDir/CurrentThreadNum.dat" | cut -d' ' -f1)
    curl -s -f "$CurrentDatURL" -o "$THDir/tempCurrentThreadNum.dat"
    iconv -f CP932 -t UTF-8//IGNORE "$THDir/tempCurrentThreadNum.dat" | sponge "$THDir/tempCurrentThreadNum.dat"
    md5_new=$(md5sum "$THDir/tempCurrentThreadNum.dat" | cut -d' ' -f1)
    rm -f "$THDir/tempCurrentThreadNum.dat"

    if [ "$md5_old" = "$md5_new" ]; then
        :
    else
        echo setumei >> "$THDir/setumei"
        exit 0
    fi
fi

adb shell input keyevent 61 66
echo pass >> "$THDir/pass"

PT_StTime=$(date +%s)

while true; do
    if ! adb shell dumpsys window | grep "mCurrentFocus" | grep -q "jp.co.airfront.android.a2chMate"; then
        MODE="PASS"
        break
    fi

    if [ $(( $(date +%s) - PT_StTime )) -ge 13 ]; then
        MODE="RETRY"
        break
    fi

    sleep 1
done

if [ "$MODE" = "PASS" ]; then
    echo "完了：次へ進みます"
else
    adb shell input keyevent 260 66

    PT_StTime=$(date +%s)

    while true; do
        if ! adb shell dumpsys window | grep "mCurrentFocus" | grep -q "jp.co.airfront.android.a2chMate"; then
            :
            break
        else
            echo tt
        fi

        if [ $(( $(date +%s) - PT_StTime )) -ge 13 ]; then
            :
            break
        fi

        sleep 1
    done
fi

curl -s -f "$CurrentDatURL" -o "$THDir/tempCurrentThreadNum.dat"
iconv -f CP932 -t UTF-8//IGNORE "$THDir/tempCurrentThreadNum.dat" | sponge "$THDir/tempCurrentThreadNum.dat"
md5_new=$(md5sum "$THDir/tempCurrentThreadNum.dat" | cut -d' ' -f1)
md5_old=$(md5sum "$THDir/CurrentThreadNum.dat" | cut -d' ' -f1)
rm -f "$THDir/tempCurrentThreadNum.dat"

if [ "$md5_old" = "$md5_new" ]; then
    :
else
    echo kikikanri-
    echo "p01" > "$THDir/SState"
    adb shell input keyevent 26

    if [ "$HPMode" = "HtP" ]; then
        echo "$CurrentThreadNum" > "$THDir/LHtPNum"
    fi

    exit 0
fi

if [ -z "$PT_StTime2" ]; then
    export PT_StTime2=$(date +%s)
fi

if [ "$md5_old" != "$md5_new" ]; then
    echo kikikanri-
    adb shell input keyevent 26

    if [ "$HPMode" = "HtP" ]; then
        echo "$CurrentThreadNum" > "$THDir/LHtPNum"
    fi

    exit 0
fi

if [ $(( $(date +%s) - PT_StTime2 )) -ge 300 ]; then
    echo taimuauto-
    adb shell input keyevent 26
    exit 1
fi

exec "$0" tmp

;;

esac
