"""
Code validator for comprehensive validation of code modifications
"""

import time
from pathlib import Path
from dataclasses import dataclass, field
from typing import Dict, Any, List, Optional

from openfund_agent.validation.sandbox import (
    DockerSandbox,
    TestResult,
    CodeValidationResult,
    BacktestResult,
)
from openfund_agent.modification.quality_checker import (
    CodeQualityChecker,
    QualityReport,
)
from openfund_agent.modification.git_manager import GitManager
from openfund_agent.models.performance import PeriodMetrics


@dataclass
class FormatResult:
    """
    Code format check result

    Attributes:
        compliant: Whether code is properly formatted
        files_need_formatting: List of files that need formatting
        diff: Diff output showing formatting changes (optional)
    """

    compliant: bool
    files_need_formatting: List[str] = field(default_factory=list)
    diff: Optional[str] = None


@dataclass
class ValidationReport:
    """
    Comprehensive validation report

    Attributes:
        overall_passed: Whether all validations passed
        code_quality: Code quality validation results
        test_results: Test execution results
        format_check: Format check results
        backtest_results: Backtest validation results
        timestamp: Validation timestamp
    """

    overall_passed: bool
    code_quality: Optional[CodeValidationResult] = None
    test_results: Optional[TestResult] = None
    format_check: Optional[FormatResult] = None
    backtest_results: Optional[BacktestResult] = None
    timestamp: int = field(default_factory=lambda: int(time.time()))


class CodeValidator:
    """
    Comprehensive code validator

    This class coordinates all validation processes including code quality checks,
    test execution, format validation, and backtest verification.
    """

    def __init__(
        self,
        sandbox: DockerSandbox,
        quality_checker: CodeQualityChecker,
        git_manager: GitManager,
    ):
        """
        Initialize code validator

        Args:
            sandbox: Docker sandbox for isolated testing
            quality_checker: Code quality checker
            git_manager: Git version manager
        """
        self.sandbox = sandbox
        self.quality_checker = quality_checker
        self.git = git_manager

    def validate_modifications(
        self,
        modified_files: List[str],
        strategy_config: Dict[str, Any],
        workspace_path: Optional[str] = None,
        run_tests: bool = True,
        run_backtest: bool = True,
    ) -> ValidationReport:
        """
        Validate code modifications comprehensively

        Args:
            modified_files: List of modified file paths
            strategy_config: Strategy configuration for backtest
            workspace_path: Path to workspace (defaults to git repo path)
            run_tests: Whether to run test suite
            run_backtest: Whether to run backtest validation

        Returns:
            Comprehensive validation report
        """
        if workspace_path is None:
            workspace_path = str(self.git.repo_path)

        report = ValidationReport(overall_passed=True)

        # Step 1: Code quality validation
        code_quality = self._validate_code_quality(modified_files)
        report.code_quality = code_quality

        if not code_quality.syntax_valid:
            report.overall_passed = False
            return report

        # Step 2: Format check
        format_result = self.run_black_formatter(modified_files, check_only=True)
        report.format_check = format_result

        # Step 3: Run tests in sandbox
        if run_tests:
            test_results = self._run_tests_in_sandbox(workspace_path)
            report.test_results = test_results

            if not test_results.passed:
                report.overall_passed = False

        # Step 4: Run backtest validation
        if run_backtest:
            backtest_results = self._run_backtest_in_sandbox(workspace_path, strategy_config)
            report.backtest_results = backtest_results

            if not backtest_results.success:
                report.overall_passed = False

        return report

    def run_pytest_suite(
        self, test_path: str = "tests/", workspace_path: Optional[str] = None
    ) -> TestResult:
        """
        Run pytest test suite

        Args:
            test_path: Path to tests directory
            workspace_path: Path to workspace (defaults to git repo path)

        Returns:
            Test execution result
        """
        if workspace_path is None:
            workspace_path = str(self.git.repo_path)

        return self._run_tests_in_sandbox(workspace_path, test_path)

    def run_black_formatter(self, files: List[str], check_only: bool = True) -> FormatResult:
        """
        Run black formatter check

        Args:
            files: List of file paths to check
            check_only: If True, only check without modifying files

        Returns:
            Format check result
        """
        files_need_formatting = []
        diff_output = []

        for file_path in files:
            # Skip non-Python files
            if not file_path.endswith(".py"):
                continue

            full_path = self.git.repo_path / file_path
            if not full_path.exists():
                continue

            # Run black check
            import subprocess

            try:
                cmd = ["black", "--check", str(full_path)]
                if not check_only:
                    cmd.remove("--check")

                result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)

                if result.returncode != 0:
                    files_need_formatting.append(file_path)
                    if result.stdout:
                        diff_output.append(f"=== {file_path} ===\n{result.stdout}")

            except subprocess.TimeoutExpired:
                files_need_formatting.append(file_path)
                diff_output.append(f"=== {file_path} ===\nTimeout during formatting")
            except Exception as e:
                files_need_formatting.append(file_path)
                diff_output.append(f"=== {file_path} ===\nError: {str(e)}")

        return FormatResult(
            compliant=len(files_need_formatting) == 0,
            files_need_formatting=files_need_formatting,
            diff="\n".join(diff_output) if diff_output else None,
        )

    def run_backtest_validation(
        self,
        strategy_config: Dict[str, Any],
        data_range: Optional[tuple] = None,
        workspace_path: Optional[str] = None,
    ) -> BacktestResult:
        """
        Run backtest validation

        Args:
            strategy_config: Strategy configuration
            data_range: Optional tuple of (start_time, end_time) for data range
            workspace_path: Path to workspace (defaults to git repo path)

        Returns:
            Backtest execution result
        """
        if workspace_path is None:
            workspace_path = str(self.git.repo_path)

        # Add data range to config if provided
        if data_range:
            strategy_config = strategy_config.copy()
            strategy_config["backtest_start"] = data_range[0]
            strategy_config["backtest_end"] = data_range[1]

        return self._run_backtest_in_sandbox(workspace_path, strategy_config)

    def rollback_on_failure(self, tag_name: str) -> bool:
        """
        Rollback code to a previous version on validation failure

        Args:
            tag_name: Tag name to rollback to

        Returns:
            True if rollback successful, False otherwise
        """
        return self.git.rollback_to_tag(tag_name)

    def _validate_code_quality(self, files: List[str]) -> CodeValidationResult:
        """
        Validate code quality for modified files

        Args:
            files: List of file paths to validate

        Returns:
            Code validation result
        """
        result = CodeValidationResult(syntax_valid=True, pep8_compliant=True, black_formatted=True)

        for file_path in files:
            # Skip non-Python files
            if not file_path.endswith(".py"):
                continue

            full_path = self.git.repo_path / file_path
            if not full_path.exists():
                result.warnings.append(f"File not found: {file_path}")
                continue

            # Run quality checks
            quality_report = self.quality_checker.check_file(str(full_path))

            if not quality_report.syntax_valid:
                result.syntax_valid = False
                result.errors.append(f"Syntax errors in {file_path}")

            if not quality_report.pep8_compliant:
                result.pep8_compliant = False
                result.warnings.append(f"PEP8 violations in {file_path}")

            # Collect specific issues
            for issue in quality_report.issues:
                if issue.severity == "error":
                    result.errors.append(f"{file_path}:{issue.line_number} - {issue.message}")
                elif issue.severity == "warning":
                    result.warnings.append(f"{file_path}:{issue.line_number} - {issue.message}")
                else:
                    result.suggestions.append(f"{file_path}:{issue.line_number} - {issue.message}")

        return result

    def _run_tests_in_sandbox(self, workspace_path: str, test_path: str = "tests/") -> TestResult:
        """
        Run tests in Docker sandbox

        Args:
            workspace_path: Path to workspace
            test_path: Path to tests directory

        Returns:
            Test execution result
        """
        container = None
        try:
            # Create sandbox container
            container = self.sandbox.create_container(
                workspace_path=workspace_path,
                env_vars={"PYTHONPATH": "/workspace"},
            )

            # Run tests
            test_command = f"pytest {test_path} -v"
            result = self.sandbox.run_tests(container, test_command)

            return result

        except Exception as e:
            return TestResult(
                passed=False,
                total_tests=0,
                passed_tests=0,
                failed_tests=1,
                skipped_tests=0,
                failures=[],
                execution_time=0.0,
            )

        finally:
            if container:
                self.sandbox.cleanup(container)

    def _run_backtest_in_sandbox(
        self, workspace_path: str, strategy_config: Dict[str, Any]
    ) -> BacktestResult:
        """
        Run backtest in Docker sandbox

        Args:
            workspace_path: Path to workspace
            strategy_config: Strategy configuration

        Returns:
            Backtest execution result
        """
        container = None
        try:
            # Create sandbox container
            container = self.sandbox.create_container(
                workspace_path=workspace_path,
                env_vars={"PYTHONPATH": "/workspace"},
            )

            # Run backtest
            result = self.sandbox.run_backtest(container, strategy_config, historical_data=None)

            return result

        except Exception as e:
            return BacktestResult(
                success=False, execution_time=0.0, errors=[f"Backtest error: {str(e)}"]
            )

        finally:
            if container:
                self.sandbox.cleanup(container)

    def compare_backtest_performance(
        self, current_metrics: PeriodMetrics, new_metrics: PeriodMetrics
    ) -> Dict[str, Any]:
        """
        Compare backtest performance between versions

        Args:
            current_metrics: Current version metrics
            new_metrics: New version metrics

        Returns:
            Comparison results dictionary
        """
        comparison = {
            "improved": False,
            "metrics_comparison": {},
            "significant_decline": False,
            "decline_percentage": 0.0,
        }

        # Compare key metrics
        metrics_to_compare = [
            "sharpe_ratio",
            "win_rate",
            "profit_factor",
            "max_drawdown",
        ]

        for metric in metrics_to_compare:
            current_value = getattr(current_metrics, metric, 0.0)
            new_value = getattr(new_metrics, metric, 0.0)
            comparison["metrics_comparison"][metric] = (current_value, new_value)

        # Determine if improved (based on Sharpe ratio as primary metric)
        if new_metrics.sharpe_ratio > current_metrics.sharpe_ratio:
            comparison["improved"] = True

        # Check for significant decline
        if current_metrics.sharpe_ratio > 0:
            decline = (
                current_metrics.sharpe_ratio - new_metrics.sharpe_ratio
            ) / current_metrics.sharpe_ratio
            comparison["decline_percentage"] = decline

            if decline > 0.15:  # 15% decline threshold
                comparison["significant_decline"] = True

        return comparison
