#!/usr/bin/env python3
"""
AutoJanet Agent Entrypoint

Bootstraps an agent container:
1. Authenticates to OpenBao via AppRole (OPENBAO_ROLE_ID + OPENBAO_SECRET_ID)
2. Fetches all secrets for AGENT_ROLE from OpenBao
3. Writes an opencode-compatible ~/.config/opencode/ environment
4. Runs opencode non-interactively with the task as the prompt

Environment variables (injected by dispatcher Job):
  AGENT_ROLE        — e.g. "coder"
  TASK_ID           — Vikunja task ID
  TASK_TITLE        — Vikunja task title (used as initial prompt)
  OPENBAO_ADDR      — e.g. "http://openbao.openbao.svc.cluster.local:8200"
  OPENBAO_ROLE_ID   — AppRole role_id
  OPENBAO_SECRET_ID — AppRole secret_id
  LITELLM_BASE_URL  — e.g. "https://llm.ctz.fyi"
  VIKUNJA_BASE_URL  — e.g. "https://tasks.ctz.fyi"
  FORGEJO_BASE_URL  — e.g. "https://git.ctz.fyi"
"""

import json
import logging
import os
import subprocess
import sys
import tempfile
from pathlib import Path

import httpx

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
    stream=sys.stdout,
)
log = logging.getLogger("entrypoint")

OPENBAO_ADDR = os.environ["OPENBAO_ADDR"]
OPENBAO_ROLE_ID = os.environ["OPENBAO_ROLE_ID"]
OPENBAO_SECRET_ID = os.environ["OPENBAO_SECRET_ID"]
AGENT_ROLE = os.environ["AGENT_ROLE"]
TASK_ID = os.environ["TASK_ID"]
TASK_TITLE = os.environ.get("TASK_TITLE", f"Task {TASK_ID}")
LITELLM_BASE_URL = os.environ.get("LITELLM_BASE_URL", "https://llm.ctz.fyi")
VIKUNJA_BASE_URL = os.environ.get("VIKUNJA_BASE_URL", "https://tasks.ctz.fyi")
FORGEJO_BASE_URL = os.environ.get("FORGEJO_BASE_URL", "https://git.ctz.fyi")
IN_REVIEW_BUCKET_ID = os.environ.get("IN_REVIEW_BUCKET_ID", "")
VIKUNJA_PROJECT_ID = os.environ.get("VIKUNJA_PROJECT_ID", "")
VIKUNJA_VIEW_ID = os.environ.get("VIKUNJA_VIEW_ID", "")

HOME = Path(os.environ.get("HOME", "/home/agent"))
CONFIG_DIR = HOME / ".config" / "opencode"


def get_openbao_token() -> str:
    resp = httpx.post(
        f"{OPENBAO_ADDR}/v1/auth/approle/login",
        json={"role_id": OPENBAO_ROLE_ID, "secret_id": OPENBAO_SECRET_ID},
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()["auth"]["client_token"]


def get_secret(bao_token: str, path: str, key: str) -> str:
    resp = httpx.get(
        f"{OPENBAO_ADDR}/v1/secret/data/{path}",
        headers={"X-Vault-Token": bao_token},
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()["data"]["data"][key]


def fetch_role_secrets(bao_token: str, role: str) -> dict:
    """Fetch all secrets for a role. Returns dict of secret_name -> value."""
    secrets = {}
    try:
        resp = httpx.get(
            f"{OPENBAO_ADDR}/v1/secret/data/autojanet/{role}/litellm-key",
            headers={"X-Vault-Token": bao_token},
            timeout=10,
        )
        resp.raise_for_status()
        secrets["litellm-key"] = resp.json()["data"]["data"]["key"]
        log.info("Fetched litellm-key")
    except Exception as e:
        log.warning("Could not fetch litellm-key: %s", e)
    return secrets


def write_opencode_config(secrets: dict, role: str) -> None:
    """Write opencode config and set secrets as env vars for opencode to pick up."""
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)

    litellm_key = secrets.get("litellm-key", "")

    config = {
        "$schema": "https://opencode.ai/config.json",
        "model": "litellm/copilot/claude-sonnet-4.6",
        "provider": {
            "litellm": {
                "npm": "@ai-sdk/openai-compatible",
                "name": "LiteLLM",
                "options": {
                    "baseURL": f"{LITELLM_BASE_URL}/v1",
                    "apiKey": litellm_key,
                },
                "models": {
                    "copilot/claude-sonnet-4.6": {"name": "copilot/claude-sonnet-4.6"},
                },
            }
        },
        "mcp": {
            "litellm": {
                "type": "remote",
                "url": f"{LITELLM_BASE_URL}/mcp/",
                "headers": {
                    "Authorization": f"Bearer {litellm_key}",
                },
                "enabled": True,
            },
        }
    }

    config_path = CONFIG_DIR / "config.json"
    config_path.write_text(json.dumps(config, indent=2))
    log.info("Wrote opencode config to %s", config_path)

    # Write AGENTS.md with role-specific instructions
    agent_md_src = Path(f"/app/agents/{role}.agent.md")
    agents_md_dst = CONFIG_DIR / "AGENTS.md"
    if agent_md_src.exists():
        agents_md_dst.write_text(agent_md_src.read_text())
        log.info("Loaded agent instructions from %s", agent_md_src)
    else:
        log.warning("No agent file found at %s", agent_md_src)


def configure_git_identity() -> None:
    """Set git commit name to Janet, keeping the agent's service account email."""
    try:
        subprocess.run(["git", "config", "--global", "user.name", "Janet"], check=True)
        subprocess.run(["git", "config", "--global", "user.email", f"svc-ag-{AGENT_ROLE}@ad.ctz.fyi"], check=True)
        log.info("Git identity set: Janet <svc-ag-%s@ad.ctz.fyi>", AGENT_ROLE)
    except Exception as e:
        log.warning("Could not set git identity: %s", e)


def build_prompt(task_id: str, task_title: str) -> str:
    return f"""You are the AutoJanet agent for role: {AGENT_ROLE}

Your current task (Vikunja task #{task_id}):
{task_title}

Instructions:
1. Read the task carefully. Fetch full task details from Vikunja if needed.
2. Complete the task using the tools available to you.
3. If you wrote code, commit with a message that includes "refs #{task_id}" so the task
   is linked in Forgejo. Example: "feat: add login endpoint refs #{task_id}"
4. Open a PR on Forgejo — do not merge it yourself.
5. Leave a comment on the Vikunja task summarising what you did and linking any PR.
6. Do not mark the task as done — the entrypoint will move it to In Review when you finish.
7. Do not ask for confirmation — act autonomously within your role constraints.
"""


def run_opencode(prompt: str) -> int:
    """Run opencode non-interactively with the given prompt."""
    cmd = ["opencode", "run", "--print-logs", prompt]
    log.info("Running: opencode run --print-logs <prompt>")
    result = subprocess.run(cmd, check=False)
    return result.returncode


def move_task_to_bucket(pm_token: str, bucket_id: str) -> None:
    """Move the task to a bucket using the kanban view endpoint (requires pm token)."""
    if not all([bucket_id, VIKUNJA_PROJECT_ID, VIKUNJA_VIEW_ID]):
        log.warning("Missing bucket/project/view IDs, skipping bucket move")
        return
    try:
        resp = httpx.post(
            f"{VIKUNJA_BASE_URL}/api/v1/projects/{VIKUNJA_PROJECT_ID}/views/{VIKUNJA_VIEW_ID}/buckets/{bucket_id}/tasks",
            headers={"Authorization": f"Bearer {pm_token}", "Content-Type": "application/json"},
            json={"task_id": int(TASK_ID)},
            timeout=10,
        )
        resp.raise_for_status()
        log.info("Moved task %s → bucket %s", TASK_ID, bucket_id)
    except Exception as e:
        log.warning("Failed to move task to bucket %s: %s", bucket_id, e)


def main() -> None:
    log.info("Agent entrypoint: role=%s task=%s", AGENT_ROLE, TASK_ID)

    bao_token = get_openbao_token()
    log.info("OpenBao authenticated")

    secrets = fetch_role_secrets(bao_token, AGENT_ROLE)
    write_opencode_config(secrets, AGENT_ROLE)
    configure_git_identity()

    # Fetch pm token for bucket moves (admin operation)
    pm_token = ""
    try:
        pm_token = get_secret(bao_token, "autojanet/pm/vikunja-token", "token")
        log.info("Fetched pm vikunja-token")
    except Exception as e:
        log.warning("Could not fetch pm vikunja-token: %s", e)

    prompt = build_prompt(TASK_ID, TASK_TITLE)
    rc = run_opencode(prompt)

    # Move to In Review regardless of exit code — work was attempted
    if pm_token:
        move_task_to_bucket(pm_token, IN_REVIEW_BUCKET_ID)

    if rc != 0:
        log.error("opencode exited with code %d", rc)
        sys.exit(rc)

    log.info("Agent completed task %s successfully", TASK_ID)


if __name__ == "__main__":
    main()
