"""
Solis Solar interface for Home Assistant
This component offers Solis portal data as a sensor.

For more information: https://github.com/hultenvp/solis-sensor/
"""

from __future__ import annotations

import logging
from datetime import datetime, timedelta
from typing import Any

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

from .const import (API_NAME, CONF_KEY_ID, CONF_PASSWORD, CONF_PLANT_ID,
                    CONF_PORTAL_DOMAIN, CONF_SECRET, CONF_USERNAME,
                    DEFAULT_DOMAIN, DOMAIN, EMPTY_ATTR, LAST_UPDATED,
                    SENSOR_PREFIX, SENSOR_TYPES, SERIAL, VERSION)
from .service import InverterService, ServiceSubscriber

_LOGGER = logging.getLogger(__name__)

# VERSION
VERSION = "3.9.1"

# ATTRIBUTES
LAST_UPDATED = "Last updated"
SERIAL = "Inverter serial"
API_NAME = "API Name"

EMPTY_ATTR: dict[str, Any] = {
    LAST_UPDATED: None,
    SERIAL: None,
    API_NAME: None,
}


def _check_config_schema(config: ConfigType):
    # Check input configuration.
    portal_domain = config.get(CONF_PORTAL_DOMAIN)
    if portal_domain is None:
        raise vol.Invalid("configuration parameter [portal_domain] does not have a value")
    if portal_domain[:4] != "http":
        portal_domain = f"https://{portal_domain}"
    if config.get(CONF_USERNAME) is None:
        raise vol.Invalid("configuration parameter [portal_username] does not have a value")
    if config.get(CONF_PLANT_ID) is None:
        raise vol.Invalid("Configuration parameter [portal_plantid] does not have a value")
    has_password: bool = config.get(CONF_PASSWORD) != ""
    has_key_id: bool = config.get(CONF_KEY_ID) != ""
    has_secret: bool = bytes(config.get(CONF_SECRET), "utf-8") != b"\x00"
    if not has_password ^ (has_key_id and has_secret):
        raise vol.Invalid(
            "Please specify either[portal_password] or [portal_key_id] \
            & [portal_secret]"
        )

    return config


PLATFORM_SCHEMA = vol.All(
    PLATFORM_SCHEMA.extend(
        {
            vol.Optional(CONF_NAME, default=SENSOR_PREFIX): cv.string,
            vol.Optional(CONF_PORTAL_DOMAIN, default=DEFAULT_DOMAIN): cv.string,
            vol.Required(CONF_USERNAME, default=None): cv.string,
            vol.Optional(CONF_PASSWORD, default=""): cv.string,
            vol.Optional(CONF_SECRET, default="00"): cv.string,
            vol.Optional(CONF_KEY_ID, default=""): cv.string,
            vol.Required(CONF_PLANT_ID, default=None): cv.positive_int,
        },
        extra=vol.PREVENT_EXTRA,
    ),
    _check_config_schema,
)


def create_sensors(
    sensors: dict[str, list[str]], inverter_service: InverterService, inverter_name: str
) -> list[SolisSensor]:
    """Create the sensors."""
    hass_sensors = []
    for inverter_sn in sensors:
        for sensor_type in sensors[inverter_sn]:
            # _LOGGER.debug("Creating %s (%s)", sensor_type, inverter_sn)
            hass_sensors.append(SolisSensor(inverter_service, inverter_name, inverter_sn, sensor_type))
    return hass_sensors


async def async_setup_platform(
    hass: HomeAssistant,
    config: ConfigType,
    async_add_entities: AddEntitiesCallback | None = None,
    discovery_info: DiscoveryInfoType | None = None,
) -> None:
    """Set up Solis platform."""
    hass.async_create_task(
        hass.config_entries.flow.async_init(
            DOMAIN,
            context={"source": SOURCE_IMPORT},
            data=config,
        )
    )


async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities):
    """Setup sensors from a config entry created in the integrations UI."""
    # Prepare the sensor entities.
    inverter_name = config_entry.data[CONF_NAME]
    service = hass.data[DOMAIN][config_entry.entry_id]
    cookie: dict[str, Any] = {"name": inverter_name, "service": service, "async_add_entities": async_add_entities}
    # Will retry endlessly to discover
    _LOGGER.info("Scheduling discovery")
    service.schedule_discovery(on_discovered, cookie, 1)


@callback
def on_discovered(capabilities, cookie):
    """Callback when discovery was successful."""
    discovered_sensors: dict[str, list] = {}
    for inverter_sn in capabilities:
        for sensor in SENSOR_TYPES.keys():
            if SENSOR_TYPES[sensor][5] in capabilities[inverter_sn]:
                if inverter_sn not in discovered_sensors:
                    discovered_sensors[inverter_sn] = list()
                discovered_sensors[inverter_sn].append(sensor)
    if not discovered_sensors:
        _LOGGER.warning("No sensors detected, nothing to register")

    # Create the sensors
    hass_sensors = create_sensors(discovered_sensors, cookie["service"], cookie["name"])
    cookie["async_add_entities"](hass_sensors)
    # schedule the first update in 1 minute from now:
    cookie["service"].schedule_update(timedelta(minutes=1))


class SolisSensor(ServiceSubscriber, SensorEntity):
    """Representation of a Solis sensor."""

    def __init__(self, ginlong_service: InverterService, inverter_name: str, inverter_sn: str, sensor_type: str):
        # Initialize the sensor.
        self._measured: datetime | None = None
        self._entity_type = "sensor"
        self._attributes = dict(EMPTY_ATTR)
        self._attributes[SERIAL] = inverter_sn
        self._attributes[API_NAME] = ginlong_service.api_name
        # Properties
        self._icon = SENSOR_TYPES[sensor_type][2]
        self._name = inverter_name + " " + SENSOR_TYPES[sensor_type][0]
        self._attr_native_value = None
        self._attr_native_unit_of_measurement = SENSOR_TYPES[sensor_type][1]
        self._attr_device_class = SENSOR_TYPES[sensor_type][3]
        self._attr_state_class = SENSOR_TYPES[sensor_type][4]
        self._attr_unique_id = f"{inverter_sn}{self._name}".replace(" ", "_")
        ginlong_service.subscribe(self, inverter_sn, SENSOR_TYPES[sensor_type][5])

    def do_update(self, value: Any, last_updated: datetime) -> bool:
        """Update the sensor."""
        if self.hass and self._attr_native_value != value:
            self._attr_native_value = value
            self._attributes[LAST_UPDATED] = last_updated
            self.async_write_ha_state()
            return True
        return False

    @property
    def icon(self):
        """Return the icon of the sensor."""
        return self._icon

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def should_poll(self):
        """No polling needed."""
        return False

    @property
    def extra_state_attributes(self):
        """Return entity specific state attributes."""
        return self._attributes

    @property
    def device_info(self) -> DeviceInfo | None:
        """Return a device description for device registry."""
        return DeviceInfo(
            #            config_entry_id=config.entry_id,
            #                        connections={(dr.CONNECTION_NETWORK_MAC, config.mac)},
            identifiers={(DOMAIN, f"{self._attributes[SERIAL]}_{DOMAIN, self._attributes[API_NAME]}")},
            manufacturer=f"Solis {self._attributes[API_NAME]}",
            name=f"Solis_Inverter_{self._attributes[SERIAL]}",
            #            model=config.modelid,
            #            sw_version=config.swversion,
            #            hw_version=config.hwversion,
        )