"""Workflow nodes for LangGraph trading workflow.

This module provides the node functions used in the LangGraph state graph.
Each node function takes the current state and returns updates to the state.
"""

import asyncio
from typing import Any, Callable, Coroutine, Dict, List, Optional

from loguru import logger

from openclaw.agents.bear_researcher import BearReport, BearResearcher
from openclaw.agents.bull_researcher import BullReport, BullResearcher
from openclaw.agents.fundamental_analyst import (
    FundamentalAnalyst,
    FundamentalReport,
)
from openclaw.agents.market_analyst import MarketAnalyst, TechnicalReport
from openclaw.agents.risk_manager import RiskManager, RiskReport
from openclaw.agents.sentiment_analyst import SentimentAnalyst, SentimentReport
from openclaw.agents.trader import SignalType, TraderAgent
from openclaw.core.work_trade_balance import ActivityDecision, WorkTradeBalance, WorkTradeConfig
from openclaw.workflow.state import TradingWorkflowState

# Type alias for node functions
NodeFunction = Callable[[TradingWorkflowState], Dict[str, Any]]


class AgentNodeExecutor:
    """Executor for agent nodes with proper initialization and error handling."""

    def __init__(
        self,
        agent_class: type,
        config_key: str,
        analysis_method: str = "analyze",
    ):
        self.agent_class = agent_class
        self.config_key = config_key
        self.analysis_method = analysis_method
        self._agent: Optional[Any] = None

    def get_agent(self, state: TradingWorkflowState) -> Any:
        """Get or create agent instance."""
        if self._agent is None:
            config = state["config"][self.config_key]
            self._agent = self.agent_class(
                agent_id=config["agent_id"],
                initial_capital=config["initial_capital"],
                skill_level=config["skill_level"],
            )
            logger.info(f"Initialized {self.config_key}: {config['agent_id']}")
        return self._agent

    async def execute(
        self,
        state: TradingWorkflowState,
        symbol: str,
        **kwargs: Any,
    ) -> Any:
        """Execute the agent's analysis method."""
        agent = self.get_agent(state)
        method = getattr(agent, self.analysis_method)

        try:
            if asyncio.iscoroutinefunction(method):
                result = await method(symbol, **kwargs)
            else:
                result = method(symbol, **kwargs)
            return result
        except Exception as e:
            logger.error(f"Error executing {self.config_key}: {e}")
            raise


# Global executor instances
_market_executor: Optional[AgentNodeExecutor] = None
_sentiment_executor: Optional[AgentNodeExecutor] = None
_fundamental_executor: Optional[AgentNodeExecutor] = None
_bull_executor: Optional[AgentNodeExecutor] = None
_bear_executor: Optional[AgentNodeExecutor] = None
_risk_executor: Optional[AgentNodeExecutor] = None


def _get_market_executor() -> AgentNodeExecutor:
    global _market_executor
    if _market_executor is None:
        _market_executor = AgentNodeExecutor(MarketAnalyst, "market_analyst")
    return _market_executor


def _get_sentiment_executor() -> AgentNodeExecutor:
    global _sentiment_executor
    if _sentiment_executor is None:
        _sentiment_executor = AgentNodeExecutor(
            SentimentAnalyst, "sentiment_analyst", "analyze_sentiment"
        )
    return _sentiment_executor


def _get_fundamental_executor() -> AgentNodeExecutor:
    global _fundamental_executor
    if _fundamental_executor is None:
        _fundamental_executor = AgentNodeExecutor(
            FundamentalAnalyst, "fundamental_analyst", "analyze_fundamentals"
        )
    return _fundamental_executor


def _get_bull_executor() -> AgentNodeExecutor:
    global _bull_executor
    if _bull_executor is None:
        _bull_executor = AgentNodeExecutor(
            BullResearcher, "bull_researcher", "generate_bull_case"
        )
    return _bull_executor


def _get_bear_executor() -> AgentNodeExecutor:
    global _bear_executor
    if _bear_executor is None:
        _bear_executor = AgentNodeExecutor(
            BearResearcher, "bear_researcher", "generate_bear_case"
        )
    return _bear_executor


def _get_risk_executor() -> AgentNodeExecutor:
    global _risk_executor
    if _risk_executor is None:
        _risk_executor = AgentNodeExecutor(
            RiskManager, "risk_manager", "assess_risk"
        )
    return _risk_executor


async def market_analysis_node(state: TradingWorkflowState) -> Dict[str, Any]:
    """Market analysis node - performs technical analysis.

    Args:
        state: Current workflow state

    Returns:
        State updates with technical report
    """
    symbol = state["config"]["symbol"]
    logger.info(f"[MarketAnalysis] Analyzing {symbol}")

    try:
        executor = _get_market_executor()
        report = await executor.execute(state, symbol)

        if isinstance(report, TechnicalReport):
            logger.info(
                f"[MarketAnalysis] Completed: trend={report.trend}, "
                f"confidence={report.confidence:.1%}"
            )
            return {
                "technical_report": report,
                "current_step": "MARKET_ANALYSIS_COMPLETE",
                "completed_steps": state["completed_steps"] + ["market_analysis"],
            }
        else:
            # Handle dict return from analyze() method
            logger.info(f"[MarketAnalysis] Completed with dict result")
            return {
                "technical_report": executor.get_agent(state).get_last_report(),
                "current_step": "MARKET_ANALYSIS_COMPLETE",
                "completed_steps": state["completed_steps"] + ["market_analysis"],
            }

    except Exception as e:
        logger.error(f"[MarketAnalysis] Error: {e}")
        return {
            "current_step": "MARKET_ANALYSIS_ERROR",
            "errors": state["errors"] + [f"Market analysis failed: {e}"],
        }


async def sentiment_analysis_node(state: TradingWorkflowState) -> Dict[str, Any]:
    """Sentiment analysis node - performs sentiment analysis.

    Args:
        state: Current workflow state

    Returns:
        State updates with sentiment report
    """
    symbol = state["config"]["symbol"]
    logger.info(f"[SentimentAnalysis] Analyzing {symbol}")

    try:
        executor = _get_sentiment_executor()
        report = await executor.execute(state, symbol)

        if isinstance(report, SentimentReport):
            logger.info(
                f"[SentimentAnalysis] Completed: sentiment={report.overall_sentiment}, "
                f"score={report.sentiment_score:.2f}"
            )
            return {
                "sentiment_report": report,
                "current_step": "SENTIMENT_ANALYSIS_COMPLETE",
                "completed_steps": state["completed_steps"] + ["sentiment_analysis"],
            }
        else:
            logger.info(f"[SentimentAnalysis] Completed with dict result")
            return {
                "sentiment_report": executor.get_agent(state).get_analysis_history()[-1]
                if executor.get_agent(state).get_analysis_history()
                else None,
                "current_step": "SENTIMENT_ANALYSIS_COMPLETE",
                "completed_steps": state["completed_steps"] + ["sentiment_analysis"],
            }

    except Exception as e:
        logger.error(f"[SentimentAnalysis] Error: {e}")
        return {
            "current_step": "SENTIMENT_ANALYSIS_ERROR",
            "errors": state["errors"] + [f"Sentiment analysis failed: {e}"],
        }


async def fundamental_analysis_node(state: TradingWorkflowState) -> Dict[str, Any]:
    """Fundamental analysis node - performs fundamental analysis.

    Args:
        state: Current workflow state

    Returns:
        State updates with fundamental report
    """
    symbol = state["config"]["symbol"]
    logger.info(f"[FundamentalAnalysis] Analyzing {symbol}")

    try:
        executor = _get_fundamental_executor()
        report = await executor.execute(state, symbol)

        if isinstance(report, FundamentalReport):
            logger.info(
                f"[FundamentalAnalysis] Completed: score={report.overall_score:.1f}, "
                f"recommendation={report.recommendation}"
            )
            return {
                "fundamental_report": report,
                "current_step": "FUNDAMENTAL_ANALYSIS_COMPLETE",
                "completed_steps": state["completed_steps"] + ["fundamental_analysis"],
            }
        else:
            logger.info(f"[FundamentalAnalysis] Completed with dict result")
            return {
                "fundamental_report": executor.get_agent(state).get_last_report(),
                "current_step": "FUNDAMENTAL_ANALYSIS_COMPLETE",
                "completed_steps": state["completed_steps"] + ["fundamental_analysis"],
            }

    except Exception as e:
        logger.error(f"[FundamentalAnalysis] Error: {e}")
        return {
            "current_step": "FUNDAMENTAL_ANALYSIS_ERROR",
            "errors": state["errors"] + [f"Fundamental analysis failed: {e}"],
        }


async def bull_bear_debate_node(state: TradingWorkflowState) -> Dict[str, Any]:
    """Bull-bear debate node - generates bull and bear cases.

    This node takes the outputs from the three analysis nodes and
    generates bullish and bearish research reports.

    Args:
        state: Current workflow state

    Returns:
        State updates with bull and bear reports
    """
    symbol = state["config"]["symbol"]
    logger.info(f"[BullBearDebate] Generating bull and bear cases for {symbol}")

    technical_report = state.get("technical_report")
    sentiment_report = state.get("sentiment_report")
    fundamental_report = state.get("fundamental_report")

    bull_report = None
    bear_report = None
    errors = []

    # Generate bull case
    try:
        bull_executor = _get_bull_executor()
        bull_result = await bull_executor.execute(
            state,
            symbol,
            technical_report=technical_report,
            sentiment_report=sentiment_report,
            fundamental_report=fundamental_report,
        )

        if isinstance(bull_result, BullReport):
            bull_report = bull_result
        else:
            bull_report = bull_executor.get_agent(state).get_last_report()

        logger.info(
            f"[BullBearDebate] Bull case generated: "
            f"conviction={bull_report.conviction_level:.1%}"
            if bull_report
            else "[BullBearDebate] Bull case: None"
        )
    except Exception as e:
        logger.error(f"[BullBearDebate] Bull case error: {e}")
        errors.append(f"Bull case generation failed: {e}")

    # Generate bear case
    try:
        bear_executor = _get_bear_executor()
        bear_result = await bear_executor.execute(
            state,
            symbol,
            technical_report=technical_report,
            sentiment_report=sentiment_report,
            fundamental_report=fundamental_report,
        )

        if isinstance(bear_result, BearReport):
            bear_report = bear_result
        else:
            bear_report = bear_executor.get_agent(state).get_last_report()

        logger.info(
            f"[BullBearDebate] Bear case generated: "
            f"conviction={bear_report.conviction_level:.1%}"
            if bear_report
            else "[BullBearDebate] Bear case: None"
        )
    except Exception as e:
        logger.error(f"[BullBearDebate] Bear case error: {e}")
        errors.append(f"Bear case generation failed: {e}")

    return {
        "bull_report": bull_report,
        "bear_report": bear_report,
        "current_step": "BULL_BEAR_DEBATE_COMPLETE",
        "completed_steps": state["completed_steps"] + ["bull_bear_debate"],
        "errors": state["errors"] + errors,
    }


async def decision_fusion_node(state: TradingWorkflowState) -> Dict[str, Any]:
    """Decision fusion node - combines analysis and debate outputs.

    This node takes all previous outputs and generates a fused trading decision
    with confidence score and recommendation.

    Args:
        state: Current workflow state

    Returns:
        State updates with fused decision
    """
    symbol = state["config"]["symbol"]
    logger.info(f"[DecisionFusion] Fusing decisions for {symbol}")

    technical_report = state.get("technical_report")
    sentiment_report = state.get("sentiment_report")
    fundamental_report = state.get("fundamental_report")
    bull_report = state.get("bull_report")
    bear_report = state.get("bear_report")

    # Calculate fused decision
    buy_signals = 0
    sell_signals = 0
    neutral_signals = 0
    total_confidence = 0.0
    signal_count = 0

    # Technical signals
    if technical_report:
        signals = technical_report.signals
        overall = signals.get("overall", "neutral")
        if overall == "buy":
            buy_signals += 1
        elif overall == "sell":
            sell_signals += 1
        else:
            neutral_signals += 1
        total_confidence += technical_report.confidence
        signal_count += 1

    # Sentiment signals
    if sentiment_report:
        sentiment = sentiment_report.overall_sentiment
        if sentiment == "bullish":
            buy_signals += 1
        elif sentiment == "bearish":
            sell_signals += 1
        else:
            neutral_signals += 1
        total_confidence += sentiment_report.confidence
        signal_count += 1

    # Fundamental signals
    if fundamental_report:
        rec = fundamental_report.recommendation
        if rec == "undervalued":
            buy_signals += 1
        elif rec == "overvalued":
            sell_signals += 1
        else:
            neutral_signals += 1
        # Fundamental doesn't have explicit confidence, use skill level
        total_confidence += 0.6
        signal_count += 1

    # Bull/Bear conviction adjustment
    bull_conviction = bull_report.conviction_level if bull_report else 0.0
    bear_conviction = bear_report.conviction_level if bear_report else 0.0

    if bull_conviction > bear_conviction:
        buy_signals += bull_conviction * 0.5
    else:
        sell_signals += bear_conviction * 0.5

    # Calculate average confidence
    avg_confidence = total_confidence / signal_count if signal_count > 0 else 0.5

    # Determine final recommendation
    if buy_signals > sell_signals and buy_signals > neutral_signals:
        recommendation = "BUY"
        confidence = avg_confidence * (0.5 + bull_conviction * 0.5)
    elif sell_signals > buy_signals and sell_signals > neutral_signals:
        recommendation = "SELL"
        confidence = avg_confidence * (0.5 + bear_conviction * 0.5)
    else:
        recommendation = "HOLD"
        confidence = avg_confidence * 0.8  # Neutral has slightly lower confidence

    fused_decision = {
        "symbol": symbol,
        "recommendation": recommendation,
        "confidence": round(confidence, 4),
        "buy_signals": round(buy_signals, 2),
        "sell_signals": round(sell_signals, 2),
        "neutral_signals": neutral_signals,
        "bull_conviction": round(bull_conviction, 4),
        "bear_conviction": round(bear_conviction, 4),
        "technical_trend": technical_report.trend if technical_report else None,
        "sentiment": sentiment_report.overall_sentiment if sentiment_report else None,
        "fundamental_rec": fundamental_report.recommendation
        if fundamental_report
        else None,
    }

    logger.info(
        f"[DecisionFusion] Decision: {recommendation} with {confidence:.1%} confidence"
    )

    return {
        "fused_decision": fused_decision,
        "current_step": "DECISION_FUSION_COMPLETE",
        "completed_steps": state["completed_steps"] + ["decision_fusion"],
    }


async def risk_assessment_node(state: TradingWorkflowState) -> Dict[str, Any]:
    """Risk assessment node - evaluates trading risks.

    This node takes the fused decision and performs risk assessment,
    providing position sizing recommendations and approval status.

    Args:
        state: Current workflow state

    Returns:
        State updates with risk report and final decision
    """
    symbol = state["config"]["symbol"]
    fused_decision = state.get("fused_decision")

    logger.info(f"[RiskAssessment] Assessing risk for {symbol}")

    if not fused_decision:
        logger.error("[RiskAssessment] No fused decision available")
        return {
            "current_step": "RISK_ASSESSMENT_ERROR",
            "errors": state["errors"] + ["No fused decision for risk assessment"],
        }

    try:
        executor = _get_risk_executor()

        # Calculate position size based on confidence
        base_position = 1000.0  # Base position size
        confidence = fused_decision.get("confidence", 0.5)
        position_size = base_position * confidence

        # Assess risk
        risk_result = await executor.execute(
            state,
            symbol,
            position_size=position_size,
            portfolio=None,
        )

        if isinstance(risk_result, RiskReport):
            risk_report = risk_result
        else:
            risk_report = executor.get_agent(state).get_latest_risk_assessment(symbol)

        if risk_report:
            logger.info(
                f"[RiskAssessment] Risk level: {risk_report.risk_level}, "
                f"recommended_size: ${risk_report.position_size_recommendation:,.2f}"
            )

            # Determine if trade is approved
            approved = (
                risk_report.risk_level in ["low", "medium"]
                and fused_decision["recommendation"] in ["BUY", "SELL"]
                and fused_decision["confidence"] > 0.5
            )

            final_decision = {
                "symbol": symbol,
                "action": fused_decision["recommendation"] if approved else "HOLD",
                "confidence": fused_decision["confidence"],
                "position_size": risk_report.position_size_recommendation,
                "approved": approved,
                "risk_level": risk_report.risk_level,
                "var_95": risk_report.var_95,
                "warnings": risk_report.warnings,
            }

            logger.info(
                f"[RiskAssessment] Final decision: {final_decision['action']} "
                f"(approved={approved})"
            )

            return {
                "risk_report": risk_report,
                "final_decision": final_decision,
                "current_step": "RISK_ASSESSMENT_COMPLETE",
                "completed_steps": state["completed_steps"] + ["risk_assessment"],
            }
        else:
            raise ValueError("No risk report generated")

    except Exception as e:
        logger.error(f"[RiskAssessment] Error: {e}")
        return {
            "current_step": "RISK_ASSESSMENT_ERROR",
            "errors": state["errors"] + [f"Risk assessment failed: {e}"],
        }


def should_continue_after_analysis(state: TradingWorkflowState) -> str:
    """Conditional edge function to determine next step after analysis.

    Checks if all three parallel analyses completed successfully.

    Args:
        state: Current workflow state

    Returns:
        Next node name or "END" if analyses failed
    """
    completed = state.get("completed_steps", [])

    has_technical = "market_analysis" in completed
    has_sentiment = "sentiment_analysis" in completed
    has_fundamental = "fundamental_analysis" in completed

    if has_technical or has_sentiment or has_fundamental:
        # At least one analysis succeeded, continue to debate
        return "bull_bear_debate"

    # All analyses failed
    logger.error("[Workflow] All analyses failed, ending workflow")
    return "END"


def should_continue_after_risk(state: TradingWorkflowState) -> str:
    """Conditional edge function to determine if workflow should end.

    Args:
        state: Current workflow state

    Returns:
        "END" to complete the workflow
    """
    return "END"


# Trader executor for order execution node
_trader_executor: Optional[AgentNodeExecutor] = None


def _get_trader_executor() -> AgentNodeExecutor:
    """Get the trader agent executor."""
    global _trader_executor
    if _trader_executor is None:
        _trader_executor = AgentNodeExecutor(TraderAgent, "trader")
    return _trader_executor


async def execute_order_node(state: TradingWorkflowState) -> Dict[str, Any]:
    """Execute order node - TraderAgent executes final decision (real or paper).

    Uses WorkTradeBalance to decide paper vs real and applies position sizing
    from risk assessment. Completes the end-to-end flow: multi-agent -> debate
    -> fusion -> risk -> execution.
    """
    symbol = state["config"]["symbol"]
    final_decision = state.get("final_decision")

    logger.info(f"[ExecuteOrder] Processing final decision for {symbol}")

    if not final_decision:
        return {
            "execution_result": {
                "skipped": True,
                "reason": "No final decision from risk assessment",
                "symbol": symbol,
            },
            "current_step": "EXECUTION_SKIPPED",
            "completed_steps": state["completed_steps"] + ["execute_order"],
        }

    approved = final_decision.get("approved", False)
    action = final_decision.get("action", "HOLD")
    position_size = float(final_decision.get("position_size", 0.0))

    if not approved or action == "HOLD" or position_size <= 0:
        return {
            "execution_result": {
                "skipped": True,
                "reason": f"Trade not approved or no size (action={action}, approved={approved})",
                "symbol": symbol,
                "final_decision": final_decision,
            },
            "current_step": "EXECUTION_SKIPPED",
            "completed_steps": state["completed_steps"] + ["execute_order"],
        }

    try:
        executor = _get_trader_executor()
        trader: TraderAgent = executor.get_agent(state)

        # WorkTradeBalance: decide paper vs real
        work_balance = WorkTradeBalance(
            economic_tracker=trader.economic_tracker,
            config=WorkTradeConfig(),
        )
        activity = work_balance.decide_activity(
            skill_level=trader.skill_level,
            win_rate=trader.win_rate,
            random_draw=0.5,
        )
        use_paper = work_balance.should_paper_trade(
            skill_level=trader.skill_level,
            win_rate=trader.win_rate,
        ) or activity == ActivityDecision.PAPER_TRADE or activity == ActivityDecision.MINIMAL_TRADE

        # Map action to SignalType
        signal = SignalType.BUY if action == "BUY" else SignalType.SELL
        amount = min(position_size, trader.balance * 0.2)  # cap at 20% of balance

        if use_paper:
            result = trader.paper_trade(symbol, signal, amount)
            execution_result = {
                "paper": True,
                "symbol": symbol,
                "signal": signal.value,
                "amount": amount,
                "success": result.success,
                "pnl": result.pnl,
                "fee": result.fee,
                "message": result.message,
            }
        else:
            result = trader.execute_trade(symbol, signal, amount)
            execution_result = {
                "paper": False,
                "symbol": symbol,
                "signal": signal.value,
                "amount": amount,
                "success": result.success,
                "pnl": result.pnl,
                "fee": result.fee,
                "message": result.message,
            }

        logger.info(
            f"[ExecuteOrder] {'Paper' if use_paper else 'Live'} trade: "
            f"{symbol} {signal.value} ${amount:.2f} -> {result.message}"
        )

        return {
            "execution_result": execution_result,
            "current_step": "EXECUTION_COMPLETE",
            "completed_steps": state["completed_steps"] + ["execute_order"],
        }

    except Exception as e:
        logger.error(f"[ExecuteOrder] Error: {e}")
        return {
            "execution_result": {
                "skipped": False,
                "error": str(e),
                "symbol": symbol,
            },
            "current_step": "EXECUTION_ERROR",
            "completed_steps": state["completed_steps"] + ["execute_order"],
            "errors": state["errors"] + [f"Order execution failed: {e}"],
        }
