import esphome.codegen as cg
from esphome.components import sensor, uart
import esphome.config_validation as cv
from esphome.const import (
    CONF_COMMAND,
    CONF_CUSTOM,
    CONF_FLOW,
    CONF_ID,
    CONF_POWER,
    CONF_VOLUME,
    DEVICE_CLASS_EMPTY,
    DEVICE_CLASS_ENERGY,
    DEVICE_CLASS_POWER,
    DEVICE_CLASS_TEMPERATURE,
    DEVICE_CLASS_VOLUME,
    DEVICE_CLASS_VOLUME_FLOW_RATE,
    STATE_CLASS_MEASUREMENT,
    STATE_CLASS_TOTAL_INCREASING,
    UNIT_CELSIUS,
    UNIT_CUBIC_METER,
    UNIT_EMPTY,
    UNIT_KELVIN,
    UNIT_KILOWATT,
)

CODEOWNERS = ["@cfeenstra1024"]
DEPENDENCIES = ["uart"]

kamstrup_kmp_ns = cg.esphome_ns.namespace("kamstrup_kmp")
KamstrupKMPComponent = kamstrup_kmp_ns.class_(
    "KamstrupKMPComponent", cg.PollingComponent, uart.UARTDevice
)

CONF_HEAT_ENERGY = "heat_energy"
CONF_TEMP1 = "temp1"
CONF_TEMP2 = "temp2"
CONF_TEMP_DIFF = "temp_diff"

UNIT_GIGA_JOULE = "GJ"
UNIT_LITRE_PER_HOUR = "l/h"

# Note: The sensor units are set automatically based un the received data from the meter
CONFIG_SCHEMA = (
    cv.Schema(
        {
            cv.GenerateID(): cv.declare_id(KamstrupKMPComponent),
            cv.Optional(CONF_HEAT_ENERGY): sensor.sensor_schema(
                accuracy_decimals=3,
                device_class=DEVICE_CLASS_ENERGY,
                state_class=STATE_CLASS_TOTAL_INCREASING,
                unit_of_measurement=UNIT_GIGA_JOULE,
            ),
            cv.Optional(CONF_POWER): sensor.sensor_schema(
                accuracy_decimals=3,
                device_class=DEVICE_CLASS_POWER,
                state_class=STATE_CLASS_MEASUREMENT,
                unit_of_measurement=UNIT_KILOWATT,
            ),
            cv.Optional(CONF_TEMP1): sensor.sensor_schema(
                accuracy_decimals=1,
                device_class=DEVICE_CLASS_TEMPERATURE,
                state_class=STATE_CLASS_MEASUREMENT,
                unit_of_measurement=UNIT_CELSIUS,
            ),
            cv.Optional(CONF_TEMP2): sensor.sensor_schema(
                accuracy_decimals=1,
                device_class=DEVICE_CLASS_TEMPERATURE,
                state_class=STATE_CLASS_MEASUREMENT,
                unit_of_measurement=UNIT_CELSIUS,
            ),
            cv.Optional(CONF_TEMP_DIFF): sensor.sensor_schema(
                accuracy_decimals=1,
                device_class=DEVICE_CLASS_TEMPERATURE,
                state_class=STATE_CLASS_MEASUREMENT,
                unit_of_measurement=UNIT_KELVIN,
            ),
            cv.Optional(CONF_FLOW): sensor.sensor_schema(
                accuracy_decimals=1,
                device_class=DEVICE_CLASS_VOLUME_FLOW_RATE,
                state_class=STATE_CLASS_MEASUREMENT,
                unit_of_measurement=UNIT_LITRE_PER_HOUR,
            ),
            cv.Optional(CONF_VOLUME): sensor.sensor_schema(
                accuracy_decimals=1,
                device_class=DEVICE_CLASS_VOLUME,
                state_class=STATE_CLASS_TOTAL_INCREASING,
                unit_of_measurement=UNIT_CUBIC_METER,
            ),
            cv.Optional(CONF_CUSTOM): cv.ensure_list(
                sensor.sensor_schema(
                    accuracy_decimals=1,
                    device_class=DEVICE_CLASS_EMPTY,
                    state_class=STATE_CLASS_MEASUREMENT,
                    unit_of_measurement=UNIT_EMPTY,
                ).extend({cv.Required(CONF_COMMAND): cv.hex_uint16_t})
            ),
        }
    )
    .extend(cv.polling_component_schema("60s"))
    .extend(uart.UART_DEVICE_SCHEMA)
)

FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
    "kamstrup_kmp", baud_rate=1200, require_rx=True, require_tx=True
)


async def to_code(config):
    var = cg.new_Pvariable(config[CONF_ID])
    await cg.register_component(var, config)
    await uart.register_uart_device(var, config)

    # Standard sensors
    for key in [
        CONF_HEAT_ENERGY,
        CONF_POWER,
        CONF_TEMP1,
        CONF_TEMP2,
        CONF_TEMP_DIFF,
        CONF_FLOW,
        CONF_VOLUME,
    ]:
        if key not in config:
            continue
        conf = config[key]
        sens = await sensor.new_sensor(conf)
        cg.add(getattr(var, f"set_{key}_sensor")(sens))

    # Custom sensors
    if CONF_CUSTOM in config:
        for conf in config[CONF_CUSTOM]:
            sens = await sensor.new_sensor(conf)
            cg.add(var.add_custom_sensor(sens, conf[CONF_COMMAND]))
