"""Vehicle listing and VIN resolution."""

from __future__ import annotations

import os
from typing import Any

from tyta.core.client import ToyotaClient
from tyta.core.config import load_config, save_config

# ---------------------------------------------------------------------------
# Endpoint constants
# ---------------------------------------------------------------------------

VEHICLE_GUID_ENDPOINT = "/v2/vehicle/guid"

# ---------------------------------------------------------------------------
# Exceptions
# ---------------------------------------------------------------------------


class VehicleError(Exception):
    """Base class for vehicle-related errors."""

    def __init__(self, code: str, message: str) -> None:
        super().__init__(message)
        self.code = code
        self.message = message


class NoVehiclesError(VehicleError):
    """Raised when account has no linked vehicles."""

    def __init__(self) -> None:
        super().__init__("no_vehicles", "No vehicles are linked to this account.")


class VehicleNotFoundError(VehicleError):
    """Raised when specified VIN is not in the account."""

    def __init__(self, vin: str) -> None:
        super().__init__(
            "vehicle_not_found",
            f"Vehicle with VIN '{vin}' was not found in your account.",
        )


# ---------------------------------------------------------------------------
# Vehicle listing
# ---------------------------------------------------------------------------


async def list_vehicles(client: ToyotaClient) -> list[dict[str, Any]]:
    """Fetch all vehicles linked to the account.

    Filters out entries with a null or missing VIN before returning.

    Args:
        client: Authenticated ToyotaClient instance.

    Returns:
        List of vehicle dicts. Each dict contains at minimum a ``vin`` key.

    Raises:
        ToyotaApiError: If the API returns a non-2xx response.

    """
    response = await client.get(VEHICLE_GUID_ENDPOINT)
    raw_list: list[Any] = response.get("payload") or []
    # Filter out None entries and entries with no VIN
    vehicles = [v for v in raw_list if v is not None and v.get("vin")]
    return vehicles


# ---------------------------------------------------------------------------
# VIN resolution
# ---------------------------------------------------------------------------


async def resolve_vin(
    client: ToyotaClient,
    vin_flag: str | None = None,
    config_path: str | None = None,
) -> tuple[str, list[str]]:
    """Resolve the VIN to use for a vehicle command.

    Resolution order:
      1. ``vin_flag`` argument (from ``--vin`` CLI flag)
      2. ``TOYOTA_VIN`` environment variable
      3. ``default_vin`` from config file
      4. Auto-select if exactly one vehicle — emits a warning

    Args:
        client: Authenticated ToyotaClient instance.
        vin_flag: Explicit VIN from ``--vin`` CLI flag.
        config_path: Override path to config file.

    Returns:
        Tuple of (vin, warnings). ``warnings`` is a list of warning strings;
        it is non-empty only when auto-selection occurred.

    Raises:
        NoVehiclesError: If the account has no vehicles.
        VehicleNotFoundError: If a VIN was supplied but is not in the account.

    """
    warnings: list[str] = []

    # Step 1: --vin flag
    if vin_flag:
        await _validate_vin(client, vin_flag)
        return vin_flag, warnings

    # Step 2: TOYOTA_VIN env var
    env_vin = os.environ.get("TOYOTA_VIN")
    if env_vin:
        await _validate_vin(client, env_vin)
        return env_vin, warnings

    # Step 3: config default_vin
    config = load_config(
        path=None if config_path is None else __import__("pathlib").Path(config_path)
    )
    config_vin = config.get("default_vin")
    if config_vin:
        await _validate_vin(client, config_vin)
        return config_vin, warnings

    # Step 4: auto-select single vehicle
    vehicles = await list_vehicles(client)
    if not vehicles:
        raise NoVehiclesError()

    if len(vehicles) == 1:
        vin = vehicles[0]["vin"]
        warnings.append(f"No default vehicle set, using only available vehicle: VIN {vin}")
        return vin, warnings

    # Multiple vehicles and no VIN specified
    raise VehicleNotFoundError("(none specified)")


async def _validate_vin(client: ToyotaClient, vin: str) -> None:
    """Validate that a VIN exists in the account.

    Args:
        client: Authenticated ToyotaClient instance.
        vin: VIN to validate.

    Raises:
        VehicleNotFoundError: If the VIN is not found.
        NoVehiclesError: If the account has no vehicles.

    """
    vehicles = await list_vehicles(client)
    if not vehicles:
        raise NoVehiclesError()
    vins = {v["vin"] for v in vehicles}
    if vin not in vins:
        raise VehicleNotFoundError(vin)


# ---------------------------------------------------------------------------
# Single vehicle lookup
# ---------------------------------------------------------------------------


async def get_vehicle(client: ToyotaClient, vin: str) -> dict[str, Any]:
    """Fetch the full vehicle record for a specific VIN.

    Args:
        client: Authenticated ToyotaClient instance.
        vin: VIN to look up.

    Returns:
        The raw vehicle dict from the API response.

    Raises:
        VehicleNotFoundError: If the VIN is not found.
        NoVehiclesError: If the account has no vehicles.
        ToyotaApiError: If the API returns a non-2xx response.

    """
    vehicles = await list_vehicles(client)
    if not vehicles:
        raise NoVehiclesError()
    for v in vehicles:
        if v.get("vin") == vin:
            return v
    raise VehicleNotFoundError(vin)


# ---------------------------------------------------------------------------
# Default VIN persistence
# ---------------------------------------------------------------------------


async def set_default_vin(
    client: ToyotaClient,
    vin: str,
    config_path: str | None = None,
) -> None:
    """Validate VIN against API, then write it as default_vin in config.

    Args:
        client: Authenticated ToyotaClient instance.
        vin: VIN to set as default.
        config_path: Override path to config file.

    Raises:
        VehicleNotFoundError: If the VIN is not in the account.
        NoVehiclesError: If the account has no vehicles.

    """
    await _validate_vin(client, vin)
    import pathlib

    path = pathlib.Path(config_path) if config_path else None
    config = load_config(path)
    config["default_vin"] = vin
    save_config(config, path)
