"""
Evolution orchestrator module

This module implements the self-evolution loop orchestrator that coordinates
the complete optimization cycle: data collection -> strategy execution ->
performance analysis -> decision making -> code modification -> validation.
"""

import logging
import time
from typing import Dict, Any, Optional

from ..models.evolution import (
    EvolutionConfig,
    EvolutionState,
    EvolutionResult,
    IterationResult,
    FinalReport,
    OptimizationGoal,
    ModificationResult,
)
from ..models.performance import PeriodMetrics, PerformanceReport
from ..models.decision import ImprovementPlan
from ..tools.manager import ToolManager
from ..risk.controller import RiskController
from ..risk.notification import NotificationService
from ..validation.validator import CodeValidator
from ..validation.sandbox import DockerSandbox
from ..modification.quality_checker import CodeQualityChecker
from ..modification.git_manager import GitManager

logger = logging.getLogger(__name__)


class EvolutionOrchestrator:
    """
    Self-evolution orchestrator that manages the complete optimization loop

    The orchestrator coordinates all components to implement a continuous
    improvement cycle for trading strategies.

    Attributes:
        config: Evolution configuration
        tools: Tool manager for accessing all agent tools
        risk: Risk controller for evaluating modifications
        notifier: Notification service for alerts
        state: Current evolution state
        iteration_history: History of all iterations
    """

    def __init__(
        self,
        config: EvolutionConfig,
        tool_manager: ToolManager,
        risk_controller: RiskController,
        notification_service: NotificationService,
    ):
        """
        Initialize evolution orchestrator

        Args:
            config: Evolution configuration
            tool_manager: Tool manager instance
            risk_controller: Risk controller instance
            notification_service: Notification service instance
        """
        self.config = config
        self.tools = tool_manager
        self.risk = risk_controller
        self.notifier = notification_service
        self.state = EvolutionState()
        self.iteration_history = []

    def start_evolution(
        self,
        initial_strategy: Dict[str, Any],
        optimization_goal: OptimizationGoal,
    ) -> EvolutionResult:
        """
        Start the self-evolution process

        This method initiates the evolution loop and runs iterations until
        the optimization goal is achieved or maximum iterations are reached.

        Args:
            initial_strategy: Initial strategy code and configuration
            optimization_goal: Optimization goal specification

        Returns:
            EvolutionResult containing final strategy and metrics

        Raises:
            Exception: If evolution fails critically
        """
        logger.info("Starting evolution process")
        self.state.status = "running"
        self.state.current_strategy = initial_strategy
        self.state.optimization_goal = optimization_goal
        self.state.start_time = int(time.time())

        try:
            while self._should_continue():
                iteration_result = self._run_iteration()
                self.iteration_history.append(iteration_result)

                if iteration_result.status == "error":
                    self._handle_error(iteration_result)
                    break

                if self._goal_achieved():
                    self.state.status = "completed"
                    logger.info("Optimization goal achieved!")
                    break

            final_report = self._generate_final_report()
            return EvolutionResult(
                success=True,
                final_strategy=self.state.current_strategy,
                iterations=len(self.iteration_history),
                final_metrics=self.state.current_metrics,
                report=final_report,
            )

        except Exception as e:
            logger.error(f"Evolution failed: {str(e)}", exc_info=True)
            self.state.status = "failed"
            self.notifier.send_risk_warning(
                risk_assessment={
                    "risk_level": "critical",
                    "risk_factors": [],
                    "requires_review": True,
                    "can_proceed": False,
                    "recommendations": [],
                    "timestamp": int(time.time()),
                },
                context={"error": str(e)},
            )
            raise

    def _run_iteration(self) -> IterationResult:
        """
        Run a single iteration of the evolution loop

        This method executes all six steps of the evolution cycle:
        1. Data collection
        2. Strategy execution
        3. Performance analysis
        4. Decision making (with risk assessment)
        5. Code modification
        6. Validation

        Returns:
            IterationResult containing iteration outcome
        """
        iteration_num = len(self.iteration_history) + 1
        logger.info(f"Starting iteration {iteration_num}")

        try:
            # Step 1: Data collection
            logger.info(f"Iteration {iteration_num}: Collecting market data")
            market_data = self._collect_data()

            # Step 2: Strategy execution
            logger.info(f"Iteration {iteration_num}: Executing strategy")
            execution_result = self._execute_strategy(market_data)

            # Step 3: Performance analysis
            logger.info(f"Iteration {iteration_num}: Analyzing performance")
            performance_report = self._analyze_performance(execution_result)

            # Step 4: Decision making (with risk assessment)
            logger.info(f"Iteration {iteration_num}: Making decision")
            improvement_plan = self._make_decision(performance_report)

            if improvement_plan is None:
                logger.info(f"Iteration {iteration_num}: No improvement needed")
                return IterationResult(
                    iteration_num=iteration_num,
                    status="no_improvement_needed",
                    metrics=performance_report.metrics,
                )

            # Step 5: Code modification
            logger.info(f"Iteration {iteration_num}: Modifying code")
            modification_result = self._modify_code(improvement_plan)

            # Step 6: Validation
            logger.info(f"Iteration {iteration_num}: Validating modifications")
            validation_result = self._validate_modifications(
                modification_result, performance_report.metrics
            )

            if not validation_result.overall_passed:
                logger.warning(f"Iteration {iteration_num}: Validation failed, rolling back")
                self._rollback(modification_result.git_commit)
                return IterationResult(
                    iteration_num=iteration_num,
                    status="validation_failed",
                    metrics=performance_report.metrics,
                    error=f"Validation failed: {validation_result}",
                )

            # Deploy new version
            logger.info(f"Iteration {iteration_num}: Deploying new version")
            self._deploy_new_version(modification_result)

            # Update state with new metrics
            self.state.current_metrics = validation_result.backtest_results.metrics

            logger.info(f"Iteration {iteration_num}: Completed successfully")
            return IterationResult(
                iteration_num=iteration_num,
                status="success",
                metrics=validation_result.backtest_results.metrics,
                improvement_plan=improvement_plan,
                modification_result=modification_result,
            )

        except Exception as e:
            logger.error(f"Iteration {iteration_num} failed: {str(e)}", exc_info=True)
            return IterationResult(iteration_num=iteration_num, status="error", error=str(e))

    def _should_continue(self) -> bool:
        """
        Determine whether to continue iterating

        Checks if evolution should continue based on:
        - Current status
        - Maximum iterations limit
        - Other stopping conditions

        Returns:
            True if evolution should continue, False otherwise
        """
        if self.state.status != "running":
            logger.info(f"Evolution stopped: status is {self.state.status}")
            return False

        if len(self.iteration_history) >= self.config.max_iterations:
            self.state.status = "max_iterations_reached"
            logger.info(f"Maximum iterations ({self.config.max_iterations}) reached")
            return False

        return True

    def _goal_achieved(self) -> bool:
        """
        Check if optimization goal has been achieved

        Compares current metrics against the target metric and value
        specified in the optimization goal.

        Returns:
            True if goal is achieved, False otherwise
        """
        if not self.state.current_metrics:
            return False

        goal = self.state.optimization_goal
        metrics = self.state.current_metrics

        if goal.target_metric == "sharpe_ratio":
            achieved = metrics.sharpe_ratio >= goal.target_value
        elif goal.target_metric == "win_rate":
            achieved = metrics.win_rate >= goal.target_value
        elif goal.target_metric == "profit_factor":
            achieved = metrics.profit_factor >= goal.target_value
        elif goal.target_metric == "max_drawdown":
            # For drawdown, lower is better
            achieved = metrics.max_drawdown <= goal.target_value
        else:
            logger.warning(f"Unknown target metric: {goal.target_metric}")
            return False

        if achieved:
            logger.info(
                f"Goal achieved: {goal.target_metric} = "
                f"{getattr(metrics, goal.target_metric)} "
                f"(target: {goal.target_value})"
            )

        return achieved

    def _handle_error(self, iteration_result: IterationResult) -> None:
        """
        Handle iteration errors

        Sends notifications and updates state when an iteration fails.

        Args:
            iteration_result: Failed iteration result
        """
        self.state.status = "error"
        logger.error(
            f"Iteration {iteration_result.iteration_num} encountered error: "
            f"{iteration_result.error}"
        )

        self.notifier.send_risk_warning(
            risk_assessment={
                "risk_level": "critical",
                "risk_factors": [],
                "requires_review": True,
                "can_proceed": False,
                "recommendations": [],
                "timestamp": int(time.time()),
            },
            context={
                "iteration": iteration_result.iteration_num,
                "error": iteration_result.error,
            },
        )

    def _generate_final_report(self) -> FinalReport:
        """
        Generate final evolution report

        Creates a comprehensive report summarizing all iterations,
        metrics improvements, and goal achievement status.

        Returns:
            FinalReport with complete evolution summary
        """
        logger.info("Generating final evolution report")

        successful_iterations = [i for i in self.iteration_history if i.status == "success"]

        initial_metrics = None
        if self.iteration_history and self.iteration_history[0].metrics:
            initial_metrics = self.iteration_history[0].metrics

        return FinalReport(
            start_time=self.state.start_time,
            end_time=int(time.time()),
            total_iterations=len(self.iteration_history),
            successful_iterations=len(successful_iterations),
            initial_metrics=initial_metrics,
            final_metrics=self.state.current_metrics,
            optimization_goal=self.state.optimization_goal,
            goal_achieved=self._goal_achieved(),
            iteration_summary=[
                {
                    "iteration": i.iteration_num,
                    "status": i.status,
                    "metrics": (
                        {
                            "sharpe_ratio": i.metrics.sharpe_ratio,
                            "win_rate": i.metrics.win_rate,
                            "profit_factor": i.metrics.profit_factor,
                            "max_drawdown": i.metrics.max_drawdown,
                        }
                        if i.metrics
                        else None
                    ),
                }
                for i in self.iteration_history
            ],
        )

    def _collect_data(self) -> Dict[str, Any]:
        """
        Step 1: Collect market data

        Fetches K-line data and SMC indicators using the data collection tool.

        Returns:
            Dictionary containing kline data and indicators
        """
        data_tool = self.tools.get_tool("data_collection")

        # Fetch K-line data
        kline_data = data_tool.execute(
            {
                "action": "fetch_kline",
                "symbol": self.config.symbol,
                "timeframe": self.config.timeframe,
                "limit": 1000,
            }
        )

        # Fetch SMC indicators
        indicators = data_tool.execute(
            {
                "action": "fetch_indicators",
                "symbol": self.config.symbol,
                "timeframe": self.config.timeframe,
                "indicator_types": ["struct", "fvg", "orderblock", "pdarray"],
            }
        )

        return {"kline": kline_data, "indicators": indicators}

    def _execute_strategy(self, market_data: Dict[str, Any]) -> Any:
        """
        Step 2: Execute trading strategy

        Runs the current strategy using the strategy execution tool.

        Args:
            market_data: Market data from data collection step

        Returns:
            Strategy execution result
        """
        strategy_tool = self.tools.get_tool("strategy_execution")

        return strategy_tool.execute(
            {
                "action": "execute_strategy",
                "strategy_name": self.state.current_strategy["name"],
                "symbol": self.config.symbol,
                "parameters": self.state.current_strategy.get("parameters", {}),
            }
        )

    def _analyze_performance(self, execution_result: Any) -> PerformanceReport:
        """
        Step 3: Analyze performance

        Analyzes trading results and generates performance report.

        Args:
            execution_result: Result from strategy execution

        Returns:
            PerformanceReport with metrics and analysis
        """
        feedback_tool = self.tools.get_tool("performance_feedback")

        return feedback_tool.execute({"action": "generate_report", "period": "daily"})

    def _make_decision(self, performance_report: PerformanceReport) -> Optional[ImprovementPlan]:
        """
        Step 4: Make decision with risk assessment

        Analyzes performance, identifies issues, generates improvement plan,
        and performs risk assessment.

        Args:
            performance_report: Performance analysis report

        Returns:
            ImprovementPlan if improvements are needed, None otherwise
        """
        decision_tool = self.tools.get_tool("decision_making")

        # Analyze performance
        analysis = decision_tool.execute(
            {
                "action": "analyze_performance",
                "performance_report": performance_report,
                "strategy_config": self.state.current_strategy,
            }
        )

        # If performance is good, no improvement needed
        if analysis.overall_rating in ["excellent", "good"]:
            logger.info("Strategy performance is good, no improvement needed")
            return None

        # Identify issues
        issues = decision_tool.execute({"action": "identify_issues", "analysis": analysis})

        # Generate improvement plan
        improvement_plan = decision_tool.execute(
            {
                "action": "generate_improvement_plan",
                "issues": issues,
                "current_strategy": self.state.current_strategy,
            }
        )

        # Risk assessment
        risk_assessment = self.risk.evaluate_modification_risk(
            plan=improvement_plan, current_strategy=self.state.current_strategy
        )

        if not risk_assessment.can_proceed:
            logger.warning(f"Risk assessment blocked modification: {risk_assessment.reason}")
            self.notifier.send_risk_warning(risk_assessment, {"improvement_plan": improvement_plan})

            if risk_assessment.requires_review:
                # Request human review
                review_request = decision_tool.execute(
                    {
                        "action": "request_human_review",
                        "plan": improvement_plan,
                        "reason": risk_assessment.reason,
                    }
                )
                self.notifier.send_review_request(review_request)
                # Pause iteration, wait for review
                self.state.status = "pending_review"
                return None

        return improvement_plan

    def _modify_code(self, improvement_plan: ImprovementPlan) -> ModificationResult:
        """
        Step 5: Modify code

        Applies code modifications according to the improvement plan.

        Args:
            improvement_plan: Plan specifying code modifications

        Returns:
            ModificationResult with details of changes made
        """
        code_tool = self.tools.get_tool("code_modification")

        return code_tool.execute(
            {"action": "apply_modifications", "improvement_plan": improvement_plan}
        )

    def _validate_modifications(
        self, modification_result: ModificationResult, current_metrics: PeriodMetrics
    ) -> Any:
        """
        Step 6: Validate modifications

        Validates code changes through testing, formatting checks, and backtesting.

        Args:
            modification_result: Result of code modifications
            current_metrics: Current performance metrics for comparison

        Returns:
            ValidationReport with validation results
        """
        validator = CodeValidator(
            sandbox=DockerSandbox(),
            quality_checker=CodeQualityChecker(),
            git_manager=GitManager(self.config.workspace_path),
        )

        return validator.validate_modifications(
            modified_files=modification_result.modified_files,
            strategy_config=self.state.current_strategy,
        )

    def _deploy_new_version(self, modification_result: ModificationResult) -> None:
        """
        Deploy new strategy version

        Updates the current strategy with the new version information
        after successful validation.

        Args:
            modification_result: Result of code modifications
        """
        self.state.current_strategy["version"] = modification_result.git_commit
        self.state.current_strategy["last_updated"] = int(time.time())
        logger.info(f"Deployed new version: {modification_result.git_commit}")

        # Send deployment notification
        self.notifier.send_iteration_complete(
            {
                "version": modification_result.git_commit,
                "modified_files": modification_result.modified_files,
                "timestamp": int(time.time()),
            }
        )

    def _rollback(self, commit_sha: Optional[str]) -> None:
        """
        Rollback code to previous version

        Reverts code changes when validation fails or errors occur.

        Args:
            commit_sha: Git commit SHA to rollback from
        """
        if not commit_sha:
            logger.warning("No commit SHA provided for rollback")
            return

        git_manager = GitManager(self.config.workspace_path)
        rollback_tag = f"pre-{commit_sha}"

        try:
            success = git_manager.rollback_to_tag(rollback_tag)
            if success:
                logger.info(f"Successfully rolled back to {rollback_tag}")
                self.notifier.send_rollback_notification(
                    reason="Validation failed",
                    from_version=commit_sha,
                    to_version=rollback_tag,
                )
            else:
                logger.error(f"Failed to rollback to {rollback_tag}")
        except Exception as e:
            logger.error(f"Rollback failed: {str(e)}", exc_info=True)
            self.notifier.send_risk_warning(
                risk_assessment={
                    "risk_level": "critical",
                    "risk_factors": [],
                    "requires_review": True,
                    "can_proceed": False,
                    "recommendations": ["Manual intervention required"],
                    "timestamp": int(time.time()),
                },
                context={"error": str(e), "commit": commit_sha},
            )
