#!/usr/bin/env python3
"""Install committed native CocoaPods dependencies through the existing account relay."""
from __future__ import annotations

import argparse
import hashlib
import json
import os
from pathlib import Path
import signal
import subprocess
import sys
import tempfile

import native_dependency_guard as guard
import native_bundle
import native_pods_retry
from native_bundle import command
from native_pods_diagnostics import refuse
from native_prepare_evidence import report


def committed(root: Path, lock: Path) -> str:
    if not lock.is_file() or lock.is_symlink():
        raise guard.Refused("native_podfile_lock_required")
    result = subprocess.run(["git", "show", "HEAD:" + lock.relative_to(root).as_posix()],
                            cwd=root, capture_output=True, check=False, timeout=15)
    if result.returncode or result.stdout != lock.read_bytes():
        raise guard.Refused("native_podfile_lock_commit_required")
    return hashlib.sha256(result.stdout).hexdigest()


def prepare(root: Path, container: str) -> dict:
    root = root.resolve(strict=True)
    folder = (root / container).parent.resolve(strict=True) if container else root
    if not folder.is_relative_to(root):
        raise guard.Refused("native_workspace_reference_unverified")
    if not (folder / "Podfile").is_file():
        return {"ok": True, "pods": "not_configured"}
    if (folder / "Podfile").is_symlink():
        raise guard.Refused("native_workspace_reference_unverified")
    digest = committed(root, folder / "Podfile.lock")
    with tempfile.TemporaryFile() as log:
        if not list(folder.glob("*.xcodeproj")) and (folder / "project.yml").is_file():
            if command(["xcodegen", "generate"], folder, log):
                refuse("native_project_generation_failed", log, guard.Refused)
        initial = container or str(guard.selected(root, "", True).relative_to(root))
        guard.check(root, initial, before_pods=True)
        prefix, bundle_snapshot = native_bundle.prepare(root, folder, log)
        snapshot = {**bundle_snapshot, folder / "Podfile.lock": digest,
                    folder / "Podfile": hashlib.sha256((folder / "Podfile").read_bytes()).hexdigest()}
        code, attempts = native_pods_retry.install(
            [*prefix, "pod", "install", "--deployment", "--no-repo-update"],
            folder, log, snapshot)
        if code:
            refuse("native_pods_install_failed", log, guard.Refused)
        native_bundle.unchanged(bundle_snapshot)
        if digest != hashlib.sha256((folder / "Podfile.lock").read_bytes()).hexdigest():
            raise guard.Refused("native_podfile_lock_changed")
        workspaces = sorted(folder.glob("*.xcworkspace"))
        final = container if container.endswith(".xcworkspace") else ""
        if not final:
            if len(workspaces) != 1:
                raise guard.Refused("native_project_selection_required")
            final = str(workspaces[0].relative_to(root))
        result = guard.check(root, final)
    return {**result, "pods": "installed", "pod_attempts": attempts, "lock_sha256": digest, "workspace": final}


def interrupted(*_args) -> None:
    raise KeyboardInterrupt


def main(argv: list[str]) -> int:
    signal.signal(signal.SIGTERM, interrupted)
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("root", type=Path)
    parser.add_argument("--container", default="")
    args = parser.parse_args(argv)
    try:
        answer = prepare(args.root, args.container)
    except (guard.Refused, OSError, subprocess.SubprocessError) as exc:
        code = str(exc) if isinstance(exc, guard.Refused) else "native_pods_operation_unverified"
        answer = {"ok": False, "code": code, "next": guard.REMEDIATION}
        if getattr(exc, "diagnostic", ""):
            answer["diagnostic"] = exc.diagnostic
    report(answer, "pods")
    print(json.dumps(answer, sort_keys=True))
    return 0 if answer["ok"] else 1


if __name__ == "__main__":
    raise SystemExit(main(sys.argv[1:]))
