"""
Doit Agent - Update & Migration Engine
Handles version checking, safe updates, schema migrations, and rollback.
"""
from __future__ import annotations

import asyncio
import json
import logging
import shutil
import subprocess
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional

import aiohttp

from core.config import VERSION, BACKUP_DIR

logger = logging.getLogger("doit.updates")

PYPI_URL = "https://pypi.org/pypi/doit-agent/json"


async def check_for_updates() -> tuple[bool, str, str]:
    """
    Check PyPI for a newer version.
    Returns (update_available, current_version, latest_version).
    """
    try:
        async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as sess:
            async with sess.get(PYPI_URL) as resp:
                if resp.status != 200:
                    return False, VERSION, VERSION
                data = await resp.json()
                latest = data["info"]["version"]
                update_available = _version_gt(latest, VERSION)
                return update_available, VERSION, latest
    except Exception as e:
        logger.debug("Update check failed: %s", e)
        return False, VERSION, VERSION


def _version_gt(a: str, b: str) -> bool:
    """Returns True if version a > version b."""
    def parse(v):
        return tuple(int(x) for x in v.split(".")[:3])
    try:
        return parse(a) > parse(b)
    except Exception:
        return False


def backup_before_update() -> Optional[Path]:
    """Create a backup of the current installation before updating."""
    BACKUP_DIR.mkdir(parents=True, exist_ok=True)
    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    backup = BACKUP_DIR / f"pre_update_{ts}"
    try:
        # Find the doit package location
        import importlib
        spec = importlib.util.find_spec("doit")
        if spec and spec.origin:
            pkg_dir = Path(spec.origin).parent
            shutil.copytree(str(pkg_dir), str(backup))
            logger.info("Backup created: %s", backup)
            return backup
    except Exception as e:
        logger.warning("Backup failed: %s", e)
    return None


async def perform_update(target_version: str = None) -> tuple[bool, str]:
    """
    Perform a pip update of doit-agent.
    Returns (success, message).
    """
    backup = backup_before_update()

    cmd = [sys.executable, "-m", "pip", "install", "--upgrade"]
    if target_version:
        cmd.append(f"doit-agent=={target_version}")
    else:
        cmd.append("doit-agent")

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
        if result.returncode == 0:
            logger.info("Update successful")
            return True, f"Updated successfully. Restart doit to apply changes.\n{result.stdout[-500:]}"
        else:
            logger.error("Update failed: %s", result.stderr)
            if backup:
                logger.info("Backup available at: %s", backup)
            return False, f"Update failed: {result.stderr[-500:]}"
    except subprocess.TimeoutExpired:
        return False, "Update timed out"
    except Exception as e:
        return False, str(e)


async def rollback(backup_path: str) -> tuple[bool, str]:
    """Rollback to a previous backup."""
    bp = Path(backup_path)
    if not bp.exists():
        return False, f"Backup not found: {backup_path}"
    try:
        # Reinstall from backup
        result = subprocess.run(
            [sys.executable, "-m", "pip", "install", str(bp)],
            capture_output=True, text=True, timeout=60
        )
        if result.returncode == 0:
            return True, "Rollback successful"
        return False, f"Rollback failed: {result.stderr}"
    except Exception as e:
        return False, str(e)


def list_backups() -> list[dict]:
    """List available backups."""
    BACKUP_DIR.mkdir(parents=True, exist_ok=True)
    backups = []
    for d in sorted(BACKUP_DIR.iterdir(), reverse=True):
        if d.is_dir() and d.name.startswith("pre_update_"):
            backups.append({
                "path": str(d),
                "name": d.name,
                "created": d.name.split("_", 2)[-1],
            })
    return backups
