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

from __future__ import annotations

from dataclasses import dataclass, field

from src.trading.market_intelligence.common.constants import (
    FORBIDDEN_TRADING_FIELDS,
    MAX_ENGINE_DEPENDENCIES,
    MAX_ENGINE_METRICS,
)
from src.trading.market_intelligence.common.enums import CheckStatus, EngineStatus
from src.trading.market_intelligence.common.models import (
    EngineContext,
    EngineResult,
)
from src.trading.market_intelligence.common.reasons import ReasonCode
from src.trading.market_intelligence.common.types import (
    ContextDict,
    PayloadDict,
    ReasonText,
)


@dataclass(frozen=True, slots=True)
class ValidationIssue:
    # Одна проблема, найденная во время проверки.
    # Это не ошибка Python, а понятное описание нарушения контракта.
    status: CheckStatus
    reason: ReasonCode
    message: ReasonText
    field_name: str | None = None


@dataclass(frozen=True, slots=True)
class ValidationResult:
    # Итог проверки одного объекта.
    # Validation Layer не выбрасывает исключения, а возвращает результат,
    # который можно безопасно записать в журнал или диагностику.
    status: CheckStatus = CheckStatus.OK
    reason: ReasonCode = ReasonCode.VALIDATION_PASSED
    issues: tuple[ValidationIssue, ...] = ()
    details: ContextDict = field(default_factory=dict)

    @property
    def is_ok(self) -> bool:
        # Проверка считается успешной только если нет ошибок.
        return self.status == CheckStatus.OK

    @property
    def has_errors(self) -> bool:
        return any(issue.status == CheckStatus.ERROR for issue in self.issues)

    @property
    def has_warnings(self) -> bool:
        return any(issue.status == CheckStatus.WARNING for issue in self.issues)


def _validation_result_from_issues(
    issues: tuple[ValidationIssue, ...],
    *,
    details: ContextDict | None = None,
) -> ValidationResult:
    # Собираем общий статус из списка найденных проблем.
    # Ошибка важнее предупреждения, предупреждение важнее OK.
    if any(issue.status == CheckStatus.ERROR for issue in issues):
        return ValidationResult(
            status=CheckStatus.ERROR,
            reason=ReasonCode.VALIDATION_FAILED,
            issues=issues,
            details=details or {},
        )

    if any(issue.status == CheckStatus.WARNING for issue in issues):
        return ValidationResult(
            status=CheckStatus.WARNING,
            reason=ReasonCode.VALIDATION_PASSED,
            issues=issues,
            details=details or {},
        )

    return ValidationResult(
        status=CheckStatus.OK,
        reason=ReasonCode.VALIDATION_PASSED,
        issues=issues,
        details=details or {},
    )


def validate_payload_has_no_trading_fields(
    payload: PayloadDict,
) -> ValidationResult:
    # Market Intelligence не должен отдавать торговые команды.
    # Поэтому payload проверяется на поля, похожие на действия торговли.
    issues: list[ValidationIssue] = []

    for field_name in sorted(FORBIDDEN_TRADING_FIELDS):
        if field_name in payload:
            issues.append(
                ValidationIssue(
                    status=CheckStatus.ERROR,
                    reason=ReasonCode.FORBIDDEN_TRADING_FIELD_FOUND,
                    message=(
                        "Payload содержит поле, запрещённое для "
                        "аналитического слоя Market Intelligence."
                    ),
                    field_name=field_name,
                )
            )

    return _validation_result_from_issues(
        tuple(issues),
        details={"checked_fields": sorted(FORBIDDEN_TRADING_FIELDS)},
    )


def validate_engine_context(context: EngineContext) -> ValidationResult:
    # Проверяем только базовый входной контракт Engine.
    # Глубокая проверка рыночных данных будет задачей отдельных Engine,
    # потому что разные движки могут требовать разные данные.
    issues: list[ValidationIssue] = []

    if not context.symbol:
        issues.append(
            ValidationIssue(
                status=CheckStatus.ERROR,
                reason=ReasonCode.REQUIRED_FIELD_MISSING,
                message="В EngineContext не указан торговый инструмент.",
                field_name="symbol",
            )
        )

    if not context.timeframe:
        issues.append(
            ValidationIssue(
                status=CheckStatus.ERROR,
                reason=ReasonCode.REQUIRED_FIELD_MISSING,
                message="В EngineContext не указан таймфрейм анализа.",
                field_name="timeframe",
            )
        )

    if not context.market_data:
        issues.append(
            ValidationIssue(
                status=CheckStatus.ERROR,
                reason=ReasonCode.EMPTY_MARKET_DATA,
                message="В EngineContext отсутствуют рыночные данные.",
                field_name="market_data",
            )
        )

    if len(context.dependency_results) > MAX_ENGINE_DEPENDENCIES:
        issues.append(
            ValidationIssue(
                status=CheckStatus.WARNING,
                reason=ReasonCode.ENGINE_DEPENDENCY_ERROR,
                message=(
                    "Количество зависимостей Engine превышает "
                    "архитектурный лимит Common Layer."
                ),
                field_name="dependency_results",
            )
        )

    return _validation_result_from_issues(
        tuple(issues),
        details={
            "symbol": context.symbol,
            "timeframe": context.timeframe,
            "dependency_count": len(context.dependency_results),
        },
    )


def validate_engine_result(result: EngineResult) -> ValidationResult:
    # Проверяем единый результат Engine.
    # Эта проверка не оценивает качество анализа рынка,
    # а только подтверждает соблюдение Runtime Contract.
    issues: list[ValidationIssue] = []

    if not result.engine_name:
        issues.append(
            ValidationIssue(
                status=CheckStatus.ERROR,
                reason=ReasonCode.REQUIRED_FIELD_MISSING,
                message="В EngineResult не указано имя движка.",
                field_name="engine_name",
            )
        )

    if not result.engine_version:
        issues.append(
            ValidationIssue(
                status=CheckStatus.ERROR,
                reason=ReasonCode.REQUIRED_FIELD_MISSING,
                message="В EngineResult не указана версия движка.",
                field_name="engine_version",
            )
        )

    if not result.symbol:
        issues.append(
            ValidationIssue(
                status=CheckStatus.ERROR,
                reason=ReasonCode.REQUIRED_FIELD_MISSING,
                message="В EngineResult не указан торговый инструмент.",
                field_name="symbol",
            )
        )

    if not result.timeframe:
        issues.append(
            ValidationIssue(
                status=CheckStatus.ERROR,
                reason=ReasonCode.REQUIRED_FIELD_MISSING,
                message="В EngineResult не указан таймфрейм анализа.",
                field_name="timeframe",
            )
        )

    if result.status == EngineStatus.UNKNOWN:
        issues.append(
            ValidationIssue(
                status=CheckStatus.WARNING,
                reason=ReasonCode.VALIDATION_FAILED,
                message="EngineResult вернул неопределённый статус выполнения.",
                field_name="status",
            )
        )

    if len(result.metrics) > MAX_ENGINE_METRICS:
        issues.append(
            ValidationIssue(
                status=CheckStatus.WARNING,
                reason=ReasonCode.VALIDATION_FAILED,
                message=(
                    "Количество метрик Engine превышает архитектурный лимит. "
                    "Возможно, движок выполняет слишком много задач."
                ),
                field_name="metrics",
            )
        )

    payload_validation = validate_payload_has_no_trading_fields(result.payload)
    issues.extend(payload_validation.issues)

    return _validation_result_from_issues(
        tuple(issues),
        details={
            "engine_name": result.engine_name,
            "engine_version": result.engine_version,
            "symbol": result.symbol,
            "timeframe": result.timeframe,
            "status": result.status.value,
            "metrics_count": len(result.metrics),
        },
    )