"""GraphQL Mutation-Operationen für Questra Data."""

from __future__ import annotations

from typing import Any  # Nur noch für GraphQL Variable-Werte benötigt

from loguru import logger

from ..models import (
    BackgroundJobResult,
    ConflictAction,
    NamedItemResult,
    TimeSeriesIdResult,
)
from ..models.inputs import (
    CreateTimeSeriesInput,
    InventoryProperty,
    InventoryRelation,
)
from ..models.permissions import (
    InventoryPrivilege,
)


class MutationOperations:
    """
    Mutation-Operationen für Dyno GraphQL API.

    Verantwortlich für:
    - Inventories erstellen und löschen
    - Namespaces erstellen
    - Roles erstellen
    - Permissions verwalten
    - Policies verwalten
    - Units erstellen und löschen
    - TimeSeries erstellen
    """

    def __init__(self, execute_func: callable):
        """
        Initialisiert die Mutation-Operationen.

        Args:
            execute_func: Funktion zum Ausführen von GraphQL-Mutations
        """
        self._execute = execute_func

    def create_inventory(
        self,
        inventory_name: str,
        properties: list[InventoryProperty],
        namespace_name: str | None = None,
        description: str | None = None,
        enable_audit: bool = False,
        relations: list[InventoryRelation] | None = None,
        if_exists: ConflictAction = ConflictAction.RAISE_ERROR,
    ) -> NamedItemResult:
        """
        Erstellt ein neues Inventory.

        Args:
            inventory_name: Name des Inventars
            properties: Liste von Properties (InventoryProperty Dataclasses)
            namespace_name: Optionaler Namespace
            description: Optionale Beschreibung
            enable_audit: Audit aktivieren
            relations: Optionale Relationen (InventoryRelation Dataclasses)
            if_exists: Verhalten bei Konflikt

        Returns:
            NamedItemResult: Dictionary mit Name und existed-Flag

        Examples:
            ```python
            # Mit Dataclasses (empfohlen)
            from questra_data import InventoryProperty, StringPropertyConfig, DataType

            properties = [
                InventoryProperty(
                    propertyName="Name",
                    dataType=DataType.STRING,
                    isRequired=True,
                    string=StringPropertyConfig(maxLength="200"),
                )
            ]

            result = mutations.create_inventory(
                inventory_name="TestInventory", properties=properties
            )
            ```

        """
        logger.debug(
            "Creating inventory",
            inventory_name=inventory_name,
            namespace_name=namespace_name,
            num_properties=len(properties),
            enable_audit=enable_audit,
        )

        # Konvertiere Dataclasses zu Dictionaries
        properties_dicts = [prop.to_dict() for prop in properties]

        relations_dicts = []
        if relations:
            relations_dicts = [rel.to_dict() for rel in relations]

        mutation = """
            mutation CreateInventory($input: _CreateInventory__InputType!) {
                _createInventory(input: $input) {
                    name
                    existed
                }
            }
        """

        variables = {
            "input": {
                "inventoryName": inventory_name,
                "properties": properties_dicts,
                "namespaceName": namespace_name,
                "description": description,
                "enableAudit": enable_audit,
                "relations": relations_dicts,
                "ifExists": if_exists.value,
            }
        }

        result = self._execute(mutation, variables)
        created_result = NamedItemResult.from_dict(result["_createInventory"])
        logger.info(
            f"Inventory {'already existed' if created_result.existed else 'created'}",
            inventory_name=created_result.name,
            existed=created_result.existed,
        )
        return created_result

    def delete_inventory(
        self,
        inventory_name: str,
        namespace_name: str | None = None,
        if_not_exists: ConflictAction = ConflictAction.RAISE_ERROR,
    ) -> NamedItemResult:
        """
        Löscht ein Inventory.

        Args:
            inventory_name: Name des Inventars
            namespace_name: Optionaler Namespace
            if_not_exists: Verhalten wenn nicht vorhanden

        Returns:
            NamedItemResult: Dictionary mit Name und existed-Flag
        """
        logger.debug(
            "Deleting inventory",
            inventory_name=inventory_name,
            namespace_name=namespace_name,
        )

        mutation = """
            mutation DropInventory($input: _DropInventory__InputType!) {
                _dropInventory(input: $input) {
                    name
                    existed
                }
            }
        """

        variables = {
            "input": {
                "inventoryName": inventory_name,
                "namespaceName": namespace_name,
                "ifNotExists": if_not_exists.value,
            }
        }

        result = self._execute(mutation, variables)
        delete_result = NamedItemResult.from_dict(result["_dropInventory"])
        logger.info(
            f"Inventory {'deleted' if delete_result.existed else 'did not exist'}",
            inventory_name=delete_result.name,
            existed=delete_result.existed,
        )
        return delete_result

    def create_namespace(
        self,
        namespace_name: str,
        description: str | None = None,
        if_exists: ConflictAction = ConflictAction.RAISE_ERROR,
    ) -> NamedItemResult:
        """
        Erstellt einen neuen Namespace.

        Args:
            namespace_name: Name des Namespace
            description: Optionale Beschreibung
            if_exists: Verhalten bei Konflikt

        Returns:
            NamedItemResult: Dictionary mit Name und existed-Flag
        """
        logger.debug("Creating namespace", namespace_name=namespace_name)

        mutation = """
            mutation CreateNamespace($input: _CreateNamespace__InputType!) {
                _createNamespace(input: $input) {
                    name
                    existed
                }
            }
        """

        variables = {
            "input": {
                "namespaceName": namespace_name,
                "description": description,
                "ifExists": if_exists.value,
            }
        }

        result = self._execute(mutation, variables)
        ns_result = NamedItemResult.from_dict(result["_createNamespace"])
        logger.info(
            f"Namespace {'already existed' if ns_result.existed else 'created'}",
            namespace_name=ns_result.name,
            existed=ns_result.existed,
        )
        return ns_result

    def create_role(
        self,
        role_name: str,
        description: str | None = None,
        if_exists: ConflictAction = ConflictAction.RAISE_ERROR,
    ) -> NamedItemResult:
        """
        Erstellt eine neue Role.

        Args:
            role_name: Name der Role
            description: Optionale Beschreibung
            if_exists: Verhalten bei Konflikt

        Returns:
            NamedItemResult: Dictionary mit Name und existed-Flag
        """
        logger.debug("Creating role", role_name=role_name)

        mutation = """
            mutation CreateRole($input: _CreateRole__InputType!) {
                _createRole(input: $input) {
                    name
                    existed
                }
            }
        """

        variables = {
            "input": {
                "roleName": role_name,
                "description": description,
                "ifExists": if_exists.value,
            }
        }

        result = self._execute(mutation, variables)
        role_result = NamedItemResult.from_dict(result["_createRole"])
        logger.info(
            f"Role {'already existed' if role_result.existed else 'created'}",
            role_name=role_result.name,
            existed=role_result.existed,
        )
        return role_result

    def grant_inventory_permissions(
        self,
        inventory_name: str,
        role_name: str,
        privileges: list[InventoryPrivilege | str],
        namespace_name: str | None = None,
    ) -> None:
        """
        Gewährt Inventory-Berechtigungen für eine Role.

        Args:
            inventory_name: Name des Inventars
            role_name: Name der Role
            privileges: Liste von Privileges (InventoryPrivilege Enums oder Strings)
            namespace_name: Optionaler Namespace

        Examples:
            ```python
            # Mit Enums (empfohlen)
            from questra_data import InventoryPrivilege

            mutations.grant_inventory_permissions(
                inventory_name="TestUser",
                role_name="TestRole",
                privileges=[
                    InventoryPrivilege.SELECT,
                    InventoryPrivilege.INSERT,
                    InventoryPrivilege.UPDATE,
                ],
            )

            # Mit Strings (legacy)
            mutations.grant_inventory_permissions(
                inventory_name="TestUser",
                role_name="TestRole",
                privileges=["SELECT", "INSERT", "UPDATE"],
            )
            ```
        """
        logger.debug(
            "Granting inventory permissions",
            inventory_name=inventory_name,
            role_name=role_name,
            privileges=privileges,
        )

        # Konvertiere Enums zu Strings falls nötig
        privileges_values = [
            priv.value if isinstance(priv, InventoryPrivilege) else priv
            for priv in privileges
        ]

        mutation = """
            mutation GrantInventoryPermissions(
                $input: _GrantInventoryPermissions__InputType!
            ) {
                _grantInventoryPermissions(input: $input)
            }
        """

        variables = {
            "input": {
                "inventoryName": inventory_name,
                "roleName": role_name,
                "privileges": privileges_values,
                "namespaceName": namespace_name,
            }
        }

        self._execute(mutation, variables)
        logger.info(
            "Permissions granted",
            inventory_name=inventory_name,
            role_name=role_name,
            privileges=privileges_values,
        )

    def deny_inventory_permissions(
        self,
        inventory_name: str,
        role_name: str,
        privileges: list[InventoryPrivilege | str],
        namespace_name: str | None = None,
    ) -> None:
        """
        Verweigert Inventory-Berechtigungen für eine Role.

        Args:
            inventory_name: Name des Inventars
            role_name: Name der Role
            privileges: Liste von Privileges (InventoryPrivilege Enums oder Strings)
            namespace_name: Optionaler Namespace
        """
        logger.debug(
            "Denying inventory permissions",
            inventory_name=inventory_name,
            role_name=role_name,
            privileges=privileges,
        )

        # Konvertiere Enums zu Strings falls nötig
        privileges_values = [
            priv.value if isinstance(priv, InventoryPrivilege) else priv
            for priv in privileges
        ]

        mutation = """
            mutation DenyInventoryPermissions(
                $input: _DenyInventoryPermissions__InputType!
            ) {
                _denyInventoryPermissions(input: $input)
            }
        """

        variables = {
            "input": {
                "inventoryName": inventory_name,
                "roleName": role_name,
                "privileges": privileges_values,
                "namespaceName": namespace_name,
            }
        }

        self._execute(mutation, variables)
        logger.info(
            "Permissions denied",
            inventory_name=inventory_name,
            role_name=role_name,
            privileges=privileges_values,
        )

    def revoke_inventory_permissions(
        self,
        inventory_name: str,
        role_name: str,
        privileges: list[InventoryPrivilege | str],
        namespace_name: str | None = None,
    ) -> None:
        """
        Widerruft Inventory-Berechtigungen für eine Role.

        Args:
            inventory_name: Name des Inventars
            role_name: Name der Role
            privileges: Liste von Privileges (InventoryPrivilege Enums oder Strings)
            namespace_name: Optionaler Namespace
        """
        logger.debug(
            "Revoking inventory permissions",
            inventory_name=inventory_name,
            role_name=role_name,
            privileges=privileges,
        )

        # Konvertiere Enums zu Strings falls nötig
        privileges_values = [
            priv.value if isinstance(priv, InventoryPrivilege) else priv
            for priv in privileges
        ]

        mutation = """
            mutation RevokeInventoryPermissions(
                $input: _RevokeInventoryPermissions__InputType!
            ) {
                _revokeInventoryPermissions(input: $input)
            }
        """

        variables = {
            "input": {
                "inventoryName": inventory_name,
                "roleName": role_name,
                "privileges": privileges_values,
                "namespaceName": namespace_name,
            }
        }

        self._execute(mutation, variables)
        logger.info(
            "Permissions revoked",
            inventory_name=inventory_name,
            role_name=role_name,
            privileges=privileges_values,
        )

    def create_inventory_policy(
        self,
        policy_name: str,
        role_name: str,
        inventory_name: str,
        property_name: str,
        filter_value: Any,
        namespace_name: str | None = None,
        description: str | None = None,
        if_exists: ConflictAction = ConflictAction.RAISE_ERROR,
    ) -> NamedItemResult:
        """
        Erstellt eine Inventory-Policy.

        Args:
            policy_name: Name der Policy
            role_name: Name der Role
            inventory_name: Name des Inventars
            property_name: Name der Property
            filter_value: Filter-Wert
            namespace_name: Optionaler Namespace
            description: Optionale Beschreibung
            if_exists: Verhalten bei Konflikt

        Returns:
            NamedItemResult: Dictionary mit Name und existed-Flag
        """
        logger.debug(
            "Creating inventory policy",
            policy_name=policy_name,
            role_name=role_name,
            inventory_name=inventory_name,
        )

        mutation = """
            mutation CreateInventoryPolicy(
                $input: _CreateInventoryPolicy__InputType!
            ) {
                _createInventoryPolicy(input: $input) {
                    name
                    existed
                }
            }
        """

        variables = {
            "input": {
                "policyName": policy_name,
                "roleName": role_name,
                "inventoryName": inventory_name,
                "propertyName": property_name,
                "filterValue": filter_value,
                "namespaceName": namespace_name,
                "description": description,
                "ifExists": if_exists.value,
            }
        }

        result = self._execute(mutation, variables)
        policy_result = NamedItemResult.from_dict(result["_createInventoryPolicy"])
        logger.info(
            f"Policy {'already existed' if policy_result.existed else 'created'}",
            policy_name=policy_result.name,
            existed=policy_result.existed,
        )
        return policy_result

    def delete_inventory_policy(
        self,
        policy_name: str,
        inventory_name: str,
        namespace_name: str | None = None,
        if_not_exists: ConflictAction = ConflictAction.RAISE_ERROR,
    ) -> NamedItemResult:
        """
        Löscht eine Inventory-Policy.

        Args:
            policy_name: Name der Policy
            inventory_name: Name des Inventars
            namespace_name: Optionaler Namespace
            if_not_exists: Verhalten wenn nicht vorhanden

        Returns:
            NamedItemResult: Dictionary mit Name und existed-Flag
        """
        logger.debug(
            "Deleting inventory policy",
            policy_name=policy_name,
            inventory_name=inventory_name,
        )

        mutation = """
            mutation DropInventoryPolicy(
                $input: _DropInventoryPolicy__InputType!
            ) {
                _dropInventoryPolicy(input: $input) {
                    name
                    existed
                }
            }
        """

        variables = {
            "input": {
                "policyName": policy_name,
                "inventoryName": inventory_name,
                "namespaceName": namespace_name,
                "ifNotExists": if_not_exists.value,
            }
        }

        result = self._execute(mutation, variables)
        delete_result = NamedItemResult.from_dict(result["_dropInventoryPolicy"])
        logger.info(
            f"Policy {'deleted' if delete_result.existed else 'did not exist'}",
            policy_name=delete_result.name,
            existed=delete_result.existed,
        )
        return delete_result

    def start_alter_inventory(
        self,
        inventory_name: str,
        namespace_name: str | None = None,
        add_properties: list[InventoryProperty] | None = None,
        alter_properties: list[InventoryProperty] | None = None,
        drop_properties: list[str] | None = None,
        add_relations: list[InventoryRelation] | None = None,
        drop_relations: list[str] | None = None,
        alter_relations: list[InventoryRelation] | None = None,
        new_inventory_name: str | None = None,
        description: str | None = None,
        if_not_exists: ConflictAction = ConflictAction.RAISE_ERROR,
    ) -> BackgroundJobResult:
        """
        Startet eine Inventory-Änderung als Background-Job.

        Args:
            inventory_name: Name des Inventars
            namespace_name: Optionaler Namespace
            add_properties: Hinzuzufügende Properties (InventoryProperty Dataclasses)
            alter_properties: Zu ändernde Properties (InventoryProperty Dataclasses)
            drop_properties: Zu löschende Properties (Namen)
            add_relations: Hinzuzufügende Relationen (InventoryRelation Dataclasses)
            drop_relations: Zu löschende Relationen (Namen)
            alter_relations: Zu ändernde Relationen (InventoryRelation Dataclasses)
            new_inventory_name: Neuer Name für das Inventory
            description: Neue Beschreibung
            if_not_exists: Verhalten wenn nicht vorhanden

        Returns:
            BackgroundJobResult: Dictionary mit Background-Job Informationen
        """
        logger.debug(
            "Starting alter inventory job",
            inventory_name=inventory_name,
            namespace_name=namespace_name,
            new_inventory_name=new_inventory_name,
        )

        # Konvertiere Dataclasses zu Dictionaries falls nötig
        add_properties_dicts = []
        if add_properties:
            add_properties_dicts = [
                prop.to_dict() if isinstance(prop, InventoryProperty) else prop
                for prop in add_properties
            ]

        alter_properties_dicts = []
        if alter_properties:
            alter_properties_dicts = [
                prop.to_dict() if isinstance(prop, InventoryProperty) else prop
                for prop in alter_properties
            ]

        add_relations_dicts = []
        if add_relations:
            add_relations_dicts = [
                rel.to_dict() if isinstance(rel, InventoryRelation) else rel
                for rel in add_relations
            ]

        alter_relations_dicts = []
        if alter_relations:
            alter_relations_dicts = [
                rel.to_dict() if isinstance(rel, InventoryRelation) else rel
                for rel in alter_relations
            ]

        mutation = """
            mutation StartAlterInventory(
                $input: _StartAlterInventory__InputType!
            ) {
                _startAlterInventory(input: $input) {
                    backgroundJobId
                }
            }
        """

        drop_properties_list = (
            [{"propertyName": prop} for prop in drop_properties]
            if drop_properties
            else []
        )
        drop_relations_list = (
            [{"propertyName": rel} for rel in drop_relations] if drop_relations else []
        )

        variables = {
            "input": {
                "inventoryName": inventory_name,
                "namespaceName": namespace_name,
                "addProperties": add_properties_dicts,
                "alterProperties": alter_properties_dicts,
                "dropProperties": drop_properties_list,
                "addRelations": add_relations_dicts,
                "dropRelations": drop_relations_list,
                "alterRelations": alter_relations_dicts,
                "newInventoryName": new_inventory_name,
                "description": description,
                "ifNotExists": if_not_exists.value,
            }
        }

        result = self._execute(mutation, variables)
        alter_result = BackgroundJobResult.from_dict(result["_startAlterInventory"])
        logger.info(
            "Alter inventory job started",
            inventory_name=inventory_name,
            background_job_id=alter_result.background_job_id,
        )
        return alter_result

    def create_unit(
        self,
        symbol: str,
        aggregation: str,
        if_exists: ConflictAction = ConflictAction.RAISE_ERROR,
    ) -> NamedItemResult:
        """
        Erstellt eine neue Einheit (Unit).

        Args:
            symbol: Symbol der Einheit (z.B. "kW", "kWh", "MW")
            aggregation: Aggregationsart (SUM, AVERAGE, MIN, MAX, etc.)
            if_exists: Verhalten bei Konflikt

        Returns:
            NamedItemResult: Dictionary mit Name und existed-Flag

        Examples:
            ```python
            result = client.mutations.create_unit(
                symbol="kW", aggregation="AVERAGE", if_exists=ConflictAction.IGNORE
            )
            ```
        """
        logger.debug("Creating unit", symbol=symbol, aggregation=aggregation)

        mutation = """
            mutation CreateUnit($input: _CreateUnit__InputType!) {
                _createUnit(input: $input) {
                    name
                    existed
                }
            }
        """

        variables = {
            "input": {
                "symbol": symbol,
                "aggregation": aggregation,
                "ifExists": if_exists.value,
            }
        }

        result = self._execute(mutation, variables)
        unit_result = NamedItemResult.from_dict(result["_createUnit"])
        logger.info(
            f"Unit {'already existed' if unit_result.existed else 'created'}",
            symbol=unit_result.name,
            existed=unit_result.existed,
        )
        return unit_result

    def delete_unit(
        self,
        symbol: str,
        if_not_exists: ConflictAction = ConflictAction.RAISE_ERROR,
    ) -> NamedItemResult:
        """
        Löscht eine Einheit (Unit).

        Args:
            symbol: Symbol der Einheit
            if_not_exists: Verhalten wenn nicht vorhanden

        Returns:
            NamedItemResult: Dictionary mit Name und existed-Flag

        Examples:
            ```python
            result = client.mutations.delete_unit(
                symbol="kW", if_not_exists=ConflictAction.IGNORE
            )
            ```
        """
        logger.debug("Deleting unit", symbol=symbol)

        mutation = """
            mutation DropUnit($input: _DropUnit__InputType!) {
                _dropUnit(input: $input) {
                    name
                    existed
                }
            }
        """

        variables = {
            "input": {
                "symbol": symbol,
                "ifNotExists": if_not_exists.value,
            }
        }

        result = self._execute(mutation, variables)
        delete_result = NamedItemResult.from_dict(result["_dropUnit"])
        logger.info(
            f"Unit {'deleted' if delete_result.existed else 'did not exist'}",
            symbol=delete_result.name,
            existed=delete_result.existed,
        )
        return delete_result

    def create_timeseries(
        self,
        time_series_inputs: list[CreateTimeSeriesInput],
    ) -> list[TimeSeriesIdResult]:
        """
        Erstellt eine oder mehrere TimeSeries.

        Args:
            time_series_inputs: Liste von TimeSeries-Definitionen (CreateTimeSeriesInput)

        Returns:
            list[TimeSeriesIdResult]: Liste mit _id für jede erstellte TimeSeries

        Examples:
            ```python
            from questra_data import (
                CreateTimeSeriesInput,
                TimeSeriesSpecifics,
                IntervalConfig,
                TimeUnit,
                ValueAlignment,
                ValueAvailability,
                Aggregation,
                QuotationBehavior,
            )

            result = client.mutations.create_timeseries(
                [
                    CreateTimeSeriesInput(
                        inventoryName="PowerPlant",
                        propertyName="PowerOutput",
                        namespaceName="Energy",
                        specifics=TimeSeriesSpecifics(
                            interval=IntervalConfig(
                                timeUnit=TimeUnit.MINUTE, multiplier=15
                            ),
                            valueAlignment=ValueAlignment.LEFT,
                            valueAvailability=ValueAvailability.AT_INTERVAL_BEGIN,
                            unit="kW",
                            timeZone="Europe/Berlin",
                            defaultAggregation=Aggregation.AVERAGE,
                            startOfTime="2024-01-01T00:00:00Z",
                            defaultQuotationBehavior=QuotationBehavior.LATEST,
                        ),
                    )
                ]
            )
            ```
        """
        logger.debug(f"Creating {len(time_series_inputs)} timeseries")

        mutation = """
            mutation CreateTimeSeries($input: [_CreateTimeSeries__InputType!]!) {
                _createTimeSeries(input: $input) {
                    _id
                }
            }
        """

        # Konvertiere CreateTimeSeriesInput Dataclasses zu Dicts
        normalized_inputs = [ts_input.to_dict() for ts_input in time_series_inputs]

        variables = {"input": normalized_inputs}

        result = self._execute(mutation, variables)
        ts_results_raw = result["_createTimeSeries"]

        # Konvertiere zu TimeSeriesIdResult Dataclasses
        ts_results = [TimeSeriesIdResult.from_dict(ts) for ts in ts_results_raw]

        logger.info(
            f"Created {len(ts_results)} timeseries",
            ids=[ts.id for ts in ts_results],
        )
        return ts_results
