"""EDA Agent API routes."""
from __future__ import annotations

from datetime import datetime
from time import perf_counter
from typing import List

from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from eda_agent.engine import EDAEngine
from eda_agent.models.persistence import AgentProfile, AgentStateSnapshot, DecisionTrace
from eda_agent.schemas import (
    AgentCreateRequest,
    AgentUpdateRequest,
    AgentStateResponse,
    AgentSummaryResponse,
    BindNormsRequest,
    DecideRequest,
    DecisionResponse,
    ExecuteRequest,
    ExecutionResponse,
    PerceiveRequest,
    TraceResponse,
)
from eda_agent.services.normbase_client import HttpNormbaseClient
from eda_agent.services.provider_builder import build_llm_provider
from eda_agent.state import EDAState, ValueWeight
from eda_agent.config import settings
from eda_agent.db.connection import get_db

router = APIRouter(prefix="/agents", tags=["eda-agent"])


def _identity_from_request(request: Request) -> dict:
    headers = request.headers
    return {
        "agent_id": headers.get("x-agent-id"),
        "key_id": headers.get("x-key-id"),
        "request_id": headers.get("x-request-id"),
        "caller_id": headers.get("x-caller-id"),
    }


def _state_from_snapshot(profile: AgentProfile, snapshot: AgentStateSnapshot) -> EDAState:
    payload = {
        "agent_id": profile.agent_id,
        "agent_role": profile.agent_role,
        "beliefs": snapshot.beliefs or {},
        "norm_bindings": snapshot.norm_bindings or [],
        "value_weights": snapshot.value_weights or [],
        "active_fields": snapshot.active_fields or [],
        "current_intention": snapshot.current_intention,
        "trust_score": snapshot.trust_score,
        "created_at": snapshot.created_at.isoformat(),
        "updated_at": snapshot.updated_at.isoformat(),
    }
    return EDAState.from_dict(payload)


def _state_to_response(state: EDAState) -> AgentStateResponse:
    data = state.to_dict()
    return AgentStateResponse(
        agent_id=data["agent_id"],
        agent_role=data["agent_role"],
        beliefs=data["beliefs"],
        norm_bindings=data["norm_bindings"],
        value_weights=data["value_weights"],
        active_fields=data["active_fields"],
        current_intention=data["current_intention"],
        trust_score=data["trust_score"],
        created_at=state.created_at,
        updated_at=state.updated_at,
    )


async def _get_profile_and_snapshot(
    db: AsyncSession, agent_id: str
) -> tuple[AgentProfile, AgentStateSnapshot]:
    profile = (
        await db.execute(select(AgentProfile).where(AgentProfile.agent_id == agent_id))
    ).scalar_one_or_none()
    if not profile:
        raise HTTPException(status_code=404, detail=f"Agent {agent_id} not found")

    snapshot = (
        await db.execute(
            select(AgentStateSnapshot).where(AgentStateSnapshot.agent_id == agent_id)
        )
    ).scalar_one_or_none()
    if not snapshot:
        raise HTTPException(
            status_code=500, detail=f"State snapshot missing for agent {agent_id}"
        )
    return profile, snapshot


def _build_engine(profile: AgentProfile) -> EDAEngine:
    provider = build_llm_provider(
        provider_name=profile.llm_provider,
        api_key=profile.llm_secret_ref or "",
        model=profile.llm_model,
        embedding_model=profile.llm_embedding_model,
        base_url=profile.llm_base_url,
    )
    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 _write_trace(
    db: AsyncSession,
    agent_id: str,
    stage: str,
    input_payload: dict,
    norms_applied: list,
    compliance_result: dict,
    value_ranking: list,
    final_action: dict | None,
    result_payload: dict,
    latency_ms: int,
) -> DecisionTrace:
    trace = DecisionTrace(
        agent_id=agent_id,
        stage=stage,
        input_payload=input_payload,
        norms_applied=norms_applied,
        compliance_result=compliance_result,
        value_ranking=value_ranking,
        final_action=final_action,
        result_payload=result_payload,
        latency_ms=latency_ms,
    )
    db.add(trace)
    await db.flush()
    return trace


@router.post("", response_model=AgentSummaryResponse, status_code=status.HTTP_201_CREATED)
async def create_agent(
    request: AgentCreateRequest,
    db: AsyncSession = Depends(get_db),
):
    exists = (
        await db.execute(select(AgentProfile).where(AgentProfile.agent_id == request.agent_id))
    ).scalar_one_or_none()
    if exists:
        raise HTTPException(status_code=409, detail=f"Agent {request.agent_id} already exists")

    profile = AgentProfile(
        agent_id=request.agent_id,
        agent_role=request.agent_role,
        llm_provider=request.llm_provider,
        llm_model=request.llm_model,
        llm_embedding_model=request.llm_embedding_model,
        llm_base_url=request.llm_base_url,
        llm_secret_ref=request.llm_api_key,
    )
    db.add(profile)

    value_weights = [
        ValueWeight(
            value_name=i.value_name,
            weight=i.weight,
            hierarchy=i.hierarchy,
            rationale=i.rationale,
        )
        for i in request.value_weights
    ]
    initial_state = EDAState(
        agent_id=request.agent_id,
        agent_role=request.agent_role,
        value_weights=value_weights,
        active_fields=request.active_fields,
        trust_score=request.trust_score,
    )
    snapshot = AgentStateSnapshot(
        agent_id=request.agent_id,
        beliefs=initial_state.to_dict()["beliefs"],
        norm_bindings=initial_state.to_dict()["norm_bindings"],
        value_weights=initial_state.to_dict()["value_weights"],
        active_fields=initial_state.to_dict()["active_fields"],
        current_intention=initial_state.current_intention,
        trust_score=initial_state.trust_score,
    )
    db.add(snapshot)
    await db.commit()
    await db.refresh(profile)
    return AgentSummaryResponse.model_validate(profile)


@router.get("", response_model=List[AgentSummaryResponse])
async def list_agents(
    limit: int = Query(100, ge=1, le=500),
    db: AsyncSession = Depends(get_db),
):
    rows = (
        await db.execute(
            select(AgentProfile).order_by(AgentProfile.updated_at.desc()).limit(limit)
        )
    ).scalars().all()
    return [AgentSummaryResponse.model_validate(row) for row in rows]


@router.get("/{agent_id}", response_model=AgentStateResponse)
async def get_agent_state(agent_id: str, db: AsyncSession = Depends(get_db)):
    profile, snapshot = await _get_profile_and_snapshot(db, agent_id)
    state = _state_from_snapshot(profile, snapshot)
    return _state_to_response(state)


@router.patch("/{agent_id}", response_model=AgentSummaryResponse)
async def update_agent(
    agent_id: str,
    request: AgentUpdateRequest,
    db: AsyncSession = Depends(get_db),
):
    profile, snapshot = await _get_profile_and_snapshot(db, agent_id)
    update_data = request.model_dump(exclude_unset=True)

    if "agent_role" in update_data:
        profile.agent_role = update_data["agent_role"]
    if "llm_provider" in update_data:
        profile.llm_provider = update_data["llm_provider"]
    if "llm_model" in update_data:
        profile.llm_model = update_data["llm_model"]
    if "llm_embedding_model" in update_data:
        profile.llm_embedding_model = update_data["llm_embedding_model"]
    if "llm_base_url" in update_data:
        profile.llm_base_url = update_data["llm_base_url"]
    if "llm_api_key" in update_data:
        profile.llm_secret_ref = update_data["llm_api_key"]

    if "active_fields" in update_data:
        snapshot.active_fields = update_data["active_fields"] or []
    if "trust_score" in update_data and update_data["trust_score"] is not None:
        snapshot.trust_score = update_data["trust_score"]
    profile.updated_at = datetime.now()
    snapshot.updated_at = datetime.now()
    await db.commit()
    await db.refresh(profile)
    return AgentSummaryResponse.model_validate(profile)


@router.delete("/{agent_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_agent(agent_id: str, db: AsyncSession = Depends(get_db)):
    profile, _snapshot = await _get_profile_and_snapshot(db, agent_id)
    await db.delete(profile)
    await db.commit()


@router.post("/{agent_id}/perceive", response_model=AgentStateResponse)
async def perceive(
    agent_id: str,
    request: PerceiveRequest,
    raw_request: Request,
    db: AsyncSession = Depends(get_db),
):
    profile, snapshot = await _get_profile_and_snapshot(db, agent_id)
    state = _state_from_snapshot(profile, snapshot)
    engine = _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 _write_trace(
        db=db,
        agent_id=agent_id,
        stage="perceive",
        input_payload=request.input_data,
        norms_applied=updated.to_dict()["norm_bindings"],
        compliance_result={},
        value_ranking=[],
        final_action=None,
        result_payload={
            "belief_count": len(updated.beliefs),
            "identity": _identity_from_request(raw_request),
        },
        latency_ms=latency_ms,
    )
    await db.commit()
    return _state_to_response(updated)


@router.post("/{agent_id}/bind_norms", response_model=AgentStateResponse)
async def bind_norms(
    agent_id: str,
    request: BindNormsRequest,
    raw_request: Request,
    db: AsyncSession = Depends(get_db),
):
    profile, snapshot = await _get_profile_and_snapshot(db, agent_id)
    state = _state_from_snapshot(profile, snapshot)
    engine = _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 _write_trace(
            db=db,
            agent_id=agent_id,
            stage="bind_norms_failed",
            input_payload=request.context,
            norms_applied=[],
            compliance_result={},
            value_ranking=[],
            final_action=None,
            result_payload={
                "error": str(exc),
                "identity": _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 _write_trace(
        db=db,
        agent_id=agent_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": _identity_from_request(raw_request),
        },
        latency_ms=latency_ms,
    )
    await db.commit()
    return _state_to_response(updated)


@router.post("/{agent_id}/decide", response_model=DecisionResponse)
async def decide(
    agent_id: str,
    request: DecideRequest,
    raw_request: Request,
    db: AsyncSession = Depends(get_db),
):
    profile, snapshot = await _get_profile_and_snapshot(db, agent_id)
    state = _state_from_snapshot(profile, snapshot)
    engine = _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 _write_trace(
        db=db,
        agent_id=agent_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": _identity_from_request(raw_request),
        },
        latency_ms=latency_ms,
    )
    await db.commit()

    return DecisionResponse(selected_action=selected, ranking=ranking)


@router.post("/{agent_id}/execute", response_model=ExecutionResponse)
async def execute(
    agent_id: str,
    request: ExecuteRequest,
    raw_request: Request,
    db: AsyncSession = Depends(get_db),
):
    profile, snapshot = await _get_profile_and_snapshot(db, agent_id)
    state = _state_from_snapshot(profile, snapshot)
    engine = _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 _write_trace(
        db=db,
        agent_id=agent_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": _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),
    )


@router.get("/{agent_id}/traces", response_model=List[TraceResponse])
async def get_traces(
    agent_id: str,
    limit: int = Query(20, ge=1, le=200),
    db: AsyncSession = Depends(get_db),
):
    _profile, _snapshot = await _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
    ]

