"""Input loader handlers for pipeline system.

This module provides handlers for loading data from various file formats.
All handlers follow the standard signature: (inputs, params, step_name) -> outputs.

Available Handlers:
    - input.file: Load from file with auto-format detection
    - input.vcd: Load VCD (Value Change Dump) files
    - input.wav: Load WAV audio files
    - input.csv: Load CSV data files
    - input.pcap: Load PCAP network capture files
    - input.binary: Load raw binary data
    - input.hdf5: Load HDF5 files
    - input.numpy: Load NumPy .npz files
    - input.tektronix: Load Tektronix oscilloscope files
    - input.rigol: Load Rigol oscilloscope files
    - input.sigrok: Load Sigrok/PulseView captures
    - input.tdms: Load NI TDMS files
    - input.touchstone: Load Touchstone S-parameter files
    - input.chipwhisperer: Load ChipWhisperer captures
    - input.multi_channel: Load all channels from multi-channel file
"""

from __future__ import annotations

from pathlib import Path
from typing import Any

from oscura.core.config.pipeline import PipelineExecutionError
from oscura.pipeline.handlers import register_handler

# Lazy imports to avoid circular dependencies
_loaders = None


def _get_loaders() -> Any:
    """Lazy import loaders module."""
    global _loaders
    if _loaders is None:
        import oscura.loaders as _loaders_module

        _loaders = _loaders_module
    return _loaders


@register_handler("input.file")
def handle_input_file(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load trace from file with auto-format detection.

    Parameters:
        path (str): File path to load
        format (str, optional): Format override (tektronix, rigol, csv, etc.)
        channel (str|int, optional): Channel name or index for multi-channel files
        lazy (bool, optional): Enable lazy loading for large files

    Outputs:
        trace: Loaded WaveformTrace, DigitalTrace, or IQTrace
        path: Absolute file path
        format: File extension
        metadata: Trace metadata dict
    """
    loaders = _get_loaders()

    path = params.get("path")
    if not path:
        raise PipelineExecutionError(
            "Missing required parameter 'path'. Suggestion: Add 'path: filename.wfm' to params",
            step_name=step_name,
        )

    format_override = params.get("format")
    channel = params.get("channel")
    lazy = params.get("lazy", False)

    try:
        trace = loaders.load(path, format=format_override, channel=channel, lazy=lazy)
    except FileNotFoundError as e:
        raise PipelineExecutionError(f"File not found: {path}", step_name=step_name) from e
    except Exception as e:
        raise PipelineExecutionError(f"Failed to load file: {e}", step_name=step_name) from e

    return {
        "trace": trace,
        "path": str(Path(path).resolve()),
        "format": Path(path).suffix,
        "metadata": trace.metadata.__dict__ if hasattr(trace, "metadata") else {},
    }


@register_handler("input.vcd")
def handle_input_vcd(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load VCD (Value Change Dump) file.

    Parameters:
        path (str): Path to VCD file
        signal_name (str, optional): Specific signal to extract

    Outputs:
        trace: Loaded DigitalTrace
        path: Absolute file path
        signals: List of available signal names
    """
    loaders = _get_loaders()

    path = params.get("path")
    if not path:
        raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)

    signal_name = params.get("signal_name")

    try:
        trace = loaders.load(path, format="vcd", channel=signal_name)
        # Get all signals if needed
        from oscura.loaders.vcd import load_vcd

        vcd_data = load_vcd(path)
        signals = list(vcd_data.keys()) if isinstance(vcd_data, dict) else []
    except Exception as e:
        raise PipelineExecutionError(f"Failed to load VCD: {e}", step_name=step_name) from e

    return {
        "trace": trace,
        "path": str(Path(path).resolve()),
        "signals": signals,
    }


@register_handler("input.wav")
def handle_input_wav(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load WAV audio file.

    Parameters:
        path (str): Path to WAV file
        channel (int, optional): Channel index (0 for mono/left, 1 for right)

    Outputs:
        trace: Loaded WaveformTrace
        path: Absolute file path
        sample_rate: Audio sample rate in Hz
        num_channels: Number of audio channels
    """
    loaders = _get_loaders()

    path = params.get("path")
    if not path:
        raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)

    channel = params.get("channel")

    try:
        trace = loaders.load(path, format="wav", channel=channel)
        sample_rate = trace.metadata.sample_rate
        # WAV files have num_channels in metadata
        num_channels = getattr(trace.metadata, "num_channels", 1)
    except Exception as e:
        raise PipelineExecutionError(f"Failed to load WAV: {e}", step_name=step_name) from e

    return {
        "trace": trace,
        "path": str(Path(path).resolve()),
        "sample_rate": sample_rate,
        "num_channels": num_channels,
    }


@register_handler("input.csv")
def handle_input_csv(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load CSV data file.

    Parameters:
        path (str): Path to CSV file
        time_column (str, optional): Name of time column
        data_column (str, optional): Name of data column
        delimiter (str, optional): CSV delimiter (default: ',')
        skip_rows (int, optional): Number of header rows to skip

    Outputs:
        trace: Loaded WaveformTrace
        path: Absolute file path
        columns: List of available column names
    """
    loaders = _get_loaders()

    path = params.get("path")
    if not path:
        raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)

    time_col = params.get("time_column")
    data_col = params.get("data_column")
    delimiter = params.get("delimiter", ",")
    skip_rows = params.get("skip_rows", 0)

    try:
        trace = loaders.load(
            path,
            format="csv",
            time_column=time_col,
            data_column=data_col,
            delimiter=delimiter,
            skip_rows=skip_rows,
        )
        # Try to get column names
        import pandas as pd

        df = pd.read_csv(path, delimiter=delimiter, skiprows=skip_rows, nrows=0)
        columns = list(df.columns)
    except Exception as e:
        raise PipelineExecutionError(f"Failed to load CSV: {e}", step_name=step_name) from e

    return {
        "trace": trace,
        "path": str(Path(path).resolve()),
        "columns": columns,
    }


@register_handler("input.pcap")
def handle_input_pcap(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load PCAP network capture file.

    Parameters:
        path (str): Path to PCAP file
        filter (str, optional): BPF filter expression

    Outputs:
        trace: Loaded trace with packet data
        path: Absolute file path
        packet_count: Number of packets
    """
    loaders = _get_loaders()

    path = params.get("path")
    if not path:
        raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)

    bpf_filter = params.get("filter")

    try:
        trace = loaders.load(path, format="pcap", filter=bpf_filter)
        # Count packets (trace data should be packet array)
        packet_count = len(trace.data) if hasattr(trace, "data") else 0
    except Exception as e:
        raise PipelineExecutionError(f"Failed to load PCAP: {e}", step_name=step_name) from e

    return {
        "trace": trace,
        "path": str(Path(path).resolve()),
        "packet_count": packet_count,
    }


@register_handler("input.binary")
def handle_input_binary(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load raw binary data file.

    Parameters:
        path (str): Path to binary file
        sample_rate (float): Sample rate in Hz
        dtype (str, optional): NumPy dtype (default: 'float32')
        offset (int, optional): Byte offset to start reading
        count (int, optional): Number of samples to read

    Outputs:
        trace: Loaded WaveformTrace
        path: Absolute file path
        num_samples: Number of samples loaded
    """
    loaders = _get_loaders()

    path = params.get("path")
    sample_rate = params.get("sample_rate")

    if not path:
        raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
    if not sample_rate:
        raise PipelineExecutionError(
            "Missing required parameter 'sample_rate'", step_name=step_name
        )

    dtype = params.get("dtype", "float32")
    offset = params.get("offset", 0)
    count = params.get("count", -1)

    try:
        trace = loaders.load_binary(
            path, sample_rate=sample_rate, dtype=dtype, offset=offset, count=count
        )
    except Exception as e:
        raise PipelineExecutionError(f"Failed to load binary: {e}", step_name=step_name) from e

    return {
        "trace": trace,
        "path": str(Path(path).resolve()),
        "num_samples": len(trace.data),
    }


@register_handler("input.hdf5")
def handle_input_hdf5(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load HDF5 file.

    Parameters:
        path (str): Path to HDF5 file
        dataset (str, optional): Dataset path within HDF5 file

    Outputs:
        trace: Loaded WaveformTrace
        path: Absolute file path
        datasets: List of available dataset paths
    """
    loaders = _get_loaders()

    path = params.get("path")
    if not path:
        raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)

    dataset = params.get("dataset")

    try:
        trace = loaders.load(path, format="hdf5", dataset=dataset)
        # Get available datasets
        import h5py

        with h5py.File(path, "r") as f:

            def list_datasets(group: Any, prefix: str = "") -> list[str]:
                datasets = []
                for key in group:
                    item = group[key]
                    if isinstance(item, h5py.Dataset):
                        datasets.append(f"{prefix}/{key}")
                    elif isinstance(item, h5py.Group):
                        datasets.extend(list_datasets(item, f"{prefix}/{key}"))
                return datasets

            datasets = list_datasets(f)
    except Exception as e:
        raise PipelineExecutionError(f"Failed to load HDF5: {e}", step_name=step_name) from e

    return {
        "trace": trace,
        "path": str(Path(path).resolve()),
        "datasets": datasets,
    }


@register_handler("input.numpy")
def handle_input_numpy(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load NumPy .npz file.

    Parameters:
        path (str): Path to .npz file
        array_name (str, optional): Name of array to load (default: first array)

    Outputs:
        trace: Loaded WaveformTrace
        path: Absolute file path
        arrays: List of available array names
    """
    loaders = _get_loaders()

    path = params.get("path")
    if not path:
        raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)

    array_name = params.get("array_name")

    try:
        trace = loaders.load(path, format="numpy", array_name=array_name)
        # Get available arrays
        import numpy as np

        with np.load(path) as npz:
            arrays = list(npz.keys())
    except Exception as e:
        raise PipelineExecutionError(f"Failed to load NumPy: {e}", step_name=step_name) from e

    return {
        "trace": trace,
        "path": str(Path(path).resolve()),
        "arrays": arrays,
    }


@register_handler("input.tektronix")
def handle_input_tektronix(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load Tektronix oscilloscope file (.wfm, .isf).

    Parameters:
        path (str): Path to Tektronix file
        channel (str|int, optional): Channel to load

    Outputs:
        trace: Loaded WaveformTrace
        path: Absolute file path
    """
    loaders = _get_loaders()

    path = params.get("path")
    if not path:
        raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)

    channel = params.get("channel")

    try:
        trace = loaders.load(path, format="tektronix", channel=channel)
    except Exception as e:
        raise PipelineExecutionError(
            f"Failed to load Tektronix file: {e}", step_name=step_name
        ) from e

    return {
        "trace": trace,
        "path": str(Path(path).resolve()),
    }


@register_handler("input.rigol")
def handle_input_rigol(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load Rigol oscilloscope file.

    Parameters:
        path (str): Path to Rigol file
        channel (str|int, optional): Channel to load

    Outputs:
        trace: Loaded WaveformTrace
        path: Absolute file path
    """
    loaders = _get_loaders()

    path = params.get("path")
    if not path:
        raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)

    channel = params.get("channel")

    try:
        trace = loaders.load(path, format="rigol", channel=channel)
    except Exception as e:
        raise PipelineExecutionError(f"Failed to load Rigol file: {e}", step_name=step_name) from e

    return {
        "trace": trace,
        "path": str(Path(path).resolve()),
    }


@register_handler("input.sigrok")
def handle_input_sigrok(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load Sigrok/PulseView capture.

    Parameters:
        path (str): Path to Sigrok file
        channel (str|int, optional): Channel to load

    Outputs:
        trace: Loaded DigitalTrace
        path: Absolute file path
    """
    loaders = _get_loaders()

    path = params.get("path")
    if not path:
        raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)

    channel = params.get("channel")

    try:
        trace = loaders.load(path, format="sigrok", channel=channel)
    except Exception as e:
        raise PipelineExecutionError(f"Failed to load Sigrok file: {e}", step_name=step_name) from e

    return {
        "trace": trace,
        "path": str(Path(path).resolve()),
    }


@register_handler("input.tdms")
def handle_input_tdms(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load National Instruments TDMS file.

    Parameters:
        path (str): Path to TDMS file
        group (str, optional): Group name
        channel (str, optional): Channel name

    Outputs:
        trace: Loaded WaveformTrace
        path: Absolute file path
    """
    loaders = _get_loaders()

    path = params.get("path")
    if not path:
        raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)

    group = params.get("group")
    channel = params.get("channel")

    try:
        trace = loaders.load(path, format="tdms", group=group, channel=channel)
    except Exception as e:
        raise PipelineExecutionError(f"Failed to load TDMS file: {e}", step_name=step_name) from e

    return {
        "trace": trace,
        "path": str(Path(path).resolve()),
    }


@register_handler("input.touchstone")
def handle_input_touchstone(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load Touchstone S-parameter file (.s1p, .s2p, etc.).

    Parameters:
        path (str): Path to Touchstone file

    Outputs:
        sparams: S-parameter data object
        path: Absolute file path
        num_ports: Number of ports
    """
    loaders = _get_loaders()

    path = params.get("path")
    if not path:
        raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)

    try:
        sparams = loaders.load_touchstone(path)
        num_ports = getattr(sparams, "num_ports", 0)
    except Exception as e:
        raise PipelineExecutionError(
            f"Failed to load Touchstone file: {e}", step_name=step_name
        ) from e

    return {
        "sparams": sparams,
        "path": str(Path(path).resolve()),
        "num_ports": num_ports,
    }


@register_handler("input.chipwhisperer")
def handle_input_chipwhisperer(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load ChipWhisperer capture.

    Parameters:
        path (str): Path to ChipWhisperer file

    Outputs:
        trace: Loaded WaveformTrace
        path: Absolute file path
    """
    loaders = _get_loaders()

    path = params.get("path")
    if not path:
        raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)

    try:
        trace = loaders.load(path, format="chipwhisperer")
    except Exception as e:
        raise PipelineExecutionError(
            f"Failed to load ChipWhisperer file: {e}", step_name=step_name
        ) from e

    return {
        "trace": trace,
        "path": str(Path(path).resolve()),
    }


@register_handler("input.multi_channel")
def handle_input_multi_channel(
    inputs: dict[str, Any], params: dict[str, Any], step_name: str
) -> dict[str, Any]:
    """Load all channels from a multi-channel file.

    Parameters:
        path (str): Path to multi-channel file
        format (str, optional): Format override

    Outputs:
        channels: Dict mapping channel names to traces
        path: Absolute file path
        channel_names: List of channel names
    """
    loaders = _get_loaders()

    path = params.get("path")
    if not path:
        raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)

    format_override = params.get("format")

    try:
        # Load all channels
        channels = loaders.load_all_channels(path, format=format_override)
        channel_names = list(channels.keys())
    except Exception as e:
        raise PipelineExecutionError(
            f"Failed to load multi-channel file: {e}", step_name=step_name
        ) from e

    return {
        "channels": channels,
        "path": str(Path(path).resolve()),
        "channel_names": channel_names,
    }
