# app/src/trading/market_intelligence/common/payloads.py

from __future__ import annotations

from dataclasses import asdict, is_dataclass
from enum import Enum
from typing import Any, Mapping, cast

from src.trading.market_intelligence.common.models import (
    EngineDependencyResult,
    EngineDiagnostics,
    EngineMetric,
    EngineResult,
)
from src.trading.market_intelligence.common.scores import (
    EngineConfidence,
    EngineScore,
)
from src.trading.market_intelligence.common.types import PayloadDict
from src.trading.market_intelligence.common.validation import (
    validate_payload_has_no_trading_fields,
)


def value_to_payload(value: Any) -> Any:
    # Приводит значение к виду, который безопасно положить в payload.
    # Payload должен состоять из простых структур: dict, list, str, int,
    # float, bool или None. Так его можно сохранить в журнал, событие
    # или будущий snapshot.
    if isinstance(value, Enum):
        return value.value

    if is_dataclass(value):
        # is_dataclass() возвращает True и для экземпляров dataclass,
        # и для самих классов dataclass. asdict() работает только
        # с экземпляром, поэтому класс пропускаем как обычное значение.
        if not isinstance(value, type):
            return mapping_to_payload(asdict(value))

    if isinstance(value, Mapping):
        return mapping_to_payload(value)

    if isinstance(value, (tuple, list)):
        return [value_to_payload(item) for item in value]

    return value


def mapping_to_payload(mapping: Mapping[str, Any]) -> PayloadDict:
    # Преобразует словарь в безопасный payload.
    # Все вложенные enum, dataclass, list и tuple приводятся
    # к простым значениям.
    return {
        str(key): value_to_payload(value)
        for key, value in mapping.items()
    }


def engine_score_to_payload(score: EngineScore) -> PayloadDict:
    # Сериализует оценку Engine.
    # Оценка не является торговым решением,
    # это только числовое качество анализа.
    return {
        "value": value_to_payload(score.value),
        "quality": value_to_payload(score.quality),
        "reason": value_to_payload(score.reason),
    }


def engine_confidence_to_payload(confidence: EngineConfidence) -> PayloadDict:
    # Сериализует уверенность Engine.
    # Уверенность показывает надёжность результата,
    # а не торговую рекомендацию.
    return {
        "value": value_to_payload(confidence.value),
        "level": value_to_payload(confidence.level),
        "reason": value_to_payload(confidence.reason),
        "is_reliable": confidence.is_reliable,
    }


def engine_metric_to_payload(metric: EngineMetric) -> PayloadDict:
    # Сериализует одну измеримую метрику Engine.
    return {
        "name": metric.name,
        "value": value_to_payload(metric.value),
        "unit": metric.unit,
        "description": metric.description,
    }


def engine_diagnostics_to_payload(
    diagnostics: EngineDiagnostics,
) -> PayloadDict:
    # Сериализует диагностику Engine.
    # Диагностика нужна для журнала и отладки,
    # но не является пользовательским UI-текстом.
    return {
        "reason": value_to_payload(diagnostics.reason),
        "details": mapping_to_payload(diagnostics.details),
        "warnings": value_to_payload(diagnostics.warnings),
        "errors": value_to_payload(diagnostics.errors),
        "has_warnings": diagnostics.has_warnings,
        "has_errors": diagnostics.has_errors,
    }


def engine_dependency_to_payload(
    dependency: EngineDependencyResult,
) -> PayloadDict:
    # Сериализует краткий результат зависимого Engine.
    # Это позволяет передавать результат зависимости
    # без прямого импорта самого Engine.
    return {
        "engine_name": dependency.engine_name,
        "status": value_to_payload(dependency.status),
        "score": engine_score_to_payload(dependency.score),
        "confidence": engine_confidence_to_payload(dependency.confidence),
        "reason": value_to_payload(dependency.reason),
        "updated_at": dependency.updated_at,
    }


def engine_result_to_payload(result: EngineResult) -> PayloadDict:
    # Преобразует EngineResult в единый payload.
    # Функция не меняет результат Engine и не добавляет торговые поля.
    payload: PayloadDict = {
        "engine_name": result.engine_name,
        "engine_version": result.engine_version,
        "symbol": result.symbol,
        "timeframe": result.timeframe,
        "status": value_to_payload(result.status),
        "reason": value_to_payload(result.reason),
        "score": engine_score_to_payload(result.score),
        "confidence": engine_confidence_to_payload(result.confidence),
        "metrics": [
            engine_metric_to_payload(metric)
            for metric in result.metrics
        ],
        "diagnostics": engine_diagnostics_to_payload(result.diagnostics),
        "dependencies": [
            engine_dependency_to_payload(dependency)
            for dependency in result.dependencies
        ],
        "meta": value_to_payload(result.meta),
        "payload": mapping_to_payload(result.payload),
        "direction": value_to_payload(result.direction),
        "bias": value_to_payload(result.bias),
        "phase": value_to_payload(result.phase),
        "regime": value_to_payload(result.regime),
        "risk_level": value_to_payload(result.risk_level),
        "is_ok": result.is_ok,
        "is_usable": result.is_usable,
        "has_errors": result.has_errors,
    }

    validation = validate_payload_has_no_trading_fields(payload)

    if validation.is_ok:
        return payload

    # Если итоговый payload нарушил границы Market Intelligence,
    # мы не скрываем проблему, а добавляем диагностический блок.
    # Это не исправляет payload автоматически, чтобы нарушение
    # было видно в журнале и при Engineering Review.
    payload["payload_validation"] = {
        "status": value_to_payload(validation.status),
        "reason": value_to_payload(validation.reason),
        "issues": value_to_payload(validation.issues),
        "details": mapping_to_payload(validation.details),
    }

    return cast(PayloadDict, payload)