"""
Validierungs-Helfer für REST-API Tests.

Validiert POST/GET Payloads gegen das Swagger/OpenAPI Schema.
"""

from __future__ import annotations

from typing import Any


class RestPayloadValidator:
    """
    Validiert REST-API Request/Response Payloads.

    Prüft Payloads gegen das Swagger/OpenAPI Schema (schemas/swagger.json).
    """

    # Erlaubte TimeUnit-Werte (aus Schema)
    VALID_TIME_UNITS = {
        "MICROSECOND",
        "MILLISECOND",
        "SECOND",
        "MINUTE",
        "HOUR",
        "DAY",
        "WEEK",
        "MONTH",
        "QUARTER",
        "YEAR",
    }

    # Erlaubte Quality-Werte (aus models/timeseries.py)
    VALID_QUALITIES = {
        "NO_VALUE",
        "MANUALLY_REPLACED",
        "FAULTY",
        "VALID",
        "SCHEDULE",
        "MISSING",
        "ACCOUNTED",
        "ESTIMATED",
        "INTERPOLATED",
    }

    # Erlaubte Aggregation-Werte (aus models/timeseries.py)
    VALID_AGGREGATIONS = {
        "SUM",
        "AVERAGE",
        "MIN",
        "MAX",
        "MOST_FREQUENTLY",
        "ABS_MIN",
        "ABS_MAX",
        "FIRST_VALUE",
        "LAST_VALUE",
    }

    # Erlaubte QuotationBehavior-Werte
    VALID_QUOTATION_BEHAVIORS = {
        "LATEST_EXACTLY_AT",
        "LATEST",
        "LATEST_NO_FUTURE",
    }

    # Erlaubte ValueAlignment-Werte
    VALID_VALUE_ALIGNMENTS = {
        "LEFT",
        "RIGHT",
        "NONE",
    }

    # Erlaubte ValueAvailability-Werte
    VALID_VALUE_AVAILABILITIES = {
        "AT_INTERVAL_BEGIN",
        "AT_INTERVAL_END",
    }

    @classmethod
    def validate_timeseries_data_payload(cls, payload: dict[str, Any]) -> dict[str, Any]:
        """
        Validiert TimeSeriesDataPayload (GET /timeseries/data Response).

        Args:
            payload: Response-Payload (dict)

        Returns:
            dict mit Validierungsergebnis:
                - valid: bool
                - errors: list[str]
                - warnings: list[str]

        Examples:
            >>> payload = {"data": [{"timeSeriesId": 123, "interval": {...}, ...}]}
            >>> result = RestPayloadValidator.validate_timeseries_data_payload(payload)
            >>> result["valid"]
            True
        """
        errors: list[str] = []
        warnings: list[str] = []

        # Prüfe Root-Struktur
        if not isinstance(payload, dict):
            errors.append("Payload muss ein dict sein")
            return {"valid": False, "errors": errors, "warnings": warnings}

        if "data" not in payload:
            errors.append("Payload muss 'data' enthalten")
            return {"valid": False, "errors": errors, "warnings": warnings}

        if not isinstance(payload["data"], list):
            errors.append("'data' muss eine Liste sein")
            return {"valid": False, "errors": errors, "warnings": warnings}

        # Validiere jedes TimeSeriesData-Objekt
        for idx, ts_data in enumerate(payload["data"]):
            if not isinstance(ts_data, dict):
                errors.append(f"data[{idx}] muss ein dict sein")
                continue

            # timeSeriesId (required)
            if "timeSeriesId" not in ts_data:
                errors.append(f"data[{idx}] fehlt 'timeSeriesId'")
            elif not isinstance(ts_data["timeSeriesId"], int):
                errors.append(
                    f"data[{idx}].timeSeriesId muss int sein, ist {type(ts_data['timeSeriesId'])}"
                )

            # interval (required)
            if "interval" not in ts_data:
                errors.append(f"data[{idx}] fehlt 'interval'")
            else:
                interval_errors = cls._validate_interval(ts_data["interval"], f"data[{idx}].interval")
                errors.extend(interval_errors)

            # values (required)
            if "values" not in ts_data:
                errors.append(f"data[{idx}] fehlt 'values'")
            else:
                values_errors = cls._validate_timeseries_values(
                    ts_data["values"], f"data[{idx}].values"
                )
                errors.extend(values_errors)

            # unit (optional)
            if "unit" in ts_data and ts_data["unit"] is not None:
                if not isinstance(ts_data["unit"], str):
                    errors.append(f"data[{idx}].unit muss string sein")

            # timeZone (optional)
            if "timeZone" in ts_data and ts_data["timeZone"] is not None:
                if not isinstance(ts_data["timeZone"], str):
                    errors.append(f"data[{idx}].timeZone muss string sein")

        return {
            "valid": len(errors) == 0,
            "errors": errors,
            "warnings": warnings,
        }

    @classmethod
    def validate_set_timeseries_data_input(
        cls, payload: list[dict[str, Any]]
    ) -> dict[str, Any]:
        """
        Validiert POST /timeseries/data Payload (SetTimeSeriesDataInput[]).

        Args:
            payload: Liste von SetTimeSeriesDataInput-Objekten

        Returns:
            dict mit Validierungsergebnis
        """
        errors: list[str] = []
        warnings: list[str] = []

        if not isinstance(payload, list):
            errors.append("Payload muss eine Liste sein")
            return {"valid": False, "errors": errors, "warnings": warnings}

        for idx, item in enumerate(payload):
            if not isinstance(item, dict):
                errors.append(f"payload[{idx}] muss ein dict sein")
                continue

            # timeSeriesId (required)
            if "timeSeriesId" not in item:
                errors.append(f"payload[{idx}] fehlt 'timeSeriesId'")
            elif not isinstance(item["timeSeriesId"], int):
                errors.append(f"payload[{idx}].timeSeriesId muss int sein")

            # values (required)
            if "values" not in item:
                errors.append(f"payload[{idx}] fehlt 'values'")
            else:
                values_errors = cls._validate_timeseries_values(
                    item["values"], f"payload[{idx}].values"
                )
                errors.extend(values_errors)

            # interval (optional)
            if "interval" in item and item["interval"] is not None:
                interval_errors = cls._validate_interval(
                    item["interval"], f"payload[{idx}].interval"
                )
                errors.extend(interval_errors)

            # unit (optional)
            if "unit" in item and item["unit"] is not None:
                if not isinstance(item["unit"], str):
                    errors.append(f"payload[{idx}].unit muss string sein")

            # timeZone (optional)
            if "timeZone" in item and item["timeZone"] is not None:
                if not isinstance(item["timeZone"], str):
                    errors.append(f"payload[{idx}].timeZone muss string sein")

            # quotationTime (optional)
            if "quotationTime" in item and item["quotationTime"] is not None:
                if not isinstance(item["quotationTime"], str):
                    errors.append(f"payload[{idx}].quotationTime muss string (ISO format) sein")

        return {
            "valid": len(errors) == 0,
            "errors": errors,
            "warnings": warnings,
        }

    @classmethod
    def validate_quotations_payload(cls, payload: dict[str, Any]) -> dict[str, Any]:
        """
        Validiert QuotationsPayload (GET /timeseries/quotations Response).

        Args:
            payload: Response-Payload (dict)

        Returns:
            dict mit Validierungsergebnis
        """
        errors: list[str] = []
        warnings: list[str] = []

        if not isinstance(payload, dict):
            errors.append("Payload muss ein dict sein")
            return {"valid": False, "errors": errors, "warnings": warnings}

        if "items" not in payload:
            errors.append("Payload muss 'items' enthalten")
            return {"valid": False, "errors": errors, "warnings": warnings}

        if not isinstance(payload["items"], list):
            errors.append("'items' muss eine Liste sein")
            return {"valid": False, "errors": errors, "warnings": warnings}

        # Validiere jedes Quotation-Objekt
        for idx, quot in enumerate(payload["items"]):
            if not isinstance(quot, dict):
                errors.append(f"items[{idx}] muss ein dict sein")
                continue

            # timeSeriesId (required)
            if "timeSeriesId" not in quot:
                errors.append(f"items[{idx}] fehlt 'timeSeriesId'")
            elif not isinstance(quot["timeSeriesId"], int):
                errors.append(f"items[{idx}].timeSeriesId muss int sein")

            # values (required)
            if "values" not in quot:
                errors.append(f"items[{idx}] fehlt 'values'")
            elif not isinstance(quot["values"], list):
                errors.append(f"items[{idx}].values muss eine Liste sein")
            else:
                for val_idx, val in enumerate(quot["values"]):
                    if not isinstance(val, dict):
                        errors.append(f"items[{idx}].values[{val_idx}] muss ein dict sein")
                        continue

                    # time, from, to (all required)
                    for field in ["time", "from", "to"]:
                        if field not in val:
                            errors.append(
                                f"items[{idx}].values[{val_idx}] fehlt '{field}'"
                            )
                        elif not isinstance(val[field], str):
                            errors.append(
                                f"items[{idx}].values[{val_idx}].{field} muss string (ISO format) sein"
                            )

        return {
            "valid": len(errors) == 0,
            "errors": errors,
            "warnings": warnings,
        }

    @classmethod
    def validate_file_upload_response(cls, payload: list[dict[str, Any]]) -> dict[str, Any]:
        """
        Validiert File Upload Response (POST /file/upload).

        Args:
            payload: Liste von File-Objekten

        Returns:
            dict mit Validierungsergebnis
        """
        errors: list[str] = []
        warnings: list[str] = []

        if not isinstance(payload, list):
            errors.append("Payload muss eine Liste sein")
            return {"valid": False, "errors": errors, "warnings": warnings}

        # Validiere jedes File-Objekt
        for idx, file_obj in enumerate(payload):
            if not isinstance(file_obj, dict):
                errors.append(f"payload[{idx}] muss ein dict sein")
                continue

            # Pflichtfelder
            required_fields = {
                "id": int,
                "createdBy": str,
                "createdAt": str,
                "size": int,
                "inventoryPropertyId": int,
                "storageDeleteAttempts": int,
                "auditEnabled": bool,
            }

            for field, expected_type in required_fields.items():
                if field not in file_obj:
                    errors.append(f"payload[{idx}] fehlt '{field}'")
                elif not isinstance(file_obj[field], expected_type):
                    errors.append(
                        f"payload[{idx}].{field} muss {expected_type.__name__} sein"
                    )

            # Optionale Felder
            optional_fields = {
                "name": (str, type(None)),
                "mediaType": (str, type(None)),
                "charset": (str, type(None)),
                "hashAlgorithm": (str, type(None)),
                "hashBase64": (str, type(None)),
                "inventoryItemId": (int, type(None)),
                "deletedAt": (str, type(None)),
            }

            for field, expected_types in optional_fields.items():
                if field in file_obj:
                    if not isinstance(file_obj[field], expected_types):
                        errors.append(
                            f"payload[{idx}].{field} hat falschen Typ: {type(file_obj[field])}"
                        )

        return {
            "valid": len(errors) == 0,
            "errors": errors,
            "warnings": warnings,
        }

    @classmethod
    def validate_timeseries_payload(cls, payload: dict[str, Any]) -> dict[str, Any]:
        """
        Validiert TimeSeriesPayload (GET /audit/timeseries Response).

        Args:
            payload: Response-Payload (dict)

        Returns:
            dict mit Validierungsergebnis
        """
        errors: list[str] = []
        warnings: list[str] = []

        if not isinstance(payload, dict):
            errors.append("Payload muss ein dict sein")
            return {"valid": False, "errors": errors, "warnings": warnings}

        # Pflichtfelder
        required_fields = {
            "id": int,
            "createdBy": str,
            "createdAt": str,
            "alteredBy": str,
            "alteredAt": str,
            "valueAlignment": str,
            "valueAvailability": str,
            "defaultAggregation": str,
            "startOfTime": str,
            "auditEnabled": bool,
            "quotationEnabled": bool,
            "defaultQuotationBehavior": str,
        }

        for field, expected_type in required_fields.items():
            if field not in payload:
                errors.append(f"Payload fehlt '{field}'")
            elif not isinstance(payload[field], expected_type):
                errors.append(f"{field} muss {expected_type.__name__} sein")

        # interval (required)
        if "interval" not in payload:
            errors.append("Payload fehlt 'interval'")
        else:
            interval_errors = cls._validate_interval(payload["interval"], "interval")
            errors.extend(interval_errors)

        # Enum-Validierung
        if "valueAlignment" in payload:
            if payload["valueAlignment"] not in cls.VALID_VALUE_ALIGNMENTS:
                errors.append(
                    f"valueAlignment '{payload['valueAlignment']}' ist ungültig"
                )

        if "valueAvailability" in payload:
            if payload["valueAvailability"] not in cls.VALID_VALUE_AVAILABILITIES:
                errors.append(
                    f"valueAvailability '{payload['valueAvailability']}' ist ungültig"
                )

        if "defaultAggregation" in payload:
            if payload["defaultAggregation"] not in cls.VALID_AGGREGATIONS:
                errors.append(
                    f"defaultAggregation '{payload['defaultAggregation']}' ist ungültig"
                )

        if "defaultQuotationBehavior" in payload:
            if payload["defaultQuotationBehavior"] not in cls.VALID_QUOTATION_BEHAVIORS:
                errors.append(
                    f"defaultQuotationBehavior '{payload['defaultQuotationBehavior']}' ist ungültig"
                )

        # Optionale Felder
        if "unit" in payload and payload["unit"] is not None:
            if not isinstance(payload["unit"], str):
                errors.append("unit muss string sein")

        if "timeZone" in payload and payload["timeZone"] is not None:
            if not isinstance(payload["timeZone"], str):
                errors.append("timeZone muss string sein")

        return {
            "valid": len(errors) == 0,
            "errors": errors,
            "warnings": warnings,
        }

    @classmethod
    def _validate_interval(cls, interval: Any, context: str) -> list[str]:
        """
        Validiert ein Interval-Objekt.

        Args:
            interval: Interval-Objekt (dict)
            context: Kontext für Fehlermeldungen (z.B. "data[0].interval")

        Returns:
            list[str]: Liste von Fehlermeldungen
        """
        errors: list[str] = []

        if not isinstance(interval, dict):
            errors.append(f"{context} muss ein dict sein")
            return errors

        # timeUnit (required)
        if "timeUnit" not in interval:
            errors.append(f"{context} fehlt 'timeUnit'")
        elif not isinstance(interval["timeUnit"], str):
            errors.append(f"{context}.timeUnit muss string sein")
        elif interval["timeUnit"] not in cls.VALID_TIME_UNITS:
            errors.append(f"{context}.timeUnit '{interval['timeUnit']}' ist ungültig")

        # multiplier (required)
        if "multiplier" not in interval:
            errors.append(f"{context} fehlt 'multiplier'")
        elif not isinstance(interval["multiplier"], int):
            errors.append(f"{context}.multiplier muss int sein")
        elif interval["multiplier"] <= 0:
            errors.append(f"{context}.multiplier muss > 0 sein")

        return errors

    @classmethod
    def _validate_timeseries_values(cls, values: Any, context: str) -> list[str]:
        """
        Validiert eine Liste von TimeSeriesValue-Objekten.

        Args:
            values: Liste von TimeSeriesValue-Objekten
            context: Kontext für Fehlermeldungen

        Returns:
            list[str]: Liste von Fehlermeldungen
        """
        errors: list[str] = []

        if not isinstance(values, list):
            errors.append(f"{context} muss eine Liste sein")
            return errors

        for idx, val in enumerate(values):
            if not isinstance(val, dict):
                errors.append(f"{context}[{idx}] muss ein dict sein")
                continue

            # time (required)
            if "time" not in val:
                errors.append(f"{context}[{idx}] fehlt 'time'")
            elif not isinstance(val["time"], str):
                errors.append(f"{context}[{idx}].time muss string (ISO format) sein")

            # value (required)
            if "value" not in val:
                errors.append(f"{context}[{idx}] fehlt 'value'")
            elif not isinstance(val["value"], (int, float)):
                errors.append(f"{context}[{idx}].value muss number sein")

            # quality (optional)
            if "quality" in val and val["quality"] is not None:
                if not isinstance(val["quality"], str):
                    errors.append(f"{context}[{idx}].quality muss string sein")
                elif val["quality"] not in cls.VALID_QUALITIES:
                    errors.append(
                        f"{context}[{idx}].quality '{val['quality']}' ist ungültig"
                    )

        return errors
