"""FormulaEvaluatorService: orchestrates render + sanitize + sequence + simpleeval.

The public entry point. Consumers construct one instance per service (DI
singleton is fine) and call ``await evaluator.evaluate(formula, record,
entity_id=..., app_id=...)``.
"""
from typing import Any, Dict, List, Optional

from motor.motor_asyncio import AsyncIOMotorDatabase
from simpleeval import DEFAULT_NAMES, EvalWithCompoundTypes

from .exceptions import FormulaEvaluationError, MissingFieldError
from .functions import FUNCTIONS
from .handlers import HandlerContext, resolve_field_token
from .render import render_formula
from .sanitize import sanitize_formula
from .sequence import preprocess_sequence_calls


class FormulaEvaluatorService:
    """Stateless service. Inject one instance per app via your DI container.

    Args:
        db: Motor ``AsyncIOMotorDatabase`` handle. Required to resolve
            field definitions, follow association references, and run
            SEQUENCE() preprocessing. May be ``None`` only for tests/
            smoke checks that pre-supply all data.
    """

    def __init__(self, db: Optional[AsyncIOMotorDatabase] = None):
        self.db = db

    async def evaluate(
        self,
        formula: List[Any],
        record: Dict[str, Any],
        entity_id: Optional[str] = None,
        app_id: Optional[str] = None,
    ) -> Any:
        """Evaluate a formula token list against a record and return the value.

        Raises:
            MissingFieldError: A referenced record field is None/missing.
            FormulaEvaluationError: Expression is malformed or simpleeval
                rejects the rendered string.
        """
        if not formula or not isinstance(formula, list):
            raise FormulaEvaluationError("Formula is empty or not a list")

        ctx = HandlerContext(db=self.db)

        async def _resolve(token: Dict[str, Any], rec: Dict[str, Any]) -> Any:
            return await resolve_field_token(token, rec, ctx)

        try:
            expr = await render_formula(formula, record, _resolve)
        except MissingFieldError:
            raise
        except ValueError as e:
            raise FormulaEvaluationError(str(e))

        expr = sanitize_formula(expr)
        if not expr:
            raise FormulaEvaluationError("Formula resolved to an empty expression")

        if self.db is not None and "SEQUENCE" in expr:
            expr = await preprocess_sequence_calls(self.db, expr, entity_id, app_id)

        evaluator = EvalWithCompoundTypes(functions=FUNCTIONS, names=DEFAULT_NAMES)
        try:
            value = evaluator.eval(expr)
        except Exception as e:
            raise FormulaEvaluationError(f"Formula evaluation failed: {e}", expression=expr)

        return value
