"""Task configuration dataclass for structured task parameters."""

from dataclasses import dataclass, field
from typing import Any

from hil_testbench.config.run_config import RunConfig
from hil_testbench.data_structs.hosts import HostDefinition
from hil_testbench.config.config_merger import merge_config_sources


@dataclass
class TaskConfig:
    """Per-task execution configuration.

    Provides:
    - Task execution parameters (duration, interval, enable_viz)
    - Task-specific configuration (task_params dict)
    - Access to all configured hosts via run_config.hosts
    - Reference to global runner config

    Example task_params patterns:
        # Single host tasks:
        task_params["host_id"] = "server1"

        # Multi-host tasks (iperf):
        task_params["local_host"] = "localhost"
        task_params["remote_host"] = "server1"

        # Multi-interface testing:
        task_params["interface_pairs"] = [
            {"host": "support_server", "interfaces": ["eth0", "eth1"]},
            {"host": "backup_server", "interfaces": ["eth0", "eth1"]},
        ]
    """

    # Reserved keys that should not appear in task_params
    RESERVED_KEYS = {
        "duration",
        "interval",
        "viz",
        "max_log_size_task",
        "max_log_file_count_task",
        "max_data_size",
        "max_log_size_main",
        "max_log_file_count_main",
        "disable_health",
        "health_interval",
        "config",
        "list",
        "task",
        "module",
        "enable_viz",
        "display",
    }

    # Common task execution parameters
    duration: str = "60"
    interval: str = "5"
    enable_viz: bool = False
    password: str = ""
    # Per-task limits (moved from RunConfig)
    # When a log file reaches max size, it rotates (creates .1, .2, etc for long-running tasks)
    max_log_size_task_mb: int = 5  # Triggers rotation when task log hits this size
    max_log_file_count_task: int = 10  # Keep this many rotated task log files
    max_data_size_mb: int = 10

    # Task-specific configuration (from YAML tasks.<name> or CLI args)
    task_params: dict[str, Any] = field(default_factory=dict)

    # Reference to global runner config (includes hosts)
    run_config: RunConfig = field(default_factory=RunConfig)

    # Display configurations for metrics/visualization
    display: dict = field(default_factory=dict)

    # Convenience properties to access run config
    @property
    def max_log_size_main_mb(self) -> int:
        """Maximum log size for main runner in MB."""
        return self.run_config.max_log_size_main_mb

    @property
    def enable_runner_health(self) -> bool:
        """Whether local host health monitoring is enabled."""
        return self.run_config.enable_runner_health

    @property
    def runner_health_interval(self) -> int:
        """Local host health check interval in seconds."""
        return self.run_config.runner_health_interval

    def get_host(self, host_id: str) -> HostDefinition:
        """Get host configuration by ID from global run config.

        Example:
            server = config.get_host("server1")
            # Access directly: server.host, server.user, server.port
            # Or convert: server.as_string() -> "user@host:port"

        Returns:
            HostDefinition object (raises KeyError if not found)
        """
        host_dict = self.run_config.hosts.get(host_id)
        if not host_dict:
            raise KeyError(f"Host '{host_id}' not found in run config hosts.")
        return HostDefinition.from_dict(host_dict)

    def get_host_param(self, param_name: str) -> HostDefinition | None:
        """Get host from task_params by parameter name.

        Example:
            # For task_params["remote_host"] = "server1"
            remote = config.get_host_param("remote_host")

        Returns:
            HostDefinition object (or None if param not set or host not found)
        """
        host_id = self.task_params.get(param_name)
        if host_id:
            return self.get_host(host_id)
        return None

    @classmethod
    def from_args(
        cls,
        args,
        top_defaults: dict[str, Any] | None = None,
        task_defaults: dict[str, Any] | None = None,
        run_config: RunConfig | None = None,
        task_ns: object | None = None,
    ):
        """Create TaskConfig from argparse namespace and task-specific config.

        Args:
            args: Top-level CLI argparse namespace
            top_defaults: Top-level YAML defaults (yaml_data["defaults"])
            task_defaults: Task-specific YAML defaults (yaml_data["tasks"][task_name])
            run_config: Global RunConfig (must be provided with hosts already set)
            task_ns: Parsed task-specific CLI namespace from task.add_cli_args

        Returns:
            TaskConfig with all sources merged according to precedence rules.
        """
        top_defaults = top_defaults or {}
        task_defaults = task_defaults or {}

        # RunConfig must be provided by caller (from config_loader or manually created)
        if run_config is None:
            raise ValueError(
                "run_config is required. Use config_loader.load_project_config() "
                "to create RunConfig from YAML, then pass it to from_args()."
            )

        # Merge all config sources using centralized precedence rules
        program_defaults = {
            "duration": cls.duration,
            "interval": cls.interval,
            "viz": cls.enable_viz,
            "max_log_size_task": cls.max_log_size_task_mb,
            "max_log_file_count_task": cls.max_log_file_count_task,
            "max_data_size": cls.max_data_size_mb,
        }
        merged = merge_config_sources(
            args, task_ns, top_defaults, task_defaults, program_defaults
        )

        # Extract execution parameters from merged config
        duration = str(merged.get("duration", "60"))
        interval = str(merged.get("interval", "5"))
        enable_viz = bool(merged.get("viz", False))
        max_log_size_task_mb = int(merged.get("max_log_size_task", 5))
        max_log_file_count_task = int(merged.get("max_log_file_count_task", 10))
        max_data_size_mb = int(merged.get("max_data_size", 10))

        # Extract display config - use merged result which includes top-level defaults
        display = merged.get("display", {})

        # Build task_params from merged config (exclude reserved execution keys)
        task_params = {k: v for k, v in merged.items() if k not in cls.RESERVED_KEYS}

        return cls(
            duration=duration,
            interval=interval,
            enable_viz=enable_viz,
            max_log_size_task_mb=max_log_size_task_mb,
            max_log_file_count_task=max_log_file_count_task,
            max_data_size_mb=max_data_size_mb,
            task_params=task_params,
            run_config=run_config,
            display=display,
        )
