from __future__ import annotations

import json
import sys
from pathlib import Path

import typer

from remotivelabs.cli.broker.lib.client import SignalIdentifier
from remotivelabs.cli.broker.typer import ApiKeyOption, BrokerUrlOption
from remotivelabs.cli.connect.protopie import protopie as ppie
from remotivelabs.cli.typer_ext import typer_utils
from remotivelabs.cli.utils.console import print_hint

app = typer_utils.create_typer()


def _parse_signal_arg(sig: str) -> SignalIdentifier:
    """Parse a signal argument in format 'namespace:signal_name'."""
    parts = sig.split(":")
    if len(parts) != 2:
        print_hint(f"--signal must have format namespace:signal ({sig})")
        sys.exit(1)
    return SignalIdentifier(namespace=parts[0], name=parts[1])


def _load_config(config_path: Path) -> tuple[list[SignalIdentifier], dict[str, list[str]]]:
    """
    Load signals and mappings from a config file.

    Returns:
        Tuple of (signals to subscribe to, signal name mappings)
    """
    with open(config_path, encoding="utf8") as f:
        data = json.load(f)

    subscription = data.get("subscription", {})
    signals: list[SignalIdentifier] = []
    mappings: dict[str, list[str]] = {}

    for signal_name, config in subscription.items():
        namespace = config.get("namespace", "")
        signals.append(SignalIdentifier(namespace=namespace, name=signal_name))

        # Handle mapTo for signal name remapping
        map_to = config.get("mapTo")
        if map_to:
            # Normalize to list (config allows single string or list)
            mappings[signal_name] = map_to if isinstance(map_to, list) else [map_to]

    return signals, mappings


@app.command()
def protopie(  # noqa: PLR0913
    config: Path | None = typer.Option(
        None,
        exists=True,
        file_okay=True,
        dir_okay=False,
        writable=False,
        readable=True,
        resolve_path=True,
        help="Configuration file with signal subscriptions and mapping if needed",
    ),
    signal: list[str] = typer.Option([], help="Signal names to subscribe to, mandatory when not using script"),
    signal_name_expression: str | None = typer.Option(
        None, help='[Experimental] String expression to rename signal names, i.e \'lower().replace(".","_")\''
    ),
    changed_values_only: bool = typer.Option(
        True, help="Only receive signal when its value is changed to minimize amount of data received"
    ),
    broker_url: str = BrokerUrlOption,
    api_key: str | None = ApiKeyOption,
    pp_connect_host: str = typer.Option("http://localhost:9981", help="ProtoPie Connect URL"),
) -> None:
    """
    ProtoPie Connect bridge-app to connect signals with RemotiveBroker

    Subscribe to signals and send signal values to your Pie in a simple way. You can subscribe to signals from command line
    using --signal or use --config to use a json configuration file.

    ```
    $ remotive connect protopie --signal vss:Vehicle.Chassis.SteeringWheel.Angle --signal vss:Vehicle.Speed
    ```

    You can use a configuration file for this if you have many signals, want to share the configuration or you want
    custom mapping of the signal name.
    ```
    $ remotive connect protopie --config my-protopie-config.json
    ```

    Sample my-protopie-config.json
    ```
    {
      "subscription": {
        "Vehicle.CurrentLocation.Heading": {
          "namespace": "vss"
        },
        "Vehicle.Speed": {
          "namespace": "vss",
           "mapTo": ["Speed", "VehicleSpeed"]
        }
      }
    }
    ```
    For simple changes to signal names its possible to use a simple string expression that will be applied to all signal
    names before its published to ProtoPie connect, i.e replacing chars or substring to match variables in Pie. This is
    intended for simple use cases when you do not have a configuration file, otherwise we recommend using the mapTo
    field in the configuration.

    Supported transformations: lower(), upper(), replace("old", "new")

    This will replace all occurrences of . (dot) with _ (underscore) and lowercase the signal name
    ```
    $ remotive connect protopie --signal vss:Vehicle.Chassis.SteeringWheel.Angle --signal-name-expression 'replace(".", "_").lower()'
    ```
    """
    if signal and config is not None:
        print_hint("You must choose either --signal or --config, not both")
        sys.exit(1)

    if not signal and config is None:
        print_hint("You must choose either --signal or --config")
        sys.exit(1)

    # Parse signals and mappings from either CLI args or config file
    if signal:
        signals_to_subscribe = [_parse_signal_arg(s) for s in signal]
        signal_mappings: dict[str, list[str]] = {}
    else:
        assert config is not None  # mypy hint
        signals_to_subscribe, signal_mappings = _load_config(config)

    ppie.do_connect(
        address=pp_connect_host,
        broker_url=broker_url,
        api_key=api_key,
        signals=signals_to_subscribe,
        signal_mappings=signal_mappings,
        expression=signal_name_expression,
        on_change_only=changed_values_only,
    )
