# app/src/trading/market_intelligence/engine/base.py

from __future__ import annotations

import time
from abc import ABC, abstractmethod
from dataclasses import replace
from typing import ClassVar

from src.trading.market_intelligence.common.enums import EngineStatus
from src.trading.market_intelligence.common.models import (
    EngineContext,
    EngineDiagnostics,
    EngineEvaluationMeta,
    EngineMetadata,
    EngineResult,
)
from src.trading.market_intelligence.common.reasons import ReasonCode
from src.trading.market_intelligence.engine.protocol import EngineProtocol
from src.trading.market_intelligence.engine.exceptions import (
    EngineConfigurationError,
    EngineContextError,
    EngineContractError,
)


class BaseEngine(EngineProtocol, ABC):
    """Базовая инфраструктура аналитического Engine."""

    ENGINE_METADATA: ClassVar[EngineMetadata | None] = None

    @classmethod
    def get_metadata(cls) -> EngineMetadata:
        """Вернуть metadata Engine без создания экземпляра."""
        if cls.ENGINE_METADATA is None:
            raise EngineConfigurationError("ENGINE_METADATA must be defined")

        return cls.ENGINE_METADATA

    async def analyze(
        self,
        context: EngineContext,
    ) -> EngineResult:
        """Выполнить единый жизненный цикл анализа."""
        started_at = time.perf_counter()

        try:
            self._validate_context(context)

            result = await self._analyze_impl(context)

            duration_ms = (time.perf_counter() - started_at) * 1000.0
            return self._normalize_result(result, context, duration_ms)

        except Exception as error:
            duration_ms = (time.perf_counter() - started_at) * 1000.0
            return self._build_error_result(context, error, duration_ms)

    def _validate_context(
        self,
        context: EngineContext,
    ) -> None:
        """Проверить минимальную корректность входного контекста."""
        if not context.symbol:
            raise EngineContextError("EngineContext.symbol is required")

        if not context.timeframe:
            raise EngineContextError("EngineContext.timeframe is required")

    @abstractmethod
    async def _analyze_impl(
        self,
        context: EngineContext,
    ) -> EngineResult:
        """Выполнить предметный анализ в конкретном Engine."""
        raise NotImplementedError

    def _normalize_result(
        self,
        result: EngineResult,
        context: EngineContext,
        duration_ms: float,
    ) -> EngineResult:
        """Дополнить результат служебной metadata выполнения."""
        metadata = self.get_metadata()

        if not isinstance(result, EngineResult):
            raise EngineContractError("_analyze_impl() must return EngineResult")

        return replace(
            result,
            engine_name=metadata.name,
            engine_version=metadata.version,
            symbol=context.symbol,
            timeframe=context.timeframe,
            meta=EngineEvaluationMeta(
                engine_name=metadata.name,
                engine_version=metadata.version,
                calculated_at=time.time(),
                duration_ms=duration_ms,
            ),
        )

    def _build_error_result(
        self,
        context: EngineContext,
        error: Exception,
        duration_ms: float,
    ) -> EngineResult:
        """Сформировать безопасный результат при ошибке Engine."""
        metadata = self.get_metadata()

        return EngineResult(
            engine_name=metadata.name,
            engine_version=metadata.version,
            symbol=context.symbol,
            timeframe=context.timeframe,
            status=EngineStatus.ERROR,
            reason=ReasonCode.UNKNOWN,
            diagnostics=EngineDiagnostics(
                reason=ReasonCode.UNKNOWN,
                errors=[str(error)],
            ),
            meta=EngineEvaluationMeta(
                engine_name=metadata.name,
                engine_version=metadata.version,
                calculated_at=time.time(),
                duration_ms=duration_ms,
            ),
        )