"""
PM-010: Test Validation Integration
Validate success via test execution for Genesis.

Acceptance Criteria:
- [x] GIVEN attempt completes WHEN tests exist THEN run suite
- [x] AND if pass THEN mark success
- [x] AND if fail THEN extract failure to learnings

Dependencies: PM-003, PM-009
"""

import os
import json
import logging
import tempfile
import subprocess
from datetime import datetime
from typing import Optional, Dict, Any, List, Callable
from dataclasses import dataclass, field
from pathlib import Path

from core.test_runner_v2 import TestRunner, TestResult
from core.learning_accumulator import get_learning_accumulator, LearningAccumulator

logger = logging.getLogger(__name__)


@dataclass
class ValidationResult:
    """Result of validation."""
    valid: bool
    validation_type: str  # "test", "syntax", "output_check", "custom"
    message: str = ""

    # Test-specific
    test_result: Optional[TestResult] = None
    passed_tests: int = 0
    failed_tests: int = 0
    error_tests: int = 0

    # Timing
    duration_ms: int = 0
    timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())

    # Failures
    failure_messages: List[str] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        result = {
            "valid": self.valid,
            "validation_type": self.validation_type,
            "message": self.message,
            "passed_tests": self.passed_tests,
            "failed_tests": self.failed_tests,
            "error_tests": self.error_tests,
            "duration_ms": self.duration_ms,
            "timestamp": self.timestamp,
            "failure_messages": self.failure_messages
        }
        if self.test_result:
            result["test_result"] = self.test_result.to_dict()
        return result


class ValidationEngine:
    """
    Validate task outputs via test execution.

    Features:
    - Run test suites to validate code output
    - Syntax validation for generated code
    - Custom validation callbacks
    - Extract failures to learnings for future attempts
    """

    def __init__(self,
                 test_runner: Optional[TestRunner] = None,
                 learning_accumulator: Optional[LearningAccumulator] = None,
                 working_dir: Optional[str] = None):
        """
        Initialize ValidationEngine.

        Args:
            test_runner: TestRunner instance
            learning_accumulator: LearningAccumulator instance
            working_dir: Working directory for test execution
        """
        self.test_runner = test_runner or TestRunner()
        self.learning_accumulator = learning_accumulator or get_learning_accumulator()
        self.working_dir = working_dir or os.getcwd()

    def validate_with_tests(self,
                           task_id: str,
                           test_command: str,
                           attempt: int = 1,
                           tier: int = 1,
                           model: str = "",
                           working_dir: Optional[str] = None,
                           timeout: int = 300) -> ValidationResult:
        """
        Validate by running a test command.

        Args:
            task_id: Task identifier
            test_command: Test command to run
            attempt: Current attempt number
            tier: Current tier
            model: Model that generated the output
            working_dir: Directory to run tests in
            timeout: Test timeout in seconds

        Returns:
            ValidationResult with test outcome
        """
        logger.info(f"Running test validation for {task_id}: {test_command}")

        working_dir = working_dir or self.working_dir

        # Run tests
        test_result = self.test_runner.run(
            command=test_command,
            timeout=timeout,
            working_dir=working_dir
        )

        # Build validation result
        validation = ValidationResult(
            valid=test_result.success and test_result.failed == 0 and test_result.errors == 0,
            validation_type="test",
            test_result=test_result,
            passed_tests=test_result.passed,
            failed_tests=test_result.failed,
            error_tests=test_result.errors,
            duration_ms=int(test_result.duration_seconds * 1000)
        )

        if validation.valid:
            validation.message = f"All {test_result.passed} tests passed"
            logger.info(f"Test validation passed for {task_id}: {validation.message}")
        else:
            # Extract failure messages
            validation.failure_messages = self.test_runner.extract_failure_messages(test_result)
            validation.message = (
                f"Tests failed: {test_result.passed} passed, "
                f"{test_result.failed} failed, {test_result.errors} errors"
            )
            logger.warning(f"Test validation failed for {task_id}: {validation.message}")

            # Capture failure as learning
            error_summary = "; ".join(validation.failure_messages[:3]) if validation.failure_messages else "Tests failed"
            self.learning_accumulator.capture_failure(
                task_id=task_id,
                attempt_number=attempt,
                tier=tier,
                error_type="TestFailure",
                error_message=error_summary[:500],
                model=model,
                error_context=test_result.stderr[:200] if test_result.stderr else None,
                fix_approach="Review test output and fix failing assertions"
            )

        return validation

    def validate_python_syntax(self,
                              task_id: str,
                              code: str,
                              attempt: int = 1,
                              tier: int = 1,
                              model: str = "") -> ValidationResult:
        """
        Validate Python code syntax.

        Args:
            task_id: Task identifier
            code: Python code to validate
            attempt: Current attempt number
            tier: Current tier
            model: Model that generated the code

        Returns:
            ValidationResult with syntax check outcome
        """
        logger.debug(f"Validating Python syntax for {task_id}")

        try:
            # Try to compile the code
            compile(code, "<string>", "exec")

            return ValidationResult(
                valid=True,
                validation_type="syntax",
                message="Python syntax is valid"
            )

        except SyntaxError as e:
            error_msg = f"SyntaxError at line {e.lineno}: {e.msg}"

            # Capture as learning
            self.learning_accumulator.capture_failure(
                task_id=task_id,
                attempt_number=attempt,
                tier=tier,
                error_type="SyntaxError",
                error_message=error_msg,
                model=model,
                error_context=e.text if e.text else None,
                fix_approach=f"Fix syntax error at line {e.lineno}"
            )

            return ValidationResult(
                valid=False,
                validation_type="syntax",
                message=error_msg,
                failure_messages=[error_msg]
            )

    def validate_file_syntax(self,
                            task_id: str,
                            file_path: str,
                            attempt: int = 1,
                            tier: int = 1,
                            model: str = "") -> ValidationResult:
        """
        Validate syntax of a Python file.

        Args:
            task_id: Task identifier
            file_path: Path to Python file
            attempt: Current attempt number
            tier: Current tier
            model: Model that generated the file

        Returns:
            ValidationResult
        """
        logger.debug(f"Validating file syntax for {task_id}: {file_path}")

        try:
            result = subprocess.run(
                ["python3", "-m", "py_compile", file_path],
                capture_output=True,
                text=True,
                timeout=30
            )

            if result.returncode == 0:
                return ValidationResult(
                    valid=True,
                    validation_type="syntax",
                    message=f"File {file_path} has valid syntax"
                )
            else:
                error_msg = result.stderr.strip() or "Syntax validation failed"

                self.learning_accumulator.capture_failure(
                    task_id=task_id,
                    attempt_number=attempt,
                    tier=tier,
                    error_type="SyntaxError",
                    error_message=error_msg[:500],
                    model=model
                )

                return ValidationResult(
                    valid=False,
                    validation_type="syntax",
                    message=error_msg,
                    failure_messages=[error_msg]
                )

        except subprocess.TimeoutExpired:
            return ValidationResult(
                valid=False,
                validation_type="syntax",
                message="Syntax validation timed out"
            )

        except Exception as e:
            return ValidationResult(
                valid=False,
                validation_type="syntax",
                message=f"Syntax validation error: {str(e)}"
            )

    def validate_output_contains(self,
                                task_id: str,
                                output: str,
                                required_strings: List[str],
                                attempt: int = 1,
                                tier: int = 1,
                                model: str = "") -> ValidationResult:
        """
        Validate that output contains required strings.

        Args:
            task_id: Task identifier
            output: Output to validate
            required_strings: Strings that must be present
            attempt: Current attempt number
            tier: Current tier
            model: Model that generated the output

        Returns:
            ValidationResult
        """
        missing = [s for s in required_strings if s not in output]

        if not missing:
            return ValidationResult(
                valid=True,
                validation_type="output_check",
                message=f"Output contains all {len(required_strings)} required strings"
            )
        else:
            error_msg = f"Missing required strings: {missing[:5]}"

            self.learning_accumulator.capture_failure(
                task_id=task_id,
                attempt_number=attempt,
                tier=tier,
                error_type="OutputValidationError",
                error_message=error_msg,
                model=model,
                fix_approach="Ensure output includes all required elements"
            )

            return ValidationResult(
                valid=False,
                validation_type="output_check",
                message=error_msg,
                failure_messages=[f"Missing: {s}" for s in missing[:10]]
            )

    def validate_with_callback(self,
                              task_id: str,
                              output: str,
                              callback: Callable[[str], bool],
                              callback_name: str = "custom",
                              attempt: int = 1,
                              tier: int = 1,
                              model: str = "") -> ValidationResult:
        """
        Validate with a custom callback function.

        Args:
            task_id: Task identifier
            output: Output to validate
            callback: Validation function (output) -> bool
            callback_name: Name for logging
            attempt: Current attempt number
            tier: Current tier
            model: Model that generated the output

        Returns:
            ValidationResult
        """
        try:
            is_valid = callback(output)

            if is_valid:
                return ValidationResult(
                    valid=True,
                    validation_type="custom",
                    message=f"Custom validation '{callback_name}' passed"
                )
            else:
                self.learning_accumulator.capture_failure(
                    task_id=task_id,
                    attempt_number=attempt,
                    tier=tier,
                    error_type="CustomValidationError",
                    error_message=f"Custom validation '{callback_name}' failed",
                    model=model
                )

                return ValidationResult(
                    valid=False,
                    validation_type="custom",
                    message=f"Custom validation '{callback_name}' failed"
                )

        except Exception as e:
            error_msg = f"Validation callback error: {str(e)}"

            return ValidationResult(
                valid=False,
                validation_type="custom",
                message=error_msg,
                failure_messages=[error_msg]
            )

    def run_full_validation(self,
                           task_id: str,
                           output: str,
                           test_command: Optional[str] = None,
                           validate_syntax: bool = False,
                           required_strings: Optional[List[str]] = None,
                           custom_callback: Optional[Callable[[str], bool]] = None,
                           attempt: int = 1,
                           tier: int = 1,
                           model: str = "") -> ValidationResult:
        """
        Run full validation suite.

        Args:
            task_id: Task identifier
            output: Output to validate
            test_command: Optional test command
            validate_syntax: Whether to validate Python syntax
            required_strings: Optional required strings
            custom_callback: Optional custom validation
            attempt: Current attempt number
            tier: Current tier
            model: Model that generated the output

        Returns:
            Combined ValidationResult
        """
        validations: List[ValidationResult] = []

        # Syntax validation
        if validate_syntax:
            syntax_result = self.validate_python_syntax(
                task_id, output, attempt, tier, model
            )
            validations.append(syntax_result)
            if not syntax_result.valid:
                return syntax_result

        # Required strings check
        if required_strings:
            strings_result = self.validate_output_contains(
                task_id, output, required_strings, attempt, tier, model
            )
            validations.append(strings_result)
            if not strings_result.valid:
                return strings_result

        # Custom callback
        if custom_callback:
            custom_result = self.validate_with_callback(
                task_id, output, custom_callback, "custom", attempt, tier, model
            )
            validations.append(custom_result)
            if not custom_result.valid:
                return custom_result

        # Test execution
        if test_command:
            test_result = self.validate_with_tests(
                task_id, test_command, attempt, tier, model
            )
            validations.append(test_result)
            if not test_result.valid:
                return test_result

        # All validations passed
        return ValidationResult(
            valid=True,
            validation_type="full",
            message=f"All {len(validations)} validations passed",
            passed_tests=sum(v.passed_tests for v in validations),
            failed_tests=sum(v.failed_tests for v in validations),
            error_tests=sum(v.error_tests for v in validations)
        )


# Singleton instance
_validation_engine: Optional[ValidationEngine] = None


def get_validation_engine() -> ValidationEngine:
    """Get or create global ValidationEngine instance."""
    global _validation_engine
    if _validation_engine is None:
        _validation_engine = ValidationEngine()
    return _validation_engine


if __name__ == "__main__":
    # Test the ValidationEngine
    logging.basicConfig(level=logging.INFO)

    engine = ValidationEngine()

    # Test syntax validation
    good_code = "def hello():\n    return 'world'"
    bad_code = "def hello(\n    return 'world'"

    print("Good code validation:")
    result = engine.validate_python_syntax("test-001", good_code)
    print(json.dumps(result.to_dict(), indent=2))

    print("\nBad code validation:")
    result = engine.validate_python_syntax("test-002", bad_code)
    print(json.dumps(result.to_dict(), indent=2))

    # Test output contains
    print("\nOutput contains validation:")
    result = engine.validate_output_contains(
        "test-003",
        "Hello World! This is a test.",
        ["Hello", "World", "test"]
    )
    print(json.dumps(result.to_dict(), indent=2))
