# -*- coding: utf-8 -*-

# Copyright (c) 2016-2026 by University of Kassel and Fraunhofer Institute for Energy Economics
# and Energy System Technology (IEE), Kassel. All rights reserved.

from __future__ import annotations

import logging
from typing import Iterable, Sequence

from numpy import nan, bool_
import numpy.typing as npt

from pandapower.auxiliary import pandapowerNet
from pandapower.pp_types import Int
from pandapower.create._utils import (
    _add_to_entries_if_not_nan,
    _check_element,
    _check_multiple_elements,
    _get_index_with_check,
    _get_multiple_index_with_check,
    _set_entries,
    _set_multiple_entries,
    _set_value_if_not_nan,
)

logger = logging.getLogger(__name__)


def create_storage(
    net: pandapowerNet,
    bus: Int,
    p_mw: float,
    max_e_mwh: float,
    q_mvar: float = 0,
    sn_mva: float = nan,
    soc_percent: float = nan,
    min_e_mwh: float = 0.0,
    name: str | None = None,
    index: Int | None = None,
    scaling: float = 1.0,
    type: str | None = None,
    in_service: bool = True,
    max_p_mw: float = nan,
    min_p_mw: float = nan,
    max_q_mvar: float = nan,
    min_q_mvar: float = nan,
    controllable: bool | float = nan,
    **kwargs,
) -> Int:
    """
    Adds a storage to the network.

    In order to simulate a storage system it is possible to use sgens or loads to model the
    discharging or charging state. The power of a storage can be positive or negative, so the use
    of either a sgen or a load is (per definition of the elements) not correct.
    To overcome this issue, a storage element can be created.

    As pandapower is not a time dependent simulation tool and there is no time domain parameter in
    default power flow calculations, the state of charge (SOC) is not updated during any power flow
    calculation.
    The implementation of energy content related parameters in the storage element allows to create
    customized, time dependent simulations by running several power flow calculations and updating
    variables manually.

    Parameters:
        net: The net within this storage should be created
        bus: The bus id to which the storage is connected
        p_mw: The momentary active power of the storage (positive for charging, negative for discharging)
        max_e_mwh: The maximum energy content of the storage (maximum charge level)
        q_mvar: The reactive power of the storage
        sn_mva: Nominal power of the storage
        soc_percent: The state of charge of the storage
        min_e_mwh: The minimum energy content of the storage (minimum charge level)
        name: The name for this storage
        index: Force a specified ID if it is available. If None, the index one higher than the highest already existing
            index is selected.
        scaling: An OPTIONAL custom scaling factor. Multiplies with p_mw and q_mvar.
        type: type variable to classify the storage
        in_service: True for in_service or False for out of service
        max_p_mw: Maximum active power injection - necessary for a controllable storage in OPF
        min_p_mw: Minimum active power injection - necessary for a controllable storage in OPF
        max_q_mvar: Maximum reactive power injection - necessary for a controllable storage in OPF
        min_q_mvar: Minimum reactive power injection - necessary for a controllable storage in OPF
        controllable: Whether this storage is controllable by the optimal powerflow; defaults to False if "controllable"
            column exists in DataFrame

    Returns:
        The ID of the created storage

    Example:
        >>> create_storage(net, 1, p_mw=-30, max_e_mwh=60, soc_percent=1.0, min_e_mwh=5)

    """
    _check_element(net, bus)

    index = _get_index_with_check(net, "storage", index)

    entries = {
        "name": name,
        "bus": bus,
        "p_mw": p_mw,
        "q_mvar": q_mvar,
        "sn_mva": sn_mva,
        "scaling": scaling,
        "soc_percent": soc_percent,
        "min_e_mwh": min_e_mwh,
        "max_e_mwh": max_e_mwh,
        "in_service": in_service,
        "type": type,
        **kwargs,
    }
    _set_entries(net, "storage", index, True, entries=entries)

    # check for OPF parameters and add columns to network table
    _set_value_if_not_nan(net, index, min_p_mw, "min_p_mw", "storage")
    _set_value_if_not_nan(net, index, max_p_mw, "max_p_mw", "storage")
    _set_value_if_not_nan(net, index, min_q_mvar, "min_q_mvar", "storage")
    _set_value_if_not_nan(net, index, max_q_mvar, "max_q_mvar", "storage")
    _set_value_if_not_nan(net, index, controllable, "controllable", "storage", dtype=bool_, default_val=False)

    return index


def create_storages(
    net: pandapowerNet,
    buses: Sequence,
    p_mw: float | Iterable[float],
    max_e_mwh: float | Iterable[float],
    q_mvar: float | Iterable[float] = 0,
    sn_mva: float | Iterable[float] = nan,
    soc_percent: float | Iterable[float] = nan,
    min_e_mwh: float | Iterable[float] = 0.0,
    name: Iterable[str] | None = None,
    index: Int | Iterable[Int] | None = None,
    scaling: float | Iterable[float] = 1.0,
    type: str | Iterable[str] | None = None,
    in_service: bool | Iterable[bool] = True,
    max_p_mw: float | Iterable[float] = nan,
    min_p_mw: float | Iterable[float] = nan,
    max_q_mvar: float | Iterable[float] = nan,
    min_q_mvar: float | Iterable[float] = nan,
    controllable: bool | Iterable[bool] | float = nan,
    **kwargs,
) -> npt.NDArray[Int]:
    """
    Adds storages to the network.

    In order to simulate a storage system it is possible to use sgens or loads to model the
    discharging or charging state. The power of a storage can be positive or negative, so the use
    of either a sgen or a load is (per definition of the elements) not correct.
    To overcome this issue, a storage element can be created.

    As pandapower is not a time dependent simulation tool and there is no time domain parameter in
    default power flow calculations, the state of charge (SOC) is not updated during any power flow
    calculation.
    The implementation of energy content related parameters in the storage element allows to create
    customized, time dependent simulations by running several power flow calculations and updating
    variables manually.

    Parameters:
        net: The net within this storage should be created
        buses: The bus ids to which the generators are connected
        p_mw: The momentary active power of the storage (positive for charging, negative for discharging)
        max_e_mwh: The maximum energy content of the storage (maximum charge level)
        q_mvar: The reactive power of the storage
        sn_mva: Nominal power of the storage
        soc_percent: The state of charge of the storage
        min_e_mwh: The minimum energy content of the storage (minimum charge level)
        name: The name for this storage
        index: Force a specified ID if it is available. If None, the index one higher than the highest already existing
            index is selected.
        scaling: An OPTIONAL custom scaling factor. Multiplies with p_mw and q_mvar.
        type:  type variable to classify the storage
        in_service: True for in_service or False for out of service
        max_p_mw: Maximum active power injection - necessary for a controllable storage in OPF
        min_p_mw: Minimum active power injection - necessary for a controllable storage in OPF
        max_q_mvar: Maximum reactive power injection - necessary for a controllable storage in OPF
        min_q_mvar: Minimum reactive power injection - necessary for a controllable storage in OPF
        controllable: Whether this storage is controllable by the optimal powerflow; defaults to False if "controllable"
            column exists in DataFrame

    Returns:
        The ID of the created storage

    Example:
        >>> create_storage(net, 1, p_mw=-30, max_e_mwh=60, soc_percent=1.0, min_e_mwh=5)
    """
    _check_multiple_elements(net, buses)

    index = _get_multiple_index_with_check(net, "storage", index, len(buses))

    entries = {
        "name": name,
        "bus": buses,
        "p_mw": p_mw,
        "q_mvar": q_mvar,
        "sn_mva": sn_mva,
        "scaling": scaling,
        "soc_percent": soc_percent,
        "min_e_mwh": min_e_mwh,
        "max_e_mwh": max_e_mwh,
        "in_service": in_service,
        "type": type,
        **kwargs,
    }

    _add_to_entries_if_not_nan(net, "storage", entries, index, "min_p_mw", min_p_mw)
    _add_to_entries_if_not_nan(net, "storage", entries, index, "max_p_mw", max_p_mw)
    _add_to_entries_if_not_nan(net, "storage", entries, index, "min_q_mvar", min_q_mvar)
    _add_to_entries_if_not_nan(net, "storage", entries, index, "max_q_mvar", max_q_mvar)
    _add_to_entries_if_not_nan(
        net, "storage", entries, index, "controllable", controllable, dtype=bool_, default_val=False
    )
    defaults_to_fill = [("controllable", False)]

    _set_multiple_entries(net, "storage", index, defaults_to_fill=defaults_to_fill, entries=entries)

    return index
