#!/usr/bin/env python3
"""Probe current GCP DB parity readiness without mutating GCP.

The probe is intentionally read-only. It copies the local gcloud config into a
temporary writable directory so sandboxed agents can run gcloud without writing
under ~/.config/gcloud, redirects access-token output out of artifacts, and
keeps GCP parity separate from VPS proof.
"""

from __future__ import annotations

import argparse
import configparser
import json
import os
import shutil
import subprocess
import tempfile
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

REPORT_DIR = Path("docs/reports/leo-working-state-20260709")
DEFAULT_OUT = REPORT_DIR / "gcp-db-parity-current-probe-current.json"
DEFAULT_MARKDOWN_OUT = REPORT_DIR / "gcp-db-parity-current-probe-current.md"
DEFAULT_GCLOUD = Path("/opt/homebrew/bin/gcloud")
DEFAULT_SOURCE_CONFIG = Path.home() / ".config" / "gcloud"
DEFAULT_PROJECT = "teleo-501523"
DEFAULT_INSTANCE = "teleo-prod-1"
DEFAULT_ZONE = "europe-west6-a"
EXPECTED_ACCESS_ACCOUNT = "billy@livingip.xyz"


@dataclass(frozen=True)
class CommandResult:
    returncode: int
    stdout: str
    stderr: str


Runner = Callable[[list[str], dict[str, str], int], CommandResult]


def subprocess_runner(command: list[str], env: dict[str, str], timeout: int) -> CommandResult:
    proc = subprocess.run(command, text=True, capture_output=True, env=env, timeout=timeout, check=False)
    return CommandResult(proc.returncode, proc.stdout, proc.stderr)


def _json_or_none(text: str) -> Any:
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        return None


def _excerpt(text: str, limit: int = 1200) -> str:
    text = text.strip()
    if len(text) <= limit:
        return text
    return text[:limit] + "...[truncated]"


def _run(
    command: list[str],
    *,
    env: dict[str, str],
    runner: Runner,
    timeout: int,
    redact_stdout: bool = False,
) -> dict[str, Any]:
    try:
        result = runner(command, env, timeout)
        stdout = "" if redact_stdout else result.stdout
        return {
            "command": redact_command(command),
            "returncode": result.returncode,
            "ok": result.returncode == 0,
            "stdout_redacted": redact_stdout,
            "stdout_excerpt": _excerpt(stdout) if stdout else "",
            "stderr_excerpt": _excerpt(result.stderr),
            "stdout_json": None if redact_stdout else _json_or_none(result.stdout),
        }
    except FileNotFoundError as exc:
        return {
            "command": redact_command(command),
            "returncode": 127,
            "ok": False,
            "stdout_redacted": redact_stdout,
            "stdout_excerpt": "",
            "stderr_excerpt": str(exc),
            "stdout_json": None,
        }
    except subprocess.TimeoutExpired as exc:
        return {
            "command": redact_command(command),
            "returncode": 124,
            "ok": False,
            "stdout_redacted": redact_stdout,
            "stdout_excerpt": "",
            "stderr_excerpt": f"timed out after {exc.timeout}s",
            "stdout_json": None,
        }


def redact_command(command: list[str]) -> list[str]:
    redacted: list[str] = []
    for part in command:
        if part.startswith("--account="):
            redacted.append(part)
        else:
            redacted.append(part)
    return redacted


def copy_gcloud_config(source: Path) -> tuple[tempfile.TemporaryDirectory[str] | None, Path, bool]:
    if not source.exists():
        return None, source, False
    tmp = tempfile.TemporaryDirectory(prefix="teleo-gcp-parity-", dir="/private/tmp")
    target = Path(tmp.name)

    def ignore(_: str, names: list[str]) -> set[str]:
        return {"logs"} & set(names)

    shutil.copytree(source, target, dirs_exist_ok=True, ignore=ignore)
    return tmp, target, True


def read_static_config(config_dir: Path) -> dict[str, Any]:
    active_name = "default"
    active_file = config_dir / "active_config"
    if active_file.exists():
        active_name = active_file.read_text(encoding="utf-8").strip() or "default"
    config_file = config_dir / "configurations" / f"config_{active_name}"
    parser = configparser.ConfigParser()
    parser.read(config_file)
    core = parser["core"] if parser.has_section("core") else {}
    return {
        "active_config": active_name,
        "config_file": str(config_file),
        "account": core.get("account"),
        "project": core.get("project"),
    }


def accounts_from_auth_list(auth_list: dict[str, Any], fallback_account: str | None) -> list[str]:
    parsed = auth_list.get("stdout_json")
    accounts: list[str] = []
    if isinstance(parsed, list):
        for item in parsed:
            if isinstance(item, dict) and item.get("account"):
                accounts.append(str(item["account"]))
    if fallback_account and fallback_account not in accounts:
        accounts.insert(0, fallback_account)
    return accounts


def flatten_command_results(results: list[dict[str, Any]]) -> list[dict[str, Any]]:
    flattened: list[dict[str, Any]] = []
    for result in results:
        if "stderr_excerpt" in result:
            flattened.append(result)
            continue
        for value in result.values():
            if isinstance(value, dict):
                flattened.extend(flatten_command_results([value]))
            elif isinstance(value, list):
                flattened.extend(flatten_command_results([item for item in value if isinstance(item, dict)]))
    return flattened


def has_network_dns_error(results: list[dict[str, Any]]) -> bool:
    needle_set = (
        "failed to resolve",
        "nameresolutionerror",
        "nodename nor servname",
        "name or service not known",
        "temporary failure in name resolution",
        "could not resolve host",
    )
    return any(
        any(needle in item.get("stderr_excerpt", "").lower() for needle in needle_set)
        for item in flatten_command_results(results)
    )


def has_reauth_error(results: list[dict[str, Any]]) -> bool:
    needle_set = ("reauthentication failed", "cannot prompt during non-interactive execution", "gcloud auth login")
    return any(
        any(needle in item.get("stderr_excerpt", "").lower() for needle in needle_set)
        for item in flatten_command_results(results)
    )


def classify_failure(results: list[dict[str, Any]]) -> str | None:
    if has_network_dns_error(results):
        return "dns_unavailable"
    if has_reauth_error(results):
        return "reauth_required"
    return None


def summarize_project(project_result: dict[str, Any]) -> dict[str, Any] | None:
    parsed = project_result.get("stdout_json")
    if not isinstance(parsed, dict):
        return None
    return {
        "projectId": parsed.get("projectId"),
        "projectNumber": parsed.get("projectNumber"),
        "lifecycleState": parsed.get("lifecycleState"),
    }


def summarize_instance(instance_result: dict[str, Any]) -> dict[str, Any] | None:
    parsed = instance_result.get("stdout_json")
    if not isinstance(parsed, dict):
        return None
    return {
        "name": parsed.get("name"),
        "status": parsed.get("status"),
        "zone": parsed.get("zone"),
        "machineType": parsed.get("machineType"),
        "serviceAccounts": [
            {"email": item.get("email"), "scopes": item.get("scopes", [])}
            for item in parsed.get("serviceAccounts", [])
            if isinstance(item, dict)
        ],
    }


def summarize_sql_instances(sql_result: dict[str, Any]) -> list[dict[str, Any]]:
    parsed = sql_result.get("stdout_json")
    if not isinstance(parsed, list):
        return []
    summary: list[dict[str, Any]] = []
    for item in parsed:
        if isinstance(item, dict):
            summary.append(
                {
                    "name": item.get("name"),
                    "state": item.get("state"),
                    "databaseVersion": item.get("databaseVersion"),
                    "region": item.get("region"),
                }
            )
    return summary


def status_from_results(
    *,
    selected_account: str | None,
    expected_account: str,
    project: str | None,
    expected_project: str,
    token_checks: list[dict[str, Any]],
    project_readback: dict[str, Any],
    instance_readback: dict[str, Any] | None,
    sql_readback: dict[str, Any] | None,
) -> str:
    command_results = [*token_checks, project_readback]
    if instance_readback is not None:
        command_results.append(instance_readback)
    if sql_readback is not None:
        command_results.append(sql_readback)
    if classify_failure(command_results) == "dns_unavailable":
        return "blocked_on_network_dns_before_auth_refresh"
    if selected_account != expected_account:
        return "blocked_on_wrong_active_account"
    if project != expected_project:
        return "blocked_on_wrong_active_project"
    selected_token = next((item for item in token_checks if item["account"] == selected_account), None)
    if selected_token and not selected_token["token_refresh"]["ok"]:
        classification = classify_failure([selected_token])
        return "blocked_on_gcloud_reauth" if classification == "reauth_required" else "blocked_on_token_refresh"
    if not project_readback["ok"]:
        return "blocked_on_project_access"
    if instance_readback is not None and not instance_readback["ok"]:
        return "blocked_on_gce_instance_readback"
    if sql_readback is not None and not sql_readback["ok"]:
        return "blocked_on_cloudsql_readback"
    return "ready_for_gcp_readonly_parity"


def probe_gcp_parity(
    *,
    gcloud_path: Path = DEFAULT_GCLOUD,
    source_config: Path = DEFAULT_SOURCE_CONFIG,
    expected_project: str = DEFAULT_PROJECT,
    expected_account: str = EXPECTED_ACCESS_ACCOUNT,
    instance: str = DEFAULT_INSTANCE,
    zone: str = DEFAULT_ZONE,
    runner: Runner = subprocess_runner,
    use_temp_config: bool = True,
    timeout: int = 30,
) -> dict[str, Any]:
    temp_dir: tempfile.TemporaryDirectory[str] | None = None
    temp_config_created = False
    config_dir = source_config
    try:
        if use_temp_config:
            temp_dir, config_dir, temp_config_created = copy_gcloud_config(source_config)
        static_config = read_static_config(config_dir)
        env = os.environ.copy()
        env["CLOUDSDK_CONFIG"] = str(config_dir)
        env["CLOUDSDK_CORE_DISABLE_PROMPTS"] = "1"

        config_list = _run(
            [str(gcloud_path), "config", "list", "--format=json"],
            env=env,
            runner=runner,
            timeout=timeout,
        )
        auth_list = _run(
            [str(gcloud_path), "auth", "list", "--format=json"],
            env=env,
            runner=runner,
            timeout=timeout,
        )
        accounts = accounts_from_auth_list(auth_list, static_config.get("account"))
        token_checks = []
        for account in accounts:
            token_checks.append(
                {
                    "account": account,
                    "token_refresh": _run(
                        [str(gcloud_path), "auth", "print-access-token", f"--account={account}"],
                        env=env,
                        runner=runner,
                        timeout=timeout,
                        redact_stdout=True,
                    ),
                }
            )

        project_readback = _run(
            [str(gcloud_path), "projects", "describe", expected_project, "--format=json"],
            env=env,
            runner=runner,
            timeout=timeout,
        )

        instance_readback: dict[str, Any] | None = None
        sql_readback: dict[str, Any] | None = None
        if project_readback["ok"]:
            instance_readback = _run(
                [
                    str(gcloud_path),
                    "compute",
                    "instances",
                    "describe",
                    instance,
                    "--zone",
                    zone,
                    "--project",
                    expected_project,
                    "--format=json",
                ],
                env=env,
                runner=runner,
                timeout=timeout,
            )
            sql_readback = _run(
                [str(gcloud_path), "sql", "instances", "list", "--project", expected_project, "--format=json"],
                env=env,
                runner=runner,
                timeout=timeout,
            )

        status = status_from_results(
            selected_account=static_config.get("account"),
            expected_account=expected_account,
            project=static_config.get("project"),
            expected_project=expected_project,
            token_checks=token_checks,
            project_readback=project_readback,
            instance_readback=instance_readback,
            sql_readback=sql_readback,
        )
        ready = status == "ready_for_gcp_readonly_parity"
        clear_cta = (
            "Run this probe from a network-enabled shell where oauth2.googleapis.com resolves, with "
            f"`{expected_account}` authenticated for project `{expected_project}`. Then rerun "
            "`python3 scripts/probe_gcp_db_parity.py` and proceed only if it reports "
            "`ready_for_gcp_readonly_parity`."
        )
        if status == "blocked_on_gcloud_reauth":
            clear_cta = (
                f"In a local terminal, run `gcloud auth login {expected_account}` and complete Google "
                f"reauth for project `{expected_project}`. Then rerun "
                "`python3 scripts/probe_gcp_db_parity.py`."
            )
        elif status == "blocked_on_wrong_active_account":
            clear_cta = (
                f"Run `gcloud config set account {expected_account}` in the active gcloud config, then rerun "
                "`python3 scripts/probe_gcp_db_parity.py`."
            )
        elif status == "blocked_on_project_access":
            clear_cta = (
                f"Grant `{static_config.get('account')}` read access to project `{expected_project}` or switch "
                "to an account that already has it, then rerun `python3 scripts/probe_gcp_db_parity.py`."
            )

        return {
            "generated_at_utc": datetime.now(timezone.utc).isoformat(),
            "artifact": "gcp_db_parity_current_probe",
            "mode": "read_only_gcloud_parity_probe",
            "status": status,
            "ready_for_gcp_readonly_parity": ready,
            "mutates_gcp": False,
            "mutates_db": False,
            "current_canary": "gcloud account can read teleo-501523 project metadata plus target VM/Cloud SQL metadata",
            "expected": {
                "project": expected_project,
                "project_access_account": expected_account,
                "gce_instance": instance,
                "gce_zone": zone,
            },
            "local_gcloud": {
                "gcloud_path": str(gcloud_path),
                "source_config": str(source_config),
                "used_temporary_writable_config_copy": temp_config_created,
                "temporary_config_removed_after_probe": True,
                "static_active_config": static_config,
                "config_list": config_list,
                "auth_list": auth_list,
            },
            "accounts_tested": token_checks,
            "project_readback": {
                **project_readback,
                "summary": summarize_project(project_readback),
            },
            "runtime_readbacks": {
                "gce_instance": None
                if instance_readback is None
                else {**instance_readback, "summary": summarize_instance(instance_readback)},
                "cloudsql_instances": None
                if sql_readback is None
                else {**sql_readback, "summary": summarize_sql_instances(sql_readback)},
            },
            "attempted_routes": [
                "static active_config/config_default readback",
                "temporary writable copy of local gcloud config to avoid sandbox writes under ~/.config/gcloud",
                "gcloud auth list without printing secrets",
                "gcloud auth print-access-token for configured accounts with stdout redacted",
                "gcloud projects describe teleo-501523",
                "if project readback works: gcloud compute instances describe teleo-prod-1",
                "if project readback works: gcloud sql instances list",
            ],
            "exact_gate": status,
            "clear_CTA": clear_cta,
            "next_non_user_action": [
                "Run this same probe again and require ready_for_gcp_readonly_parity=true.",
                "SSH into teleo-prod-1 or use Cloud SQL read-only access for DB/runtime parity proof.",
                "Run the Working Leo benchmark against GCP only after read-only GCP metadata is proven current.",
            ],
            "claim_ceiling": (
                "This probe proves only current local gcloud/GCP read-only parity readiness. It does not prove "
                "VPS behavior, Telegram-visible behavior, production DB mutation, or GCP-hosted Leo correctness."
            ),
        }
    finally:
        if temp_dir is not None:
            temp_dir.cleanup()


def write_json(path: Path, data: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8")


def write_markdown(path: Path, data: dict[str, Any]) -> None:
    lines = [
        "# GCP DB Parity Current Probe",
        "",
        f"Generated UTC: `{data['generated_at_utc']}`",
        f"Status: `{data['status']}`",
        f"Ready for GCP read-only parity: `{data['ready_for_gcp_readonly_parity']}`",
        f"Mutates GCP: `{data['mutates_gcp']}`",
        f"Mutates DB: `{data['mutates_db']}`",
        f"Current canary: {data['current_canary']}",
        "",
        "## Active Config",
        "",
        f"- Account: `{data['local_gcloud']['static_active_config'].get('account')}`",
        f"- Project: `{data['local_gcloud']['static_active_config'].get('project')}`",
        f"- Temporary writable config copy: `{data['local_gcloud']['used_temporary_writable_config_copy']}`",
        f"- Temporary config removed after probe: `{data['local_gcloud']['temporary_config_removed_after_probe']}`",
        "",
        "## Account Checks",
        "",
    ]
    for item in data["accounts_tested"]:
        token = item["token_refresh"]
        lines.append(
            f"- `{item['account']}`: `ok={token['ok']}`, `returncode={token['returncode']}`, "
            f"`stdout_redacted={token['stdout_redacted']}`"
        )
        if token["stderr_excerpt"]:
            lines.append(f"  - stderr: `{token['stderr_excerpt']}`")
    project = data["project_readback"]
    lines.extend(
        [
            "",
            "## Project Readback",
            "",
            f"- `ok={project['ok']}`, `returncode={project['returncode']}`",
        ]
    )
    if project.get("summary"):
        lines.append(f"- Summary: `{project['summary']}`")
    if project["stderr_excerpt"]:
        lines.append(f"- stderr: `{project['stderr_excerpt']}`")
    lines.extend(["", "## Gate", "", f"- Exact gate: `{data['exact_gate']}`", f"- Clear CTA: {data['clear_CTA']}"])
    lines.extend(["", "## Next Non-User Action", ""])
    lines.extend(f"- {item}" for item in data["next_non_user_action"])
    lines.extend(["", "## Claim Ceiling", "", data["claim_ceiling"], ""])
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text("\n".join(lines), encoding="utf-8")


def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--gcloud", type=Path, default=DEFAULT_GCLOUD)
    parser.add_argument("--source-config", type=Path, default=DEFAULT_SOURCE_CONFIG)
    parser.add_argument("--project", default=DEFAULT_PROJECT)
    parser.add_argument("--expected-account", default=EXPECTED_ACCESS_ACCOUNT)
    parser.add_argument("--instance", default=DEFAULT_INSTANCE)
    parser.add_argument("--zone", default=DEFAULT_ZONE)
    parser.add_argument("--timeout", type=int, default=30)
    parser.add_argument("--no-temp-config-copy", action="store_true")
    parser.add_argument("--out", type=Path, default=DEFAULT_OUT)
    parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MARKDOWN_OUT)
    return parser.parse_args(argv)


def main(argv: list[str] | None = None) -> int:
    args = parse_args(argv)
    data = probe_gcp_parity(
        gcloud_path=args.gcloud,
        source_config=args.source_config,
        expected_project=args.project,
        expected_account=args.expected_account,
        instance=args.instance,
        zone=args.zone,
        use_temp_config=not args.no_temp_config_copy,
        timeout=args.timeout,
    )
    write_json(args.out, data)
    write_markdown(args.markdown_out, data)
    print(
        json.dumps(
            {
                "out": str(args.out),
                "markdown_out": str(args.markdown_out),
                "status": data["status"],
                "ready_for_gcp_readonly_parity": data["ready_for_gcp_readonly_parity"],
                "mutates_gcp": data["mutates_gcp"],
                "mutates_db": data["mutates_db"],
            },
            indent=2,
            sort_keys=True,
        )
    )
    return 0 if data["ready_for_gcp_readonly_parity"] else 2


if __name__ == "__main__":
    raise SystemExit(main())
