#!/usr/bin/env python3
"""post-push hook: CI monitoring + optional deploy script + health check.

Works out of the box with zero configuration — just monitors CI. Add
``.autobuilder/deploy.json`` to enable deployment features.

Copy (or symlink) this file to your project's
``.autobuilder/hooks/post-push.py``.

Environment variables (set automatically by the autobuilder daemon):
    AUTOBUILDER_PROJECT_DIR   — absolute path to the project
    AUTOBUILDER_PROJECT_ID    — project identifier
    AUTOBUILDER_PORT          — assigned port (from config.json)
    AUTOBUILDER_PUSH_BRANCH   — branch that was just pushed
    AUTOBUILDER_EXECUTOR      — "cursor" or "codex"
    AUTOBUILDER_MODEL         — model name (may be empty)

Exit codes:
    0 — CI passed and deploy succeeded (or nothing configured)
    1 — Unrecoverable failure (CI broken, deploy broken after retries)

Behaviour with no deploy.json:
    - Monitors GitHub Actions CI if workflows exist
    - Spawns fix agents on CI failure
    - No deployment step

Behaviour with deploy.json:
    - All of the above, plus:
    - Runs a user-provided deploy script (if found)
    - Health-checks the deploy URL (if deploy_domain is set)
    - Spawns fix agents on deploy failure (if server access is configured)

deploy.json example (all keys optional)::

    {
        "deploy_domain": "example.com",
        "server_host": "1.2.3.4",
        "server_user": "deploy",
        "ssh_key": "~/.ssh/id_ed25519",
        "app_dir": "~/apps/my-app",
        "deploy_script": null,
        "server_app_name": null,
        "max_ci_fix_attempts": 3,
        "max_deploy_fix_attempts": 2,
        "ci_timeout": 600,
        "ci_poll_interval": 15
    }
"""

from __future__ import annotations

import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Any, Dict

from autobuilder_hook_helpers.ci import wait_for_ci
from autobuilder_hook_helpers.deploy import health_check, run_deploy_script, store_deploy_url
from autobuilder_hook_helpers.fix_agent import (
    build_ci_fix_prompt,
    build_deploy_fix_prompt,
    run_fix_agent,
)
from autobuilder_hook_helpers.github import (
    get_ci_failure_logs,
    get_github_remote,
    get_head_sha,
    has_github_workflows,
)

DEFAULTS: Dict[str, Any] = {
    "deploy_domain": "",
    "server_host": "",
    "server_user": "",
    "ssh_key": "",
    "app_dir": "",
    "deploy_script": None,
    "server_app_name": None,
    "max_ci_fix_attempts": 3,
    "max_deploy_fix_attempts": 2,
    "ci_timeout": 600,
    "ci_poll_interval": 15,
}


def _load_config(project_dir: Path) -> Dict[str, Any]:
    cfg_file = project_dir / ".autobuilder" / "deploy.json"
    cfg = dict(DEFAULTS)
    if cfg_file.is_file():
        try:
            user_cfg = json.loads(cfg_file.read_text(encoding="utf-8"))
            cfg.update(user_cfg)
        except Exception as exc:
            _log(f"WARNING: could not parse {cfg_file}: {exc}")
    return cfg


def _log(msg: str) -> None:
    print(msg, file=sys.stderr, flush=True)


def _push_local_commits(project_dir: Path, branch: str) -> bool:
    """Push any local commits (from fix agents) to origin."""
    try:
        result = subprocess.run(
            ["git", "rev-list", "--count", f"origin/{branch}..HEAD"],
            cwd=str(project_dir), capture_output=True, text=True, timeout=15,
        )
        if result.returncode != 0 or int(result.stdout.strip()) == 0:
            return True
    except Exception:
        return True

    _log("Pushing fix-agent commits...")
    result = subprocess.run(
        ["git", "push", "origin", branch],
        cwd=str(project_dir), capture_output=True, text=True, timeout=120,
    )
    if result.returncode == 0:
        _log("Push succeeded.")
        return True
    _log(f"Push failed: {result.stderr.strip()}")
    return False


def _run_ci_pipeline(
    project_dir: Path,
    owner: str,
    repo: str,
    branch: str,
    cfg: Dict[str, Any],
    executor: str,
    model: str,
) -> bool:
    """Wait for CI, fix on failure, return True if CI passes."""
    max_attempts = cfg["max_ci_fix_attempts"]

    for attempt in range(1, max_attempts + 1):
        sha = get_head_sha(project_dir, branch=branch)
        if not sha:
            _log("Could not determine HEAD sha — skipping CI wait.")
            return True

        ci_ok, run_id = wait_for_ci(
            project_dir, owner, repo, sha,
            branch=branch,
            timeout=cfg["ci_timeout"],
            poll_interval=cfg["ci_poll_interval"],
            log=_log,
        )
        if ci_ok:
            return True

        if attempt >= max_attempts:
            _log(f"CI still failing after {max_attempts} fix attempts.")
            return False

        ci_logs = get_ci_failure_logs(project_dir, owner, repo, run_id) if run_id else ""
        if not ci_logs:
            _log("No CI logs available — cannot auto-fix.")
            return False

        _log(f"CI failed (attempt {attempt}/{max_attempts}) — spawning fix agent...")
        prompt = build_ci_fix_prompt(ci_logs)
        run_fix_agent(
            project_dir, prompt,
            executor=executor, model=model, log=_log,
        )

        if not _push_local_commits(project_dir, branch):
            _log("Could not push fix-agent commits.")
            return False

    return False


def _run_deploy_pipeline(
    project_dir: Path,
    project_id: str,
    port: int,
    cfg: Dict[str, Any],
    executor: str,
    model: str,
) -> bool:
    """Run optional deploy script with fix-agent retries. Return True on success."""
    max_attempts = cfg["max_deploy_fix_attempts"]
    deploy_domain = cfg["deploy_domain"]
    if not deploy_domain:
        return True
    deploy_url = f"https://{project_id}.{deploy_domain}"

    for attempt in range(1, max_attempts + 2):
        deploy_ok, deploy_output = run_deploy_script(
            project_dir, project_id, port,
            deploy_script=cfg.get("deploy_script"),
            server_app_name=cfg.get("server_app_name"),
            log=_log,
        )
        if deploy_ok:
            if not health_check(deploy_url, log=_log):
                _log(f"WARNING: {deploy_url} failed health check after deploy.")
                return False
            store_deploy_url(project_dir, deploy_url)
            _log(f"Deployment succeeded: {deploy_url}")
            return True

        if attempt > max_attempts:
            _log(f"Deploy failed after {max_attempts} fix attempts.")
            return False

        _log(f"Deploy failed (attempt {attempt}) — spawning fix agent...")
        prompt = build_deploy_fix_prompt(
            deploy_output,
            project_id=project_id,
            port=port,
            server_host=cfg.get("server_host", ""),
            server_user=cfg.get("server_user", ""),
            deploy_domain=deploy_domain,
            ssh_key=cfg.get("ssh_key", ""),
            app_dir=cfg.get("app_dir", ""),
        )
        run_fix_agent(
            project_dir, prompt,
            executor=executor, model=model, log=_log,
        )

    return False


def main() -> int:
    project_dir = Path(os.environ.get("AUTOBUILDER_PROJECT_DIR", ".")).resolve()
    project_id = os.environ.get("AUTOBUILDER_PROJECT_ID", project_dir.name)
    port = int(os.environ.get("AUTOBUILDER_PORT", "0"))
    branch = os.environ.get("AUTOBUILDER_PUSH_BRANCH", "main")
    executor = os.environ.get("AUTOBUILDER_EXECUTOR", "cursor")
    model = os.environ.get("AUTOBUILDER_MODEL", "")

    cfg = _load_config(project_dir)

    # --- CI ---
    remote = get_github_remote(project_dir)
    if remote:
        owner, repo = remote
        _log(f"GitHub remote: {owner}/{repo}")

        has_ci = has_github_workflows(project_dir, owner, repo)
        if has_ci:
            ci_ok = _run_ci_pipeline(
                project_dir, owner, repo, branch, cfg, executor, model,
            )
            if not ci_ok:
                print(
                    f"CI pipeline failed for {owner}/{repo} after "
                    f"{cfg['max_ci_fix_attempts']} fix attempts.",
                    flush=True,
                )
                return 1
    else:
        _log("No GitHub remote — skipping CI.")

    # --- Deploy (only when configured) ---
    if cfg["deploy_domain"] and port:
        deploy_ok = _run_deploy_pipeline(
            project_dir, project_id, port, cfg, executor, model,
        )
        if not deploy_ok:
            print(
                f"Deploy failed for {project_id} after "
                f"{cfg['max_deploy_fix_attempts']} fix attempts.",
                flush=True,
            )
            return 1

    return 0


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