#!/usr/bin/env python3
"""Deploy delegation script - Network type (SSH/SCP).

Features:
- SCP upload to remote board
- Kill existing process before upload (avoid "Text file busy")
- SSH execute with remote log support
- User-friendly error messages
"""

import os
import stat
import subprocess
import sys
import time
import tomllib
from pathlib import Path


def main() -> int:
    """Execute network deployment via SSH/SCP."""
    project_root = Path.cwd()
    config_path = project_root / ".fwauto" / "config.toml"

    if not config_path.exists():
        print(f"❌ Config not found: {config_path}", file=sys.stderr)
        return 1

    with open(config_path, "rb") as f:
        config = tomllib.load(f)

    # Read deployment settings
    deployment = config.get("deployment", {})
    board_ip = deployment.get("board_ip")
    board_user = deployment.get("board_user", "root")
    deploy_path = deployment.get("deploy_path", "/home/root")
    ssh_options = deployment.get("ssh_options", "-o StrictHostKeyChecking=no -o BatchMode=yes")
    remote_log_enabled = deployment.get("remote_log_enabled", True)
    remote_log_pattern = deployment.get("remote_log_pattern", "{{ deploy_path }}/{{ app_name }}_{{ date }}.log")

    if not board_ip:
        print("❌ deployment.board_ip not configured", file=sys.stderr)
        return 1

    # Read build settings
    build_config = config.get("build", {})
    binary_name = build_config.get("binary_name", "app")
    build_dir = project_root / build_config.get("output_dir", "build")
    binary_path = build_dir / binary_name

    if not binary_path.exists():
        # Try to find executable in build directory
        if build_dir.exists():
            for f in build_dir.iterdir():
                if f.is_file() and _is_executable(f):
                    binary_path = f
                    binary_name = f.name
                    break
            else:
                print(f"❌ No executable binary found in: {build_dir}", file=sys.stderr)
                return 1
        else:
            print(f"❌ Build directory not found: {build_dir}", file=sys.stderr)
            return 1

    # Get binary args from environment
    binary_args = os.environ.get("BINARY_ARGS", "")

    # Step 1: Kill existing process on remote (avoid "Text file busy")
    print(f"🔪 Killing existing {binary_name} process...")
    _kill_remote_process(binary_name, board_ip, board_user, ssh_options)

    # Step 2: SCP upload
    target = f"{board_user}@{board_ip}:{deploy_path}/"
    scp_cmd = ["scp"] + ssh_options.split() + [str(binary_path), target]
    print(f"📦 Uploading to {board_ip}:{deploy_path}/")

    result = subprocess.run(scp_cmd, capture_output=True, text=True)
    if result.returncode != 0:
        raw_error = result.stderr.strip() if result.stderr else ""
        error_msg = _translate_ssh_error(raw_error, board_ip)
        print(f"❌ {error_msg}", file=sys.stderr)
        return result.returncode

    print(f"✅ Uploaded {binary_name}")

    # Step 3: Prepare remote log path (from env or generate)
    remote_log_path = os.environ.get("REMOTE_LOG_PATH", "")
    if not remote_log_path and remote_log_enabled:
        remote_log_path = _render_log_path(remote_log_pattern, deploy_path, binary_name)
    if remote_log_path:
        print(f"📊 Remote log: {remote_log_path}")

    # Step 4: SSH execute binary on remote
    print(f"🚀 Executing: {binary_name} {binary_args}".strip())

    remote_script = _build_remote_script(binary_name, deploy_path, binary_args, remote_log_path)

    ssh_cmd = ["ssh"] + ssh_options.split() + [f"{board_user}@{board_ip}", "bash"]

    result = subprocess.run(ssh_cmd, input=remote_script, capture_output=True, text=True, timeout=10)

    # Show output
    if result.stdout:
        # Filter out internal markers
        output = result.stdout
        if "PROGRAM_OUTPUT_START" in output and "PROGRAM_OUTPUT_END" in output:
            start = output.find("PROGRAM_OUTPUT_START") + len("PROGRAM_OUTPUT_START")
            end = output.find("PROGRAM_OUTPUT_END")
            program_output = output[start:end].strip()
            if program_output:
                print(program_output)
        else:
            print(result.stdout)

    if result.stderr:
        # Filter SSH warnings
        for line in result.stderr.split("\n"):
            if line and not line.startswith("Warning:") and not line.startswith("**"):
                print(line, file=sys.stderr)

    if result.returncode != 0:
        print(f"❌ Execution failed (exit code: {result.returncode})", file=sys.stderr)
        return result.returncode

    print("✅ Execution completed")
    return 0


def _is_executable(file: Path) -> bool:
    """Check if file is executable."""
    try:
        return bool(file.stat().st_mode & stat.S_IXUSR)
    except Exception:
        return file.suffix in ["", ".exe", ".out", ".elf"]


def _kill_remote_process(binary_name: str, board_ip: str, board_user: str, ssh_options: str) -> None:
    """Kill existing process on remote board."""
    ssh_cmd = (
        ["ssh"] + ssh_options.split() + [f"{board_user}@{board_ip}", f"pkill -9 '{binary_name}' 2>/dev/null || true"]
    )
    try:
        subprocess.run(ssh_cmd, capture_output=True, timeout=5)
    except Exception:
        pass  # Ignore errors


def _render_log_path(pattern: str, deploy_path: str, binary_name: str) -> str:
    """Render remote log path from pattern."""
    from datetime import datetime

    date_str = datetime.now().strftime("%Y-%m-%d")

    # Simple template rendering
    result = pattern
    result = result.replace("{{ deploy_path }}", deploy_path)
    result = result.replace("{{deploy_path}}", deploy_path)
    result = result.replace("{{ app_name }}", binary_name)
    result = result.replace("{{app_name}}", binary_name)
    result = result.replace("{{ binary_name }}", binary_name)
    result = result.replace("{{binary_name}}", binary_name)
    result = result.replace("{{ date }}", date_str)
    result = result.replace("{{date}}", date_str)

    return result


def _build_remote_script(binary_name: str, deploy_path: str, binary_args: str, remote_log_path: str) -> str:
    """Build bash script to execute on remote board."""
    # Escape single quotes in arguments
    binary_args_escaped = binary_args.replace("'", "'\\''")
    timestamp = time.strftime("%Y-%m-%d %H:%M:%S")

    if remote_log_path:
        # With remote log: append output to log file
        log_path_escaped = remote_log_path.replace("'", "'\\''")
        log_setup = f"""
mkdir -p "$(dirname '{log_path_escaped}')"
cat >> '{log_path_escaped}' <<'LOG_HEADER'

==========================================
Firmware Execution: {timestamp}
Binary: {binary_name}
Arguments: {binary_args_escaped}
==========================================

LOG_HEADER
LOG_TARGET='{log_path_escaped}'
"""
    else:
        log_setup = "LOG_TARGET='/dev/null'"

    return f"""
set -e
cd '{deploy_path}' || {{ echo "ERROR: Cannot cd to {deploy_path}" >&2; exit 1; }}
chmod +x './{binary_name}' || {{ echo "ERROR: Cannot chmod {binary_name}" >&2; exit 1; }}
{log_setup}
# Capture output to temp file first (for error reporting)
TEMP_OUTPUT=$(mktemp)
'./{binary_name}' {binary_args_escaped} > "$TEMP_OUTPUT" 2>&1 &
FIRMWARE_PID=$!
sleep 0.5

# Check if process is still running (daemon) or completed successfully (short-lived)
if kill -0 $FIRMWARE_PID 2>/dev/null; then
    # Process still running - it's a daemon, append output to log and continue
    cat "$TEMP_OUTPUT" >> "$LOG_TARGET" 2>/dev/null
    rm -f "$TEMP_OUTPUT"
    echo "SUCCESS: Firmware started with PID $FIRMWARE_PID (running)" >&2
    exit 0
else
    # Process already exited, check if it was successful
    EXIT_CODE=0
    wait $FIRMWARE_PID 2>/dev/null || EXIT_CODE=$?
    if [ $EXIT_CODE -eq 0 ]; then
        cat "$TEMP_OUTPUT" >> "$LOG_TARGET" 2>/dev/null
        rm -f "$TEMP_OUTPUT"
        echo "SUCCESS: Firmware completed successfully" >&2
        exit 0
    else
        # Show program output on failure
        echo "PROGRAM_OUTPUT_START"
        cat "$TEMP_OUTPUT" 2>/dev/null
        echo "PROGRAM_OUTPUT_END"
        cat "$TEMP_OUTPUT" >> "$LOG_TARGET" 2>/dev/null
        rm -f "$TEMP_OUTPUT"
        echo "ERROR: Firmware failed with exit code $EXIT_CODE" >&2
        exit 1
    fi
fi
"""


def _translate_ssh_error(raw_error: str, board_ip: str) -> str:
    """Translate SSH/SCP technical errors to user-friendly messages."""
    error_lower = raw_error.lower()

    # Network connectivity issues
    if "no route to host" in error_lower:
        return f"Cannot reach {board_ip} (device not powered on or not on same network)"
    if "connection refused" in error_lower:
        return f"Connection refused (SSH service not running on {board_ip})"
    if "connection timed out" in error_lower:
        return f"Connection timed out ({board_ip} not responding)"
    if "network is unreachable" in error_lower:
        return "Network unreachable (check network settings)"
    if "host is down" in error_lower:
        return f"Host is down ({board_ip} not responding)"

    # Authentication issues
    if "permission denied" in error_lower:
        return "SSH authentication failed (check SSH key or password)"
    if "authentication failed" in error_lower:
        return "SSH authentication failed (invalid username or password)"

    # File issues
    if "no such file" in error_lower:
        return "File not found (run build first)"

    # Default: show raw error
    return f"Upload failed: {raw_error}" if raw_error else "Upload failed (unknown error)"


if __name__ == "__main__":
    sys.exit(main())
