#!/usr/bin/env python3
"""
Convierte subtareas de un parent (p.ej. SQPD-3356) desde "Subtask" a "Dev Task".

Notas:
- Cambiar issue type en Jira puede o no permitir remover automáticamente el parent.
- Este script intenta primero con cambio de issuetype y, si falla, reintenta con parent=null.
- Incluye modo dry-run para validar antes de ejecutar.
"""

import argparse
import base64
import json
import os
import sys
from typing import Any, Dict, List, Optional, Tuple

import requests

SKILL_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LEGACY_CONFIG_PATH = os.path.join(SKILL_DIR, "config.json")
NO_PROXY = {"http": None, "https": None}


def _user_config_path() -> str:
    """Return the user-writable config path (XDG-compliant)."""
    base = os.environ.get("XDG_CONFIG_HOME") or os.path.join(os.path.expanduser("~"), ".config")
    return os.path.join(base, "humand-pm-jira", "config.json")


def _load_config_from_env() -> Optional[Dict[str, Any]]:
    """Build a config dict from environment variables; return None if any required var is missing."""
    url = (
        os.environ.get("JIRA_BASE_URL")
        or os.environ.get("JIRA_URL")
        or os.environ.get("ATLASSIAN_BASE_URL")
        or ""
    ).rstrip("/")
    email = os.environ.get("JIRA_EMAIL") or os.environ.get("ATLASSIAN_EMAIL") or ""
    token = os.environ.get("JIRA_API_TOKEN") or os.environ.get("ATLASSIAN_API_TOKEN") or ""
    if url and email and token:
        return {"jira_url": url, "jira_email": email, "jira_api_token": token}
    return None


def _load_config_from_file(path: str) -> Dict[str, Any]:
    with open(path, "r", encoding="utf-8") as f:
        config = json.load(f)
    url = (config.get("jira_url") or "").rstrip("/")
    email = config.get("jira_email") or ""
    token = config.get("jira_api_token") or ""
    if not url or not email or not token:
        print(f"Error: {path} debe incluir jira_url, jira_email y jira_api_token.", file=sys.stderr)
        sys.exit(1)
    return {"jira_url": url, "jira_email": email, "jira_api_token": token}


def load_config() -> Dict[str, Any]:
    """Resolve Jira credentials.

    Order:
    1. Environment variables (JIRA_BASE_URL / JIRA_EMAIL / JIRA_API_TOKEN, or ATLASSIAN_* aliases).
    2. ~/.config/humand-pm-jira/config.json (XDG-compliant user path).
    3. config.json next to the skill directory (legacy / local-only setup).
    """
    env_cfg = _load_config_from_env()
    if env_cfg:
        return env_cfg

    user_path = _user_config_path()
    for path in (user_path, LEGACY_CONFIG_PATH):
        if os.path.exists(path):
            return _load_config_from_file(path)

    print("Error: credenciales de Jira no configuradas.", file=sys.stderr)
    print("Opciones:", file=sys.stderr)
    print("  1) Exportar JIRA_BASE_URL, JIRA_EMAIL, JIRA_API_TOKEN.", file=sys.stderr)
    print(f"  2) Copiar config.template.json a {user_path} y completarlo.", file=sys.stderr)
    sys.exit(1)


def get_auth_headers(config: Dict[str, Any]) -> Dict[str, str]:
    credentials = f"{config['jira_email']}:{config['jira_api_token']}"
    encoded = base64.b64encode(credentials.encode()).decode()
    return {
        "Authorization": f"Basic {encoded}",
        "Accept": "application/json",
        "Content-Type": "application/json",
    }


def jira_get(config: Dict[str, Any], path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
    url = f"{config['jira_url'].rstrip('/')}/rest/api/3/{path}"
    headers = get_auth_headers(config)
    resp = requests.get(url, headers=headers, params=params, timeout=30, proxies=NO_PROXY)
    resp.raise_for_status()
    return resp.json() if resp.text else {}


def jira_post(config: Dict[str, Any], path: str, body: Dict[str, Any]) -> Dict[str, Any]:
    url = f"{config['jira_url'].rstrip('/')}/rest/api/3/{path}"
    headers = get_auth_headers(config)
    resp = requests.post(url, headers=headers, json=body, timeout=30, proxies=NO_PROXY)
    resp.raise_for_status()
    return resp.json() if resp.text else {}


def jira_put(config: Dict[str, Any], path: str, body: Dict[str, Any]) -> Tuple[bool, Dict[str, Any]]:
    """
    Retorna (ok, payload).
    No hace sys.exit para permitir seguir con issues que fallen.
    """
    url = f"{config['jira_url'].rstrip('/')}/rest/api/3/{path}"
    headers = get_auth_headers(config)
    resp = requests.put(url, headers=headers, json=body, timeout=30, proxies=NO_PROXY)
    try:
        payload = resp.json() if resp.text else {}
    except Exception:
        payload = {"raw": resp.text[:500]}
    if 200 <= resp.status_code < 300:
        return True, payload
    return False, payload


def resolve_issue_type_id(
    config: Dict[str, Any],
    project_key: str,
    target_issue_type_name: str,
) -> str:
    """
    Usa el endpoint vigente para obtener issue types de un proyecto.

    Endpoint: GET /rest/api/3/issue/createmeta/{projectKey}/issuetypes
    (reemplaza al deprecado /rest/api/3/issue/createmeta?projectKeys=...).
    Es paginado, pero ningún proyecto real tiene > 50 issue types,
    así que con maxResults=200 alcanza en un solo request.
    """
    path = f"issue/createmeta/{project_key}/issuetypes"
    params = {"maxResults": 200}
    data = jira_get(config, path, params=params)
    issuetypes = data.get("issueTypes") or data.get("values") or []
    if not issuetypes:
        raise RuntimeError(f"No se encontraron issue types para proyecto {project_key}")

    target_lower = target_issue_type_name.strip().lower()

    for it in issuetypes:
        if (it.get("name") or "").strip().lower() == target_lower:
            return str(it.get("id"))

    for it in issuetypes:
        n = (it.get("name") or "").strip().lower()
        if target_lower in n or n in target_lower:
            return str(it.get("id"))

    available = [(it.get("name"), it.get("id")) for it in issuetypes]
    raise RuntimeError(
        f"No se encontró issue type '{target_issue_type_name}' en proyecto {project_key}. "
        f"Disponibles: {available[:20]}"
    )


def list_subtasks(config: Dict[str, Any], parent_key: str, max_results: int = 200) -> List[Dict[str, Any]]:
    jql = f'parent = "{parent_key}"'
    fields = ["key", "summary", "issuetype", "parent"]
    body = {"jql": jql, "maxResults": max_results, "fields": fields}
    data = jira_post(config, "search/jql", body)
    return data.get("issues", []) or []


def should_convert(current_type_name: str, current_name_target_subtask_markers: List[str]) -> bool:
    n = (current_type_name or "").strip().lower()
    return any(marker in n for marker in current_name_target_subtask_markers)


def main() -> None:
    parser = argparse.ArgumentParser(description="Convierte Subtask -> Dev Task en subtareas de un parent.")
    parser.add_argument("--parent", required=True, help="Issue key padre (ej. SQPD-3356)")
    parser.add_argument("--target-type", default="Dev Task", help="Nombre del issue type destino (default: 'Dev Task')")
    parser.add_argument("--dry-run", action="store_true", help="No ejecuta cambios; solo lista y valida.")
    parser.add_argument("--limit", type=int, default=0, help="Limita cantidad de issues a convertir (0 = todos).")
    parser.add_argument(
        "--subtask-markers",
        default="subtask,sub-task",
        help="Comas: markers para detectar el type 'subtask' (default incluye 'subtask' y 'sub-task').",
    )
    args = parser.parse_args()

    config = load_config()
    parent_key = args.parent.strip()
    project_key = parent_key.split("-")[0]

    target_type_id = resolve_issue_type_id(config, project_key, args.target_type)
    markers = [m.strip().lower() for m in (args.subtask_markers or "").split(",") if m.strip()]

    issues = list_subtasks(config, parent_key)
    if not issues:
        print(f"No se encontraron subtareas para {parent_key}.")
        return

    to_convert = []
    for issue in issues:
        fields = issue.get("fields") or {}
        key = issue.get("key")
        issuetype = fields.get("issuetype") or {}
        current_type = issuetype.get("name") or ""
        if should_convert(current_type, markers):
            to_convert.append((key, current_type))

    if args.limit and args.limit > 0:
        to_convert = to_convert[: args.limit]

    print(f"Parent: {parent_key}")
    print(f"Proyecto: {project_key}")
    print(f"Destino: {args.target_type} (id: {target_type_id})")
    print(f"Subtasks encontradas: {len(issues)}")
    print(f"Subtasks a convertir: {len(to_convert)}")

    if not to_convert:
        print("No hay subtareas que coincidan con markers de 'Subtask'. No se hará nada.")
        return

    if args.dry_run:
        print("\nDRY RUN. Cambios propuestos:")
        for k, t in to_convert:
            print(f"- {k}: {t} -> {args.target_type}")
        return

    print("\nEjecutando cambios...")
    ok_count = 0
    fail_count = 0
    for key, current_type in to_convert:
        body = {"fields": {"issuetype": {"id": target_type_id}}}
        ok, payload = jira_put(config, f"issue/{key}", body)
        if ok:
            ok_count += 1
            print(f"[OK] {key}: {current_type} -> {args.target_type}")
            continue

        retry_body = {"fields": {"issuetype": {"id": target_type_id}, "parent": None}}
        ok2, payload2 = jira_put(config, f"issue/{key}", retry_body)
        if ok2:
            ok_count += 1
            print(f"[OK] {key}: (retry con parent=null) {current_type} -> {args.target_type}")
        else:
            fail_count += 1
            msg = ""
            if isinstance(payload2, dict):
                msg = (payload2.get("errorMessages") or payload2.get("errors") or payload2.get("message") or "")
                msg = str(msg)[:220]
            print(f"[FAIL] {key}: {current_type} -> {args.target_type}. Detalle: {msg}")

    print(f"\nResumen: ok={ok_count}, fail={fail_count}")


if __name__ == "__main__":
    main()
