"""Retry one positively identified transient Pod fetch fault within the existing install deadline."""
import json
import re
import sys
import tempfile
import time

import native_bundle
import native_dependency_guard as guard
from native_pods_diagnostics import diagnostic, refuse

TERMINAL = re.compile(
    r"certificate|SSL peer|TLS|authentication|unauthorized|forbidden|permission denied|"
    r"repository not found|checksum|compatible versions|deployment mode|"
    r"returned error:\s*(?:401|403|407)\b|\bcurl(?:\s+(?:51|60)\b|:\s*\((?:51|60)\))", re.I)
PARTIAL = re.compile(r"\bcurl(?:\s+18\b|:\s*\(18\))", re.I)
# CocoaPods reports a timed-out CDN spec fetch as its own sentence and never as a curl
# code, so the partial-transfer evidence above cannot see it: "CDN: trunk URL couldn't be
# downloaded: <url> Response: Timeout was reached". A timeout leaves the successfully
# fetched specs cached, so the second attempt resumes rather than starting over.
TIMED_OUT = re.compile(r"Response:\s*Timeout was reached|\bcurl(?:\s+28\b|:\s*\(28\))", re.I)
# A handshake that dies mid-negotiation names no curl code and no verdict about the
# peer, so neither pattern above can see it: printer-ai run 34374868740 lost the iOS
# build to `LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to github.com:443`
# while CocoaPods cloned a dependency, and 2phone-ios run 34374984763 lost the same
# step to bundler's `SSL_connect returned=1 ... unexpected eof while reading`
# (2026-09-09). Both are a connection the exit dropped before either side spoke, which
# the next attempt usually completes. A CERTIFICATE verdict is a different fact and
# stays permanent above: TERMINAL still vetoes this class whenever it appears.
HANDSHAKE = re.compile(r"SSL_ERROR_SYSCALL|unexpected eof while reading", re.I)


def interrupted_transfer(body: str) -> bool:
    return bool(PARTIAL.search(body) and not TERMINAL.search(body) and any(
        text in body.lower() for text in ("early eof", "transfer closed", "bytes of body are still expected")))


def timed_out(body: str) -> bool:
    return bool(TIMED_OUT.search(body) and not TERMINAL.search(body))


def handshake_reset(body: str) -> bool:
    return bool(HANDSHAKE.search(body) and not TERMINAL.search(body))


def retryable(body: str) -> str:
    """Name the transient class this failure belongs to, or "" when it is not one."""
    if interrupted_transfer(body):
        return "partial_transfer"
    if timed_out(body):
        return "transient_timeout"
    return "handshake_reset" if handshake_reset(body) else ""


def install(argv: list[str], folder, log, snapshot: dict, *, timeout: float = 900) -> tuple[int, int]:
    deadline = time.monotonic() + timeout
    for attempt in (1, 2):
        native_bundle.unchanged(snapshot)
        remaining = deadline - time.monotonic()
        if remaining <= 0:
            refuse("native_pods_operation_interrupted", log, guard.Refused)
        with tempfile.TemporaryFile() as child_log:
            code = native_bundle.command(argv, folder, child_log, timeout=remaining)
            body = diagnostic(child_log)
        native_bundle.unchanged(snapshot)
        log.seek(0)
        log.truncate()
        log.write(body.encode())
        reason = retryable(body)
        if code == 0 or attempt == 2 or not reason:
            return code, attempt
        payload = {"schema": "gowalk-cicd/native-pods-retry.v1", "attempt": 2, "reason": reason}
        print("::notice title=native_pods_retry::" + json.dumps(payload, separators=(",", ":")),
              file=sys.stderr, flush=True)
    raise AssertionError("unreachable")
