"""Shared runner for saved tasks over SSH task services.

Both the Actions page (``routers/tasks.py``) and the SSH task widget
(``widgets/sources.py``) run saved tasks against ``ssh_tasks`` service instances.
This module is the single execution path: build the client from the service
record, render the command, run it with the service timeout, append a
``service_task_runs`` row, and return the result.

There is intentionally no local execution mode — tasks are SSH-only.
"""

from __future__ import annotations

import logging
import shlex
import time
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any

from media_library_viewer_api.clients.ssh import RemoteSSHClient
from media_library_viewer_api.config import get_settings
from media_library_viewer_api.services.settings_store import SettingsStore

if TYPE_CHECKING:
    from media_library_viewer_api.widgets.sources import ServiceRecord

logger = logging.getLogger(__name__)


@dataclass
class TaskRunResult:
    """Outcome of a single saved-task run."""

    exit_status: int
    stdout: str
    stderr: str
    duration_ms: int
    status: str  # "success" | "failure" | "error"
    error: str = ""


def build_ssh_client(store: SettingsStore, service: "ServiceRecord") -> RemoteSSHClient:
    """Build an SSH client from an ssh_tasks service instance + referenced key."""
    config = service.config
    host = str(config.get("host") or "").strip()
    username = str(config.get("username") or "").strip()
    if not host or not username:
        raise ValueError("SSH task service is missing host or username")

    settings = get_settings()
    private_key = ""
    key_passphrase = ""
    ssh_key_id = str(config.get("ssh_key_id") or "").strip()
    if ssh_key_id:
        ssh_key = store.get_ssh_key(ssh_key_id)
        if ssh_key:
            private_key = str(ssh_key.get("private_key") or "")
            key_passphrase = str(ssh_key.get("passphrase") or "")
    # Service-level passphrase secret takes precedence.
    key_passphrase = str(service.secrets.get("passphrase") or "") or key_passphrase

    return RemoteSSHClient(
        host=host,
        username=username,
        port=int(config.get("port") or 22),
        private_key=private_key or None,
        private_key_passphrase=key_passphrase or None,
        known_hosts_path=str(settings.ssh_known_hosts_file),
        timeout=int(config.get("timeout_seconds") or 30),
    )


def _render_command(task: dict[str, Any]) -> str:
    """Render a saved task into a shell command (shell or python3 -c)."""
    task_type = str(task.get("task_type") or "shell").lower()
    command = str(task.get("content") or "")
    if task_type == "python":
        return f"python3 -c {shlex.quote(command)}"
    if task_type == "shell":
        return command
    raise ValueError(f"Unknown task type: {task_type}")


def run_saved_task(
    store: SettingsStore,
    task: dict[str, Any],
    service: "ServiceRecord",
    *,
    timeout: int | None = None,
) -> TaskRunResult:
    """Run a saved task on an ssh_tasks service instance and log the run.

    The ``timeout`` defaults to the service's ``timeout_seconds`` config. The run
    is recorded in ``service_task_runs`` regardless of outcome (success, failure,
    error). Raises ``ValueError`` for an unsupported task type or an incomplete
    service config (propagated from ``build_ssh_client`` / ``_render_command``).
    """
    timeout = int(timeout if timeout is not None else service.config.get("timeout_seconds") or 30)
    client = build_ssh_client(store, service)
    command = _render_command(task)

    start = time.perf_counter()
    try:
        result = client.run(command, timeout=timeout)
    except Exception as exc:
        duration_ms = int((time.perf_counter() - start) * 1000)
        _record(store, task, service, "error", duration_ms=duration_ms, error=str(exc)[:1000])
        logger.exception("saved task run failed task_id=%s", task.get("id"))
        return TaskRunResult(
            exit_status=1,
            stdout="",
            stderr=str(exc),
            duration_ms=duration_ms,
            status="error",
            error=str(exc),
        )

    duration_ms = int((time.perf_counter() - start) * 1000)
    stdout = result.stdout or ""
    stderr = result.stderr or ""
    status = "success" if result.exit_status == 0 else "failure"
    _record(
        store,
        task,
        service,
        status,
        exit_status=result.exit_status,
        duration_ms=duration_ms,
        stdout_tail=stdout,
        stderr_tail=stderr,
        error="" if result.exit_status == 0 else (stderr or stdout or "Task failed"),
    )
    return TaskRunResult(
        exit_status=result.exit_status,
        stdout=stdout,
        stderr=stderr,
        duration_ms=duration_ms,
        status=status,
    )


def _record(
    store: SettingsStore,
    task: dict[str, Any],
    service: "ServiceRecord",
    status: str,
    *,
    exit_status: int | None = None,
    duration_ms: int = 0,
    stdout_tail: str = "",
    stderr_tail: str = "",
    error: str = "",
) -> None:
    """Append a service_task_runs row for a saved-task run."""
    store.record_service_task_run(
        {
            "task_id": str(task.get("id") or ""),
            "service_id": service.id,
            "status": status,
            "exit_status": exit_status,
            "duration_ms": duration_ms,
            "stdout_tail": stdout_tail,
            "stderr_tail": stderr_tail,
            "error": error,
        }
    )
