"""Models for SmartThings API."""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime  # noqa: TC003
from enum import StrEnum
from typing import Any

from mashumaro import field_options
from mashumaro.mixins.orjson import DataClassORJSONMixin

from .attribute import Attribute
from .capability import Capability
from .const import LOGGER

ALREADY_LOGGED_CAPABILITIES = set()


@dataclass
class InstalledApp(DataClassORJSONMixin):
    """Installed app model."""

    installed_app_id: str = field(metadata=field_options(alias="installedAppId"))
    location_id: str = field(metadata=field_options(alias="locationId"))
    app_id: str = field(metadata=field_options(alias="appId"))
    display_name: str | None = field(
        metadata=field_options(alias="displayName"), default=None
    )


@dataclass
class BaseLocation(DataClassORJSONMixin):
    """Base location model."""

    location_id: str = field(metadata=field_options(alias="locationId"))
    name: str


@dataclass
class Location(BaseLocation):
    """Location model."""

    country_code: str = field(metadata=field_options(alias="countryCode"))
    latitude: float
    longitude: float
    region_radius: int = field(metadata=field_options(alias="regionRadius"))
    temperature_scale: str = field(metadata=field_options(alias="temperatureScale"))


@dataclass
class Room(DataClassORJSONMixin):
    """Room model."""

    room_id: str = field(metadata=field_options(alias="roomId"))
    location_id: str = field(metadata=field_options(alias="locationId"))
    name: str


class MatterProvisioningState(StrEnum):
    """Matter provisioning state."""

    PROVISIONED = "PROVISIONED"
    TYPED = "TYPED"
    DRIVER_SWITCH = "DRIVER_SWITCH"
    NONFUNCTIONAL = "NONFUNCTIONAL"


class MatterListeningType(StrEnum):
    """Matter listening type."""

    ALWAYS = "ALWAYS"
    SLEEPY = "SLEEPY"


class MatterSupportedNetworkInterfaces(StrEnum):
    """Matter supported network interfaces."""

    THREAD = "THREAD"
    WIFI = "WIFI"
    ETHERNET = "ETHERNET"


class DeviceType(StrEnum):
    """Device type."""

    BLE = "BLE"
    BLE_D2D = "BLE_D2D"
    DTH = "DTH"
    ENDPOINT_APP = "ENDPOINT_APP"
    GROUP = "GROUP"
    HUB = "HUB"
    IR = "IR"
    IR_OCF = "IR_OCF"
    LAN = "LAN"
    MATTER = "MATTER"
    MOBILE = "MOBILE"
    MQTT = "MQTT"
    OCF = "OCF"
    PENGYOU = "PENGYOU"
    SAFER = "SAFER"
    SHP = "SHP"
    VIDEO = "VIDEO"
    VIPER = "VIPER"
    VIRTUAL = "VIRTUAL"
    WATCH = "WATCH"
    ZIGBEE = "ZIGBEE"
    ZWAVE = "ZWAVE"
    EDGE_CHILD = "EDGE_CHILD"


class DeviceNetworkType(StrEnum):
    """Device network type."""

    ZWAVE = "ZWAVE"


class Category(StrEnum):
    """Category model."""

    AIR_CONDITIONER = "AirConditioner"
    AIR_PURIFIER = "AirPurifier"
    AIR_QUALITY_DETECTOR = "AirQualityDetector"
    AIR_QUALITY_DETECTOR_2 = "Air Quality Detector"
    BATTERY = "Battery"
    BED = "Bed"
    BIDET = "Bidet"
    BLIND = "Blind"
    BLU_RAY_PLAYER = "BluRayPlayer"
    BLUETOOTH_CAR_SPEAKER = "BluetoothCarSpeaker"
    BLUETOOTH_TRACKER = "BluetoothTracker"
    BRIDGES = "Bridges"
    BUTTON = "Button"
    CAMERA = "Camera"
    CAR = "Car"
    CHARGER = "Charger"
    CLOTHING_CARE_MACHINE = "ClothingCareMachine"
    COFFEE_MAKER = "CoffeeMaker"
    CONTACT_SENSOR = "ContactSensor"
    COOKTOP = "Cooktop"
    CUBE_REFRIGERATOR = "CubeRefrigerator"
    CURB_POWER_METER = "CurbPowerMeter"
    DEHUMIDIFIER = "Dehumidifier"
    DISHWASHER = "Dishwasher"
    DOOR = "Door"
    DOOR_BELL = "DoorBell"
    DRYER = "Dryer"
    EARBUDS = "Earbuds"
    ELEVATOR = "Elevator"
    FAN = "Fan"
    FEEDER = "Feeder"
    FITNESS_MAT = "FitnessMat"
    FLASHLIGHT = "Flashlight"
    GARAGE_DOOR = "GarageDoor"
    GAS_VALVE = "GasValve"
    GAS_METER = "GasMeter"
    GENERIC_SENSOR = "GenericSensor"
    HEALTH_TRACKER = "HealthTracker"
    HEATED_MATTRESS_PAD = "Heatedmattresspad"
    HOME_THEATER = "HomeTheater"
    HUB = "Hub"
    HUMIDIFIER = "Humidifier"
    HUMIDITY_SENSOR = "HumiditySensor"
    IR_REMOTE = "IrRemote"
    IRRIGATION = "Irrigation"
    KIMCHI_REFRIGERATOR = "KimchiRefrigerator"
    KITCHEN_HOOD = "KitchenHood"
    LEAK_SENSOR = "LeakSensor"
    LIGHT = "Light"
    LIGHT_SENSOR = "LightSensor"
    MICRO_FIBER_FILTER = "MicroFiberFilter"
    MICROWAVE = "Microwave"
    MOBILE = "Mobile"
    MOBILE_PRESENCE = "MobilePresence"
    MOTION_SENSOR = "MotionSensor"
    MULTI_FUNCTIONAL_SENSOR = "MultiFunctionalSensor"
    NETWORK_AUDIO = "NetworkAudio"
    NETWORKING = "Networking"
    OTHER = "Other"
    OTHERS = "Others"
    OVEN = "Oven"
    POWER_MEASUREMENT_SENSOR = "PowerMeasurementSensor"
    POWER_METER = "PowerMeter"
    PRESENCE_SENSOR = "PresenceSensor"
    PRESENCE_SENSOR_2 = "Presence Sensor"
    PRINTER = "Printer"
    PRINTER_MULTI_FUNCTION = "PrinterMultiFunction"
    PROJECTOR = "Projector"
    PUMP = "Pump"
    RAIN_SENSOR = "RainSensor"
    RANGE = "Range"
    RECEIVER = "Receiver"
    REFRIGERATOR = "Refrigerator"
    REMOTE_CONTROLLER = "RemoteController"
    RICE_COOKER = "RiceCooker"
    ROBOT_CLEANER = "RobotCleaner"
    SCALE_TO_MEASURE_MASS_OF_HUMAN_BODY = "ScaleToMeasureMassOfHumanBody"
    SCANNER = "Scanner"
    SECURITY_PANEL = "SecurityPanel"
    SET_TOP = "SetTop"
    SHADE = "Shade"
    SHOES_CARE_MACHINE = "ShoesCareMachine"
    SHOWER = "Shower"
    SIREN = "Siren"
    SMART_LOCK = "SmartLock"
    SMART_MONITOR = "SmartMonitor"
    SMART_PLUG = "SmartPlug"
    SMOKE_DETECTOR = "SmokeDetector"
    SMOKE_DETECTOR_2 = "Smoke Detector"
    SOLAR_PANEL = "SolarPanel"
    SOUND_SENSOR = "SoundSensor"
    SOUND_MACHINE = "SoundMachine"
    SPEAKER = "Speaker"
    STICK_VACUUM_CLEANER = "StickVacuumCleaner"
    STORAGE = "Storage"
    STOVE = "Stove"
    SWITCH = "Switch"
    TELEVISION = "Television"
    TEMP_HUMIDITY_SENSOR = "TempHumiditySensor"
    TEMP_SENSOR = "TempSensor"
    THERMOSTAT = "Thermostat"
    TRACKER = "Tracker"
    UPNP_MEDIA_RENDERER = "UPnPMediaRenderer"
    VENT = "Vent"
    VISION_SENSOR = "VisionSensor"
    VOICE_ASSISTANCE = "VoiceAssistance"
    WASHER = "Washer"
    WATER_FREEZER_DETECTOR = "WaterFreezeDetector"
    WATER_HEATER = "WaterHeater"
    WATER_VALVE = "WaterValve"
    WATER_PURIFIER = "WaterPurifier"
    WEATHER_STATION = "WeatherStation"
    WIFI_ROUTER = "WiFiRouter"
    WINDOW = "Window"
    WINE_CELLAR = "WineCellar"


@dataclass
class Component(DataClassORJSONMixin):
    """Component model."""

    id: str
    capabilities: list[Capability | str]
    manufacturer_category: Category | str
    label: str | None = None
    user_category: Category | str | None = None

    @classmethod
    def __pre_deserialize__(cls, d: dict[str, Any]) -> dict[str, Any]:
        """Pre deserialize hook."""
        d["capabilities"] = [c["id"] for c in d["capabilities"]]
        for cat in d["categories"]:
            if (category := cat["name"]) not in Category:
                LOGGER.error(
                    "Unknown category `%s`. Please raise an issue at https://github.com/pySmartThings/pysmartthings.",
                    category,
                )
            if cat["categoryType"] == "manufacturer":
                d["manufacturer_category"] = category
            else:
                d["user_category"] = category
        return d


@dataclass
class OCF(DataClassORJSONMixin):
    """OCF model."""

    device_type: str = field(metadata=field_options(alias="ocfDeviceType"))
    manufacturer_name: str = field(metadata=field_options(alias="manufacturerName"))
    vendor_id: str = field(metadata=field_options(alias="vendorId"))
    last_signup: str = field(metadata=field_options(alias="lastSignupTime"))
    transfer_candidate: bool = field(metadata=field_options(alias="transferCandidate"))
    additional_auth_code_required: bool = field(
        metadata=field_options(alias="additionalAuthCodeRequired")
    )
    spec_version: str | None = field(
        metadata=field_options(alias="specVersion"), default=None
    )
    spec_version_vertical: str | None = field(
        metadata=field_options(alias="verticalDomainSpecVersion"), default=None
    )
    name: str | None = None
    model_number: str | None = field(
        metadata=field_options(alias="modelNumber"), default=None
    )
    platform_version: str | None = field(
        metadata=field_options(alias="platformVersion"), default=None
    )
    platform_os: str | None = field(
        metadata=field_options(alias="platformOS"), default=None
    )
    hardware_version: str | None = field(
        metadata=field_options(alias="hwVersion"), default=None
    )
    firmware_version: str | None = field(
        metadata=field_options(alias="firmwareVersion"), default=None
    )
    model_code: str | None = field(
        metadata=field_options(alias="modelCode"), default=None
    )

    @classmethod
    def __post_deserialize__(cls, obj: OCF) -> OCF:
        """Post deserialize hook."""
        if obj.model_code in {"", "NONE"}:
            obj.model_code = None
        return obj


@dataclass
class Matter(DataClassORJSONMixin):
    """Matter model."""

    driver_id: str = field(metadata=field_options(alias="driverId"))
    hub_id: str = field(metadata=field_options(alias="hubId"))
    provisioning_state: MatterProvisioningState = field(
        metadata=field_options(alias="provisioningState")
    )
    network_id: str = field(metadata=field_options(alias="networkId"))
    executing_locally: bool = field(metadata=field_options(alias="executingLocally"))
    supported_network_interfaces: list[MatterSupportedNetworkInterfaces] = field(
        metadata=field_options(alias="supportedNetworkInterfaces"), default_factory=list
    )
    unique_id: str | None = field(
        metadata=field_options(alias="uniqueId"), default=None
    )
    vendor_id: int | None = field(
        metadata=field_options(alias="vendorId"), default=None
    )
    product_id: int | None = field(
        metadata=field_options(alias="productId"), default=None
    )
    serial_number: str | None = field(
        metadata=field_options(alias="serialNumber"), default=None
    )
    listening_type: MatterListeningType | None = field(
        metadata=field_options(alias="listeningType"), default=None
    )
    hardware_version: str | None = field(
        metadata=field_options(alias="hardwareVersion"), default=None
    )
    software_version: str | None = field(
        metadata=field_options(alias="softwareVersion"), default=None
    )

    @classmethod
    def __pre_deserialize__(cls, d: dict[str, Any]) -> dict[str, Any]:
        """Pre deserialize hook."""
        d["hardwareVersion"] = d.get("version", {}).get("hardwareLabel")
        d["softwareVersion"] = d.get("version", {}).get("softwareLabel")
        return d


@dataclass
class Viper(DataClassORJSONMixin):
    """Viper model."""

    manufacturer_name: str = field(metadata=field_options(alias="manufacturerName"))
    model_name: str = field(metadata=field_options(alias="modelName"))
    hardware_version: str | None = field(
        metadata=field_options(alias="hwVersion"), default=None
    )
    software_version: str | None = field(
        metadata=field_options(alias="swVersion"), default=None
    )


@dataclass
class Hub(DataClassORJSONMixin):
    """Hub model."""

    firmware_version: str = field(metadata=field_options(alias="firmwareVersion"))
    hardware_type: str = field(metadata=field_options(alias="hardwareType"))
    mac_address: str | None = field(metadata=field_options(alias="macAddress"))
    hub_eui: str | None = field(metadata=field_options(alias="hubEui"), default=None)

    @classmethod
    def __pre_deserialize__(cls, d: dict[str, Any]) -> dict[str, Any]:
        """Pre deserialize hook."""
        return {
            **d,
            "hardwareType": d["hubData"]["hardwareType"],
            "macAddress": d["hubData"].get("macAddress"),
        }


@dataclass
class Zigbee(DataClassORJSONMixin):
    """Zigbee model."""

    eui: str


@dataclass
class Device(DataClassORJSONMixin):
    """Device model."""

    device_id: str = field(metadata=field_options(alias="deviceId"))
    name: str
    label: str
    location_id: str = field(metadata=field_options(alias="locationId"))
    type: DeviceType
    components: dict[str, Component]
    presentation_id: str | None = field(
        metadata=field_options(alias="presentationId"), default=None
    )
    parent_device_id: str | None = field(
        metadata=field_options(alias="parentDeviceId"), default=None
    )
    device_network_type: DeviceNetworkType | None = field(
        metadata=field_options(alias="deviceNetworkType"), default=None
    )
    device_type_id: str | None = field(
        metadata=field_options(alias="deviceTypeId"), default=None
    )
    device_type_name: str | None = field(
        metadata=field_options(alias="deviceTypeName"), default=None
    )
    device_manufacturer_code: str | None = field(
        metadata=field_options(alias="deviceManufacturerCode"), default=None
    )
    room_id: str | None = field(metadata=field_options(alias="roomId"), default=None)
    ocf: OCF | None = None
    viper: Viper | None = None
    hub: Hub | None = None
    matter: Matter | None = None
    zigbee: Zigbee | None = None

    @classmethod
    def __pre_deserialize__(cls, d: dict[str, Any]) -> dict[str, Any]:
        """Pre deserialize hook."""
        d["components"] = {component["id"]: component for component in d["components"]}
        return d


@dataclass
class Scene(DataClassORJSONMixin):
    """Scene model."""

    scene_id: str = field(metadata=field_options(alias="sceneId"))
    name: str = field(metadata=field_options(alias="sceneName"))
    location_id: str = field(metadata=field_options(alias="locationId"))
    icon: str = field(metadata=field_options(alias="sceneIcon"))
    color: str | None = field(metadata=field_options(alias="sceneColor"), default=None)


@dataclass
class Status(DataClassORJSONMixin):
    """Status model."""

    value: str | int | float | dict[str, Any] | list[Any] | None
    unit: str | None = None
    data: dict[str, Any] | None = None
    timestamp: datetime | None = None


@dataclass
class DeviceStatus(DataClassORJSONMixin):
    """Device status model."""

    components: dict[str, ComponentStatus]

    @classmethod
    def __post_deserialize__(cls, obj: DeviceStatus) -> DeviceStatus:
        """Make sure we let the user know about unknown capabilities."""
        for component in obj.components.values():
            for capability in component:
                if (
                    capability not in Capability
                    and capability not in ALREADY_LOGGED_CAPABILITIES
                ):
                    ALREADY_LOGGED_CAPABILITIES.add(capability)
                    LOGGER.warning(
                        "Unknown capability %s. Please raise an issue at https://github.com/pySmartThings/pysmartthings.",
                        capability,
                    )
        return obj


@dataclass
class LocationResponse(DataClassORJSONMixin):
    """Location response model."""

    items: list[BaseLocation]


@dataclass
class RoomResponse(DataClassORJSONMixin):
    """Room response model."""

    items: list[Room]


@dataclass
class DeviceResponse(DataClassORJSONMixin):
    """Device response model."""

    items: list[Device]
    next_link: str | None

    @classmethod
    def __pre_deserialize__(cls, d: dict[str, Any]) -> dict[str, Any]:
        """Pre deserialize hook."""
        return {
            **d,
            "next_link": d.get("_links", {}).get("next", {}).get("href"),
        }


@dataclass
class SceneResponse(DataClassORJSONMixin):
    """Scene response model."""

    items: list[Scene]


@dataclass
class ErrorResponse(DataClassORJSONMixin):
    """Error response model."""

    request_id: str = field(metadata=field_options(alias="requestId"))
    error: ErrorDetails


@dataclass
class ErrorDetails:
    """Error details model."""

    code: str
    message: str
    details: list[ErrorDetails]
    target: str | None = field(default=None)


@dataclass
class Subscription(DataClassORJSONMixin):
    """Subscription model."""

    subscription_id: str = field(metadata=field_options(alias="subscriptionId"))
    registration_url: str = field(metadata=field_options(alias="registrationUrl"))
    name: str


@dataclass
class DeviceEvent(DataClassORJSONMixin):
    """Device event model."""

    event_id: str = field(metadata=field_options(alias="eventId"))
    location_id: str = field(metadata=field_options(alias="locationId"))
    owner_id: str = field(metadata=field_options(alias="ownerId"))
    device_id: str = field(metadata=field_options(alias="deviceId"))
    component_id: str = field(metadata=field_options(alias="componentId"))
    capability: Capability | str
    attribute: Attribute | str
    value: str | int | float | dict[str, Any] | list[Any] | None
    data: dict[str, Any] | None = None


@dataclass
class Event(DataClassORJSONMixin):
    """Event model."""

    event_time: int = field(metadata=field_options(alias="eventTime"))
    event_type: str = field(metadata=field_options(alias="eventType"))


class Lifecycle(StrEnum):
    """Lifecycle model."""

    CREATE = "CREATE"
    UPDATE = "UPDATE"
    DELETE = "DELETE"
    ROOM_MOVE = "ROOM_MOVE"


@dataclass
class DeviceLifecycleEvent(DataClassORJSONMixin):
    """Device lifecycle event model."""

    lifecycle: Lifecycle
    device_id: str = field(metadata=field_options(alias="deviceId"))
    location_id: str = field(metadata=field_options(alias="locationId"))


@dataclass
class DeviceEventRoot(Event):
    """Device event root model."""

    device_event: DeviceEvent = field(metadata=field_options(alias="deviceEvent"))


@dataclass
class DeviceLifecycleEventRoot(Event):
    """Device lifecycle event root model."""

    device_lifecycle_event: DeviceLifecycleEvent = field(
        metadata=field_options(alias="deviceLifecycleEvent")
    )


class HealthStatus(StrEnum):
    """Health status model."""

    ONLINE = "ONLINE"
    OFFLINE = "OFFLINE"
    UNHEALTHY = "UNHEALTHY"


@dataclass
class DeviceHealthEvent:
    """Device health event model."""

    device_id: str = field(metadata=field_options(alias="deviceId"))
    location_id: str = field(metadata=field_options(alias="locationId"))
    status: HealthStatus


@dataclass
class DeviceHealthEventRoot(Event):
    """Device health event root model."""

    device_health_event: DeviceHealthEvent = field(
        metadata=field_options(alias="deviceHealthEvent")
    )


class EventType(StrEnum):
    """Event type."""

    CONTROL_EVENT = "CONTROL_EVENT"
    DEVICE_EVENT = "DEVICE_EVENT"
    DEVICE_LIFECYCLE_EVENT = "DEVICE_LIFECYCLE_EVENT"
    DEVICE_HEALTH_EVENT = "DEVICE_HEALTH_EVENT"


@dataclass
class DeviceHealth(DataClassORJSONMixin):
    """Device health model."""

    device_id: str = field(metadata=field_options(alias="deviceId"))
    state: HealthStatus
    last_updated_date: datetime = field(metadata=field_options(alias="lastUpdatedDate"))


type CapabilityStatus = dict[Attribute | str, Status]
type ComponentStatus = dict[Capability | str, CapabilityStatus]
