"""Application service for EDA runtime stage orchestration."""
from __future__ import annotations

from datetime import datetime
from time import perf_counter
from typing import Callable, List
import uuid

from fastapi import HTTPException, Request
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from eda_agent.config import settings
from eda_agent.engine import EDAEngine
from eda_agent.models.persistence import AgentProfile, DecisionTrace
from eda_agent.schemas import (
    AgentStateResponse,
    BindNormsRequest,
    DecideRequest,
    DecisionResponse,
    ExecuteRequest,
    ExecutionResponse,
    PerceiveRequest,
    TraceResponse,
)
from eda_agent.services.agent_service import AgentService
from eda_agent.services.field_service import FieldService
from eda_agent.infra.normbase_client import HttpNormbaseClient
from eda_agent.infra.llm import get_llm_provider
from eda_agent.services.trace_service import TraceService


class RuntimeService:
    """Service layer for perceive/bind/decide/execute flows."""

    def __init__(
        self,
        agent_service: AgentService | None = None,
        field_service: FieldService | None = None,
        trace_service: TraceService | None = None,
        engine_builder: Callable[[AgentProfile], EDAEngine] | None = None,
    ):
        self.field_service = field_service or FieldService()
        self.agent_service = agent_service or AgentService(self.field_service)
        self.trace_service = trace_service or TraceService()
        self.engine_builder = engine_builder

    def _build_engine(self, profile: AgentProfile) -> EDAEngine:
        if self.engine_builder is not None:
            return self.engine_builder(profile)
        provider = get_llm_provider()
        normbase = HttpNormbaseClient(
            base_url=settings.eda_normbase_base_url,
            retrieve_timeout_seconds=settings.eda_normbase_retrieve_timeout_seconds,
        )
        return EDAEngine(normbase_client=normbase, llm_provider=provider)

    async def perceive(
        self,
        db: AsyncSession,
        *,
        agent_id: uuid.UUID,
        request: PerceiveRequest,
        raw_request: Request,
    ) -> AgentStateResponse:
        profile, snapshot = await self.agent_service.get_profile_and_snapshot(db, agent_id)
        state = self.agent_service.state_from_snapshot(profile, snapshot)
        engine = self._build_engine(profile)

        start = perf_counter()
        updated = await engine.perceive(state, request.input_data)
        latency_ms = int((perf_counter() - start) * 1000)

        payload = updated.to_dict()
        snapshot.beliefs = payload["beliefs"]
        snapshot.norm_bindings = payload["norm_bindings"]
        snapshot.value_weights = payload["value_weights"]
        snapshot.active_fields = payload["active_fields"]
        snapshot.current_intention = payload["current_intention"]
        snapshot.trust_score = payload["trust_score"]
        snapshot.updated_at = datetime.now()

        await self.trace_service.write_trace(
            db=db,
            agent_id=profile.id,
            stage="perceive",
            input_payload=request.input_data,
            norms_applied=payload["norm_bindings"],
            compliance_result={},
            value_ranking=[],
            final_action=None,
            result_payload={
                "belief_count": len(updated.beliefs),
                "identity": self.trace_service.identity_from_request(raw_request),
            },
            latency_ms=latency_ms,
        )
        await db.commit()
        return self.agent_service.state_to_response(updated, profile, snapshot)

    async def bind_norms(
        self,
        db: AsyncSession,
        *,
        agent_id: uuid.UUID,
        request: BindNormsRequest,
        raw_request: Request,
    ) -> AgentStateResponse:
        profile, snapshot = await self.agent_service.get_profile_and_snapshot(db, agent_id)
        state = self.agent_service.state_from_snapshot(profile, snapshot)
        state.active_fields = await self.field_service.validate_active_fields(db, state.active_fields)
        engine = self._build_engine(profile)

        start = perf_counter()
        try:
            updated = await engine.bind_norms(state, request.context)
            latency_ms = int((perf_counter() - start) * 1000)
        except Exception as exc:
            latency_ms = int((perf_counter() - start) * 1000)
            await self.trace_service.write_trace(
                db=db,
                agent_id=profile.id,
                stage="bind_norms_failed",
                input_payload=request.context,
                norms_applied=[],
                compliance_result={},
                value_ranking=[],
                final_action=None,
                result_payload={
                    "error": str(exc),
                    "identity": self.trace_service.identity_from_request(raw_request),
                },
                latency_ms=latency_ms,
            )
            await db.commit()
            raise HTTPException(
                status_code=503,
                detail="Normbase unavailable or retrieval failed during bind_norms.",
            ) from exc

        payload = updated.to_dict()
        snapshot.beliefs = payload["beliefs"]
        snapshot.norm_bindings = payload["norm_bindings"]
        snapshot.value_weights = payload["value_weights"]
        snapshot.active_fields = payload["active_fields"]
        snapshot.current_intention = payload["current_intention"]
        snapshot.trust_score = payload["trust_score"]
        snapshot.updated_at = datetime.now()

        await self.trace_service.write_trace(
            db=db,
            agent_id=profile.id,
            stage="bind_norms",
            input_payload=request.context,
            norms_applied=payload["norm_bindings"],
            compliance_result={},
            value_ranking=[],
            final_action=None,
            result_payload={
                "norm_binding_count": len(updated.norm_bindings),
                "bind_prompt": engine.last_bind_debug or {},
                "identity": self.trace_service.identity_from_request(raw_request),
            },
            latency_ms=latency_ms,
        )
        await db.commit()
        return self.agent_service.state_to_response(updated, profile, snapshot)

    async def decide(
        self,
        db: AsyncSession,
        *,
        agent_id: uuid.UUID,
        request: DecideRequest,
        raw_request: Request,
    ) -> DecisionResponse:
        profile, snapshot = await self.agent_service.get_profile_and_snapshot(db, agent_id)
        state = self.agent_service.state_from_snapshot(profile, snapshot)
        engine = self._build_engine(profile)

        start = perf_counter()
        ranking = await engine.rank_values(state, request.candidate_actions)
        selected = await engine.decide(state, request.candidate_actions)
        latency_ms = int((perf_counter() - start) * 1000)

        payload = state.to_dict()
        snapshot.current_intention = payload["current_intention"]
        snapshot.updated_at = datetime.now()

        await self.trace_service.write_trace(
            db=db,
            agent_id=profile.id,
            stage="decide",
            input_payload={"candidate_actions": request.candidate_actions},
            norms_applied=payload["norm_bindings"],
            compliance_result={},
            value_ranking=ranking,
            final_action=selected,
            result_payload={
                "selected": selected,
                "identity": self.trace_service.identity_from_request(raw_request),
            },
            latency_ms=latency_ms,
        )
        await db.commit()
        return DecisionResponse(selected_action=selected, ranking=ranking)

    async def execute(
        self,
        db: AsyncSession,
        *,
        agent_id: uuid.UUID,
        request: ExecuteRequest,
        raw_request: Request,
    ) -> ExecutionResponse:
        profile, snapshot = await self.agent_service.get_profile_and_snapshot(db, agent_id)
        state = self.agent_service.state_from_snapshot(profile, snapshot)
        engine = self._build_engine(profile)

        start = perf_counter()
        result = await engine.execute_action(state, request.action)
        latency_ms = int((perf_counter() - start) * 1000)

        payload = state.to_dict()
        snapshot.beliefs = payload["beliefs"]
        snapshot.norm_bindings = payload["norm_bindings"]
        snapshot.value_weights = payload["value_weights"]
        snapshot.active_fields = payload["active_fields"]
        snapshot.current_intention = payload["current_intention"]
        snapshot.trust_score = payload["trust_score"]
        snapshot.updated_at = datetime.now()

        trace = await self.trace_service.write_trace(
            db=db,
            agent_id=profile.id,
            stage="execute",
            input_payload=request.action,
            norms_applied=payload["norm_bindings"],
            compliance_result=result.get("compliance", {}),
            value_ranking=[],
            final_action=request.action,
            result_payload={**result, "identity": self.trace_service.identity_from_request(raw_request)},
            latency_ms=latency_ms,
        )
        await db.commit()
        return ExecutionResponse(
            success=result.get("success", False),
            reason=result.get("reason"),
            compliance=result.get("compliance"),
            trace_id=str(trace.id),
        )

    async def get_traces(
        self,
        db: AsyncSession,
        *,
        agent_id: uuid.UUID,
        limit: int,
    ) -> List[TraceResponse]:
        await self.agent_service.get_profile_and_snapshot(db, agent_id)
        rows = (
            await db.execute(
                select(DecisionTrace)
                .where(DecisionTrace.agent_id == agent_id)
                .order_by(DecisionTrace.created_at.desc())
                .limit(limit)
            )
        ).scalars().all()
        return [
            TraceResponse(
                id=str(r.id),
                agent_id=r.agent_id,
                stage=r.stage,
                input_payload=r.input_payload or {},
                norms_applied=r.norms_applied or [],
                compliance_result=r.compliance_result or {},
                value_ranking=r.value_ranking or [],
                final_action=r.final_action,
                result_payload=r.result_payload or {},
                latency_ms=r.latency_ms,
                created_at=r.created_at,
            )
            for r in rows
        ]
