"""Hub setup automation for the AgentWeave setup wizard."""

import secrets
import subprocess
import time
import urllib.error
import urllib.request
from contextlib import suppress
from pathlib import Path
from typing import Any, Dict, Optional, Tuple

from .interactive import (
    Emojis,
    Styled,
    print_error_item,
    print_info_item,
    print_success_item,
)

HUB_FILES_URL = "https://raw.githubusercontent.com/gutohuida/AgentWeave/master/hub"


def check_docker_installed() -> Tuple[bool, Optional[str]]:
    """Check if Docker and Docker Compose are installed.

    Returns:
        Tuple of (success, error_message)
    """
    # Check docker
    try:
        result = subprocess.run(
            ["docker", "--version"],
            capture_output=True,
            text=True,
            timeout=5,
        )
        if result.returncode != 0:
            return False, "Docker is not installed or not in PATH"
    except (subprocess.TimeoutExpired, FileNotFoundError):
        return False, "Docker is not installed or not in PATH"

    # Check docker compose (newer versions use 'docker compose', older use 'docker-compose')
    try:
        result = subprocess.run(
            ["docker", "compose", "version"],
            capture_output=True,
            text=True,
            timeout=5,
        )
        if result.returncode != 0:
            # Try old docker-compose
            try:
                result = subprocess.run(
                    ["docker-compose", "--version"],
                    capture_output=True,
                    text=True,
                    timeout=5,
                )
                if result.returncode != 0:
                    return False, "Docker Compose is not installed"
            except (subprocess.TimeoutExpired, FileNotFoundError):
                return False, "Docker Compose is not installed"
    except (subprocess.TimeoutExpired, FileNotFoundError):
        return False, "Docker Compose is not installed"

    return True, None


def generate_api_key() -> str:
    """Generate a secure random API key."""
    return f"aw_live_{secrets.token_hex(16)}"


def download_file(url: str, dest: Path, timeout: int = 30) -> bool:
    """Download a file from URL to destination.

    Args:
        url: The URL to download from
        dest: Destination path
        timeout: Request timeout in seconds

    Returns:
        True if successful, False otherwise
    """
    try:
        req = urllib.request.Request(url)
        req.add_header(
            "User-Agent",
            "AgentWeave-Setup/1.0 (https://github.com/gutohuida/AgentWeave)",
        )
        with urllib.request.urlopen(req, timeout=timeout) as response:
            dest.write_bytes(response.read())
        return True
    except (urllib.error.URLError, urllib.error.HTTPError, OSError):
        return False


def download_hub_files(target_dir: Path) -> Tuple[bool, Optional[str]]:
    """Download Hub configuration files.

    Args:
        target_dir: Directory to download files to

    Returns:
        Tuple of (success, error_message)
    """
    target_dir = Path(target_dir)
    target_dir.mkdir(parents=True, exist_ok=True)

    files_to_download = [
        ("docker-compose.yml", f"{HUB_FILES_URL}/docker-compose.yml"),
        (".env.example", f"{HUB_FILES_URL}/.env.example"),
    ]

    for filename, url in files_to_download:
        dest = target_dir / filename
        if dest.exists():
            continue

        print_info_item(f"Downloading {filename}...")
        if not download_file(url, dest):
            return False, f"Failed to download {filename}"

    return True, None


def create_hub_env(
    target_dir: Path,
    api_key: str,
    port: int = 8000,
    project_name: str = "Default Project",
) -> Path:
    """Create the Hub .env file.

    Args:
        target_dir: Directory to create .env in
        api_key: The API key to use
        port: Port for the Hub to listen on
        project_name: Name of the default project

    Returns:
        Path to the created .env file
    """
    target_dir = Path(target_dir)
    target_dir.mkdir(parents=True, exist_ok=True)

    env_content = f"""# AgentWeave Hub — environment configuration
# Generated by agentweave init

# SQLite database path (default: data/agentweave.db inside the container)
DATABASE_URL=sqlite+aiosqlite:///data/agentweave.db

# Port the Hub listens on (default: 8000)
AW_PORT={port}

# Bootstrap credentials — created automatically on first startup.
AW_BOOTSTRAP_API_KEY={api_key}
AW_BOOTSTRAP_PROJECT_ID=proj-default
AW_BOOTSTRAP_PROJECT_NAME={project_name}

# CORS allowed origins (comma-separated). Leave empty for same-origin only.
# Example for local dev with Vite: AW_CORS_ORIGINS=http://localhost:5173
AW_CORS_ORIGINS=
"""

    env_path = target_dir / ".env"
    env_path.write_text(env_content, encoding="utf-8")
    return env_path


def is_port_available(port: int) -> bool:
    """Check if a port is available.

    Args:
        port: Port number to check

    Returns:
        True if port is available, False otherwise
    """
    import socket

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        try:
            s.bind(("", port))
            return True
        except OSError:
            return False


def find_available_port(start_port: int = 8000, max_port: int = 9000) -> int:
    """Find an available port.

    Args:
        start_port: Port to start checking from
        max_port: Maximum port to check

    Returns:
        Available port number
    """
    for port in range(start_port, max_port + 1):
        if is_port_available(port):
            return port
    return start_port  # Fallback


def start_hub(target_dir: Path, port: int = 8000) -> Tuple[bool, Optional[str]]:
    """Start the Hub using Docker Compose.

    Args:
        target_dir: Directory containing docker-compose.yml
        port: Port to run on

    Returns:
        Tuple of (success, error_message)
    """
    target_dir = Path(target_dir)

    # Check if docker-compose.yml exists
    compose_file = target_dir / "docker-compose.yml"
    if not compose_file.exists():
        return False, "docker-compose.yml not found"

    # Check if .env exists
    env_file = target_dir / ".env"
    if not env_file.exists():
        return False, ".env file not found"

    # Check if already running
    try:
        result = subprocess.run(
            ["docker", "compose", "ps", "-q"],
            cwd=target_dir,
            capture_output=True,
            text=True,
            timeout=10,
        )
        if result.returncode == 0 and result.stdout.strip():
            # Already running
            return True, None
    except (subprocess.TimeoutExpired, FileNotFoundError):
        pass

    # Start the Hub
    print_info_item("Starting Hub container...")
    try:
        result = subprocess.run(
            ["docker", "compose", "up", "-d"],
            cwd=target_dir,
            capture_output=True,
            text=True,
            timeout=60,
        )
        if result.returncode != 0:
            return False, f"Failed to start Hub: {result.stderr}"
    except subprocess.TimeoutExpired:
        return False, "Timeout while starting Hub"
    except FileNotFoundError:
        return False, "Docker Compose not found"

    return True, None


def stop_hub(target_dir: Path) -> bool:
    """Stop the Hub using Docker Compose.

    Args:
        target_dir: Directory containing docker-compose.yml

    Returns:
        True if successful, False otherwise
    """
    target_dir = Path(target_dir)
    compose_file = target_dir / "docker-compose.yml"

    if not compose_file.exists():
        return False

    try:
        result = subprocess.run(
            ["docker", "compose", "down"],
            cwd=target_dir,
            capture_output=True,
            text=True,
            timeout=30,
        )
        return result.returncode == 0
    except (subprocess.TimeoutExpired, FileNotFoundError):
        return False


def wait_for_hub(url: str, timeout: int = 60, interval: float = 1.0) -> bool:
    """Wait for the Hub to be ready.

    Args:
        url: Hub URL (e.g., http://localhost:8000)
        timeout: Maximum time to wait in seconds
        interval: Polling interval in seconds

    Returns:
        True if Hub is ready, False if timeout
    """
    health_url = f"{url.rstrip('/')}/health"
    start_time = time.time()

    while time.time() - start_time < timeout:
        try:
            req = urllib.request.Request(health_url)
            req.add_header("Accept", "application/json")
            with urllib.request.urlopen(req, timeout=5) as response:
                if response.status == 200:
                    return True
        except (urllib.error.URLError, urllib.error.HTTPError, OSError):
            pass

        time.sleep(interval)

    return False


def get_hub_status(target_dir: Path) -> dict:
    """Get the status of the Hub.

    Args:
        target_dir: Directory containing docker-compose.yml

    Returns:
        Dictionary with status information
    """
    target_dir = Path(target_dir)
    compose_file = target_dir / "docker-compose.yml"

    result: Dict[str, Any] = {
        "installed": compose_file.exists(),
        "running": False,
        "healthy": False,
        "url": None,
    }

    if not compose_file.exists():
        return result

    # Check if running
    try:
        ps_result = subprocess.run(
            ["docker", "compose", "ps", "-q"],
            cwd=target_dir,
            capture_output=True,
            text=True,
            timeout=10,
        )
        result["running"] = ps_result.returncode == 0 and bool(ps_result.stdout.strip())
    except (subprocess.TimeoutExpired, FileNotFoundError):
        pass

    # Check health if running
    if result["running"]:
        # Get port from .env
        env_file = target_dir / ".env"
        port = 8000
        if env_file.exists():
            for line in env_file.read_text().splitlines():
                if line.startswith("AW_PORT="):
                    with suppress(ValueError):
                        port = int(line.split("=", 1)[1])
                    break

        url = f"http://localhost:{port}"
        result["url"] = url

        try:
            health_url = f"{url}/health"
            req = urllib.request.Request(health_url)
            req.add_header("Accept", "application/json")
            with urllib.request.urlopen(req, timeout=5) as response:
                result["healthy"] = response.status == 200
        except (urllib.error.URLError, urllib.error.HTTPError, OSError):
            pass

    return result


def setup_hub_interactive(
    project_name: str,
) -> Tuple[bool, Optional[str], Optional[str], Optional[str]]:
    """Interactive Hub setup.

    Args:
        project_name: Name of the project

    Returns:
        Tuple of (success, api_key, hub_url, error_message)
    """
    from .interactive import ask_confirm, ask_text

    # Check Docker
    print_info_item("Checking Docker...")
    docker_ok, docker_error = check_docker_installed()
    if not docker_ok:
        print_error_item(f"Docker not found: {docker_error}")
        print()
        print("To set up the Hub manually:")
        print("  1. Install Docker: https://docs.docker.com/get-docker/")
        print("  2. Run: agentweave hub setup")
        return False, None, None, docker_error

    print_success_item("Docker detected")

    # Determine target directory
    target_dir = Path.cwd() / "hub"
    if target_dir.exists() and (target_dir / "docker-compose.yml").exists():
        # Use existing hub directory
        pass
    elif (Path.cwd() / "docker-compose.yml").exists():
        # Use current directory
        target_dir = Path.cwd()
    else:
        # Create hub directory
        target_dir = Path.cwd() / "hub"
        target_dir.mkdir(parents=True, exist_ok=True)

    # Download files if needed
    compose_file = target_dir / "docker-compose.yml"
    if not compose_file.exists():
        print_info_item("Downloading Hub files...")
        download_ok, download_error = download_hub_files(target_dir)
        if not download_ok:
            print_error_item(f"Failed to download: {download_error}")
            print()
            print("You can download manually:")
            print(f"  curl -O {HUB_FILES_URL}/docker-compose.yml")
            print(f"  curl -O {HUB_FILES_URL}/.env.example")
            return False, None, None, download_error
        print_success_item("Downloaded Hub files")
    else:
        print_info_item("Using existing Hub files")

    # Check port availability
    port = 8000
    if not is_port_available(port):
        new_port = find_available_port(port + 1)
        if Styled.enabled():
            print(f"  {Styled.yellow(Emojis.WARNING)} Port {port} is in use.")
        else:
            print(f"  [WARN] Port {port} is in use.")

        use_alt = ask_confirm(f"Use port {new_port} instead?", default=True)
        if use_alt:
            port = new_port
        else:
            custom_port = ask_text(
                "Enter a port number",
                default=str(new_port),
                validator=lambda x: (
                    x.isdigit() and 1024 <= int(x) <= 65535,
                    "Port must be 1024-65535",
                ),
            )
            port = int(custom_port)

    # Generate API key
    api_key = generate_api_key()

    # Create .env file
    env_path = create_hub_env(
        target_dir=target_dir,
        api_key=api_key,
        port=port,
        project_name=project_name,
    )
    print_success_item(f"Created {env_path.name}")

    # Start the Hub
    start_ok, start_error = start_hub(target_dir, port)
    if not start_ok:
        print_error_item(f"Failed to start Hub: {start_error}")
        return False, None, None, start_error

    # Wait for Hub to be ready
    hub_url = f"http://localhost:{port}"
    print_info_item("Waiting for Hub to be ready...")
    if wait_for_hub(hub_url, timeout=60):
        print_success_item(f"Hub is ready at {hub_url}")
    else:
        print_error_item("Hub started but health check timed out")
        print_info_item(
            f"Check status with: docker compose -f {target_dir}/docker-compose.yml logs"
        )

    return True, api_key, hub_url, None
