"""tyta remote subcommands: lock, unlock, horn, headlights, hazard, find."""

from __future__ import annotations

import asyncio
import json
import sys
from typing import Annotated

import typer

from tyta.cli import render
from tyta.core import auth as core_auth
from tyta.core import config as core_config
from tyta.core import envelope as env
from tyta.core import remote as core_remote
from tyta.core import status as core_status
from tyta.core import vehicles as core_vehicles
from tyta.core.client import ToyotaApiError, ToyotaClient

app = typer.Typer(
    name="remote",
    help="Send remote commands to your vehicle, or show remote status.",
    no_args_is_help=False,
    invoke_without_command=True,
)

# Nested apps for headlights and hazard subcommand groups
headlights_app = typer.Typer(
    name="headlights",
    help="Control headlights.",
    no_args_is_help=True,
)
hazard_app = typer.Typer(
    name="hazard",
    help="Control hazard lights.",
    no_args_is_help=True,
)

app.add_typer(headlights_app, name="headlights")
app.add_typer(hazard_app, name="hazard")


def _get_state() -> dict:
    from tyta.cli.main import _get_state as _gs

    return _gs()


def _is_json_mode() -> bool:
    return _get_state().get("json", False)


def _load_config() -> dict:
    config_path = core_config.get_config_path(_get_state().get("config_file"))
    try:
        return core_config.load_config(config_path)
    except Exception:  # noqa: BLE001
        return {}


def _emit_and_exit(data: dict, exit_code: int = 0, cli_command: str = "") -> None:
    json_mode = _is_json_mode()
    if exit_code != 0:
        render.render_error(data, json_mode)
    else:
        render.render_output(data, json_mode, cli_command, config=_load_config())
    sys.exit(exit_code)


@app.callback(invoke_without_command=True)
def remote_callback(
    ctx: typer.Context,
    vin: Annotated[
        str | None,
        typer.Option("--vin", help="VIN of the vehicle.", show_default=False),
    ] = None,
) -> None:
    """Show remote status, or run a remote subcommand."""
    if ctx.invoked_subcommand is None:
        asyncio.run(_remote_status(vin))


async def _remote_status(vin_flag: str | None) -> None:
    """Fetch and display remote/lock status."""
    state = _get_state()
    debug = state.get("debug", False)
    auth_path = core_auth.get_auth_path(state.get("auth_file"))
    config_path = state.get("config_file")

    try:
        token_data = await core_auth.get_valid_token(auth_path)
    except core_auth.NotAuthenticatedError as exc:
        _emit_and_exit(env.error(exc.code, exc.message), exit_code=1)
        return
    except core_auth.TokenExpiredError as exc:
        _emit_and_exit(env.error(exc.code, exc.message), exit_code=1)
        return
    except json.JSONDecodeError:
        if debug:
            import traceback

            traceback.print_exc(file=sys.stderr)
        _emit_and_exit(env.error("invalid_config", "Token file is malformed JSON."), exit_code=1)
        return

    client = ToyotaClient(
        access_token=token_data["access_token"],
        uuid=token_data["uuid"],
    )

    try:
        vin, warnings = await core_vehicles.resolve_vin(
            client, vin_flag=vin_flag, config_path=config_path
        )
    except core_vehicles.NoVehiclesError as exc:
        _emit_and_exit(env.error(exc.code, exc.message), exit_code=1)
        return
    except core_vehicles.VehicleNotFoundError as exc:
        _emit_and_exit(env.error(exc.code, exc.message), exit_code=1)
        return
    except ToyotaApiError as exc:
        if debug:
            import traceback

            traceback.print_exc(file=sys.stderr)
        _emit_and_exit(env.error("api_error", f"Toyota API error: {exc.message}"), exit_code=1)
        return

    try:
        payload = await core_status.get_remote_status(client, vin)
    except ToyotaApiError as exc:
        if debug:
            import traceback

            traceback.print_exc(file=sys.stderr)
        _emit_and_exit(env.error("api_error", f"Toyota API error: {exc.message}"), exit_code=1)
        return
    except Exception as exc:
        if debug:
            import traceback

            traceback.print_exc(file=sys.stderr)
        _emit_and_exit(env.error("unknown_error", str(exc)), exit_code=1)
        return

    _emit_and_exit(
        env.ok({"vin": vin, "remote_status": payload}, warnings=warnings),
        cli_command="remote",
    )


async def _send(
    vin_flag: str | None, command: str, beep_count: int | None = None, cli_command: str = ""
) -> None:
    """Core helper for all remote commands."""
    state = _get_state()
    debug = state.get("debug", False)
    auth_path = core_auth.get_auth_path(state.get("auth_file"))
    config_path = state.get("config_file")

    try:
        token_data = await core_auth.get_valid_token(auth_path)
    except core_auth.NotAuthenticatedError as exc:
        _emit_and_exit(env.error(exc.code, exc.message), exit_code=1)
        return
    except core_auth.TokenExpiredError as exc:
        _emit_and_exit(env.error(exc.code, exc.message), exit_code=1)
        return
    except json.JSONDecodeError:
        if debug:
            import traceback

            traceback.print_exc(file=sys.stderr)
        _emit_and_exit(env.error("invalid_config", "Token file is malformed JSON."), exit_code=1)
        return

    client = ToyotaClient(
        access_token=token_data["access_token"],
        uuid=token_data["uuid"],
    )

    try:
        vin, warnings = await core_vehicles.resolve_vin(
            client, vin_flag=vin_flag, config_path=config_path
        )
    except core_vehicles.NoVehiclesError as exc:
        _emit_and_exit(env.error(exc.code, exc.message), exit_code=1)
        return
    except core_vehicles.VehicleNotFoundError as exc:
        _emit_and_exit(env.error(exc.code, exc.message), exit_code=1)
        return
    except ToyotaApiError as exc:
        if debug:
            import traceback

            traceback.print_exc(file=sys.stderr)
        _emit_and_exit(env.error("api_error", f"Toyota API error: {exc.message}"), exit_code=1)
        return

    try:
        await core_remote.send_command(client, vin=vin, command=command, beep_count=beep_count)
    except ToyotaApiError as exc:
        if debug:
            import traceback

            traceback.print_exc(file=sys.stderr)
        _emit_and_exit(env.error("api_error", f"Toyota API error: {exc.message}"), exit_code=1)
        return
    except Exception as exc:
        if debug:
            import traceback

            traceback.print_exc(file=sys.stderr)
        _emit_and_exit(env.error("unknown_error", str(exc)), exit_code=1)
        return

    response = env.ok(
        {
            "status": "command_queued",
            "message": f"{command.replace('-', ' ').title()} command sent. The vehicle may take a moment to respond.",
        },
        warnings=warnings,
    )
    _emit_and_exit(response, cli_command=cli_command)


# ---------------------------------------------------------------------------
# lock / unlock / horn / find
# ---------------------------------------------------------------------------


@app.command("lock")
def remote_lock(
    vin: Annotated[
        str | None,
        typer.Option("--vin", help="VIN of the vehicle.", show_default=False),
    ] = None,
) -> None:
    """Lock the vehicle doors."""
    asyncio.run(_send(vin, core_remote.CMD_DOOR_LOCK, cli_command="remote lock"))


@app.command("unlock")
def remote_unlock(
    vin: Annotated[
        str | None,
        typer.Option("--vin", help="VIN of the vehicle.", show_default=False),
    ] = None,
) -> None:
    """Unlock the vehicle doors."""
    asyncio.run(_send(vin, core_remote.CMD_DOOR_UNLOCK, cli_command="remote unlock"))


@app.command("horn")
def remote_horn(
    vin: Annotated[
        str | None,
        typer.Option("--vin", help="VIN of the vehicle.", show_default=False),
    ] = None,
) -> None:
    """Sound the vehicle horn."""
    asyncio.run(_send(vin, core_remote.CMD_SOUND_HORN, cli_command="remote horn"))


@app.command("find")
def remote_find(
    vin: Annotated[
        str | None,
        typer.Option("--vin", help="VIN of the vehicle.", show_default=False),
    ] = None,
) -> None:
    """Locate the vehicle by flashing lights and sounding the horn."""
    asyncio.run(_send(vin, core_remote.CMD_FIND_VEHICLE, cli_command="remote find"))


# ---------------------------------------------------------------------------
# headlights on / off
# ---------------------------------------------------------------------------


@headlights_app.command("on")
def headlights_on(
    vin: Annotated[
        str | None,
        typer.Option("--vin", help="VIN of the vehicle.", show_default=False),
    ] = None,
) -> None:
    """Turn headlights on."""
    asyncio.run(_send(vin, core_remote.CMD_HEADLIGHT_ON, cli_command="remote headlights on"))


@headlights_app.command("off")
def headlights_off(
    vin: Annotated[
        str | None,
        typer.Option("--vin", help="VIN of the vehicle.", show_default=False),
    ] = None,
) -> None:
    """Turn headlights off."""
    asyncio.run(_send(vin, core_remote.CMD_HEADLIGHT_OFF, cli_command="remote headlights off"))


# ---------------------------------------------------------------------------
# hazard on / off
# ---------------------------------------------------------------------------


@hazard_app.command("on")
def hazard_on(
    vin: Annotated[
        str | None,
        typer.Option("--vin", help="VIN of the vehicle.", show_default=False),
    ] = None,
) -> None:
    """Turn hazard lights on."""
    asyncio.run(_send(vin, core_remote.CMD_HAZARD_ON, cli_command="remote hazard on"))


@hazard_app.command("off")
def hazard_off(
    vin: Annotated[
        str | None,
        typer.Option("--vin", help="VIN of the vehicle.", show_default=False),
    ] = None,
) -> None:
    """Turn hazard lights off."""
    asyncio.run(_send(vin, core_remote.CMD_HAZARD_OFF, cli_command="remote hazard off"))
