"""Autonomous Test Generation - Enhanced Edition.

Generates behavioral tests with real-time progress monitoring.

ENHANCEMENTS (Phase 1):
- Extended thinking mode for better test planning
- Prompt caching for 90% cost reduction
- Full source code (no truncation)
- Workflow-specific prompts with mocking templates
- Few-shot learning with examples

ENHANCEMENTS (Phase 2 - Multi-Turn Refinement):
- Iterative test generation with validation loop
- Automatic failure detection and fixing
- Conversation history for context preservation

ENHANCEMENTS (Phase 3 - Coverage-Guided Generation):
- Coverage analysis integration
- Iterative coverage improvement targeting uncovered lines
- Systematic path to 80%+ coverage

Copyright 2026 Smart-AI-Memory
Licensed under Apache 2.0
"""

import json
import logging
import subprocess
import sys
import warnings
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from attune.memory.short_term import RedisShortTermMemory
from attune.security.path_validation import _validate_file_path
from attune.telemetry.agent_tracking import HeartbeatCoordinator
from attune.telemetry.event_streaming import EventStreamer
from attune.telemetry.feedback_loop import FeedbackLoop

from .test_gen_coverage import TestGenCoverageMixin
from .test_gen_prompts import TestGenPromptMixin
from .test_gen_refinement import TestGenRefinementMixin

logger = logging.getLogger(__name__)


@dataclass
class ValidationResult:
    """Result of pytest validation."""

    passed: bool
    failures: str
    error_count: int
    output: str


@dataclass
class CoverageResult:
    """Result of coverage analysis."""

    coverage: float
    missing_lines: list[int]
    total_statements: int
    covered_statements: int


class AutonomousTestGenerator(
    TestGenPromptMixin,
    TestGenRefinementMixin,
    TestGenCoverageMixin,
):
    """Generate tests autonomously with progress monitoring and Anthropic best practices."""

    def __init__(
        self,
        agent_id: str,
        batch_num: int,
        modules: list[dict[str, Any]],
        enable_refinement: bool = True,
        max_refinement_iterations: int = 3,
        enable_coverage_guided: bool = False,
        target_coverage: float = 0.80,
    ):
        """Initialize generator.

        .. deprecated:: 5.3.0
            Use :class:`ParallelTestGenerationWorkflow` via
            ``attune workflow run test-gen-parallel``.
            Will be removed in v6.0.0.

        Args:
            agent_id: Unique agent identifier
            batch_num: Batch number (1-18)
            modules: List of modules to generate tests for
            enable_refinement: Enable Phase 2 multi-turn refinement (default: True)
            max_refinement_iterations: Max iterations for refinement (default: 3)
            enable_coverage_guided: Enable Phase 3 coverage-guided generation (default: False)
            target_coverage: Target coverage percentage (default: 0.80 = 80%)

        """
        warnings.warn(
            "AutonomousTestGenerator is deprecated since v5.3.0. "
            "Use ParallelTestGenerationWorkflow via 'attune workflow run test-gen-parallel' instead. "
            "Will be removed in v6.0.0.",
            DeprecationWarning,
            stacklevel=2,
        )
        self.agent_id = agent_id
        self.batch_num = batch_num
        self.modules = modules

        # Phase 2 & 3 configuration
        self.enable_refinement = enable_refinement
        self.max_refinement_iterations = max_refinement_iterations
        self.enable_coverage_guided = enable_coverage_guided
        self.target_coverage = target_coverage

        # Initialize memory backend for tracking
        try:
            self.memory = RedisShortTermMemory()
            self.coordinator = HeartbeatCoordinator(memory=self.memory, enable_streaming=True)
            self.event_streamer = EventStreamer(memory=self.memory)
            self.feedback_loop = FeedbackLoop(memory=self.memory)
        except (ConnectionError, ImportError, OSError) as e:
            logger.warning(f"Failed to initialize memory backend: {e}")
            self.coordinator = HeartbeatCoordinator()
            self.event_streamer = None
            self.feedback_loop = None

        # Validate batch_num to prevent path traversal
        validated_dir = _validate_file_path(f"tests/behavioral/generated/batch{int(batch_num)}")
        self.output_dir = validated_dir
        self.output_dir.mkdir(parents=True, exist_ok=True)

        logger.info(
            f"Generator initialized: refinement={enable_refinement}, coverage_guided={enable_coverage_guided}",
        )

    def generate_all(self) -> dict[str, Any]:
        """Generate tests for all modules with progress tracking.

        Returns:
            Summary of generation results

        """
        # Start tracking
        self.coordinator.start_heartbeat(
            agent_id=self.agent_id,
            metadata={
                "batch": self.batch_num,
                "total_modules": len(self.modules),
                "workflow": "autonomous_test_generation",
            },
        )

        try:
            results = {
                "batch": self.batch_num,
                "total_modules": len(self.modules),
                "completed": 0,
                "failed": 0,
                "tests_generated": 0,
                "files_created": [],
            }

            for i, module in enumerate(self.modules):
                progress = (i + 1) / len(self.modules)
                module_name = module["file"].replace("src/attune/", "")

                # Update progress
                self.coordinator.beat(
                    status="running",
                    progress=progress,
                    current_task=f"Generating tests for {module_name}",
                )

                try:
                    # Generate tests for this module
                    test_file = self._generate_module_tests(module)
                    if test_file:
                        results["completed"] += 1
                        results["files_created"].append(str(test_file))
                        logger.info(f"✅ Generated tests for {module_name}")

                        # Publish progress event
                        if self.event_streamer:
                            self.event_streamer.publish_event(
                                event_type="test_file_created",
                                data={
                                    "agent_id": self.agent_id,
                                    "module": module_name,
                                    "test_file": str(test_file),
                                    "batch": self.batch_num,
                                },
                            )

                        # Record quality feedback
                        if self.feedback_loop:
                            self.feedback_loop.record_feedback(
                                workflow_name="test-generation",
                                stage_name="generation",
                                tier="capable",
                                quality_score=1.0,  # Success
                                metadata={
                                    "module": module_name,
                                    "status": "success",
                                    "batch": self.batch_num,
                                },
                            )
                    else:
                        results["failed"] += 1
                        logger.warning(f"⚠️ Skipped {module_name} (validation failed)")

                        # Record failure feedback
                        if self.feedback_loop:
                            self.feedback_loop.record_feedback(
                                workflow_name="test-generation",
                                stage_name="validation",
                                tier="capable",
                                quality_score=0.0,  # Failure
                                metadata={
                                    "module": module_name,
                                    "status": "validation_failed",
                                    "batch": self.batch_num,
                                },
                            )

                except Exception as e:  # noqa: BLE001
                    results["failed"] += 1
                    logger.error(f"❌ Error generating tests for {module_name}: {e}")

                    # Send error event
                    if self.event_streamer:
                        self.event_streamer.publish_event(
                            event_type="test_generation_error",
                            data={
                                "agent_id": self.agent_id,
                                "module": module_name,
                                "error": str(e),
                                "batch": self.batch_num,
                            },
                        )

            # Count total tests
            results["tests_generated"] = self._count_tests()

            # Final update
            self.coordinator.beat(
                status="completed",
                progress=1.0,
                current_task=f"Completed: {results['completed']}/{results['total_modules']} modules",
            )

            return results

        except Exception as e:  # noqa: BLE001
            # Error tracking
            self.coordinator.beat(status="failed", progress=0.0, current_task=f"Failed: {e!s}")
            raise

        finally:
            # Stop heartbeat
            self.coordinator.stop_heartbeat(
                final_status="completed" if results["completed"] > 0 else "failed",
            )

    def _generate_module_tests(self, module: dict[str, Any]) -> Path | None:
        """Generate tests for a single module using LLM agent.

        Args:
            module: Module info dict with 'file', 'total', 'missing', etc.

        Returns:
            Path to generated test file, or None if skipped

        """
        source_file = Path(module["file"])
        module_name = source_file.stem

        # Skip if module doesn't exist
        if not source_file.exists():
            logger.warning(f"Source file not found: {source_file}")
            return None

        # Read source to understand what needs testing
        try:
            source_code = source_file.read_text()
        except Exception as e:  # noqa: BLE001
            logger.error(f"Cannot read {source_file}: {e}")
            return None

        # Generate test file path
        test_file = self.output_dir / f"test_{module_name}_behavioral.py"

        # Extract module path for imports
        module_path = str(source_file).replace("src/", "").replace(".py", "").replace("/", ".")

        # Generate tests using LLM agent with Anthropic best practices
        # Phase 1: Basic generation
        # Phase 2: Multi-turn refinement (if enabled)
        # Phase 3: Coverage-guided improvement (if enabled)

        if self.enable_refinement:
            logger.info(f"🔄 Using Phase 2: Multi-turn refinement for {module_name}")
            test_content = self._generate_with_refinement(
                module_name,
                module_path,
                source_file,
                source_code,
                test_file,
            )
        else:
            logger.info(f"📝 Using Phase 1: Basic generation for {module_name}")
            test_content = self._generate_with_llm(
                module_name,
                module_path,
                source_file,
                source_code,
            )

        if not test_content:
            logger.warning(f"LLM generation failed for {module_name}")
            return None

        logger.info(f"LLM generated {len(test_content)} bytes for {module_name}")

        # Phase 3: Coverage-guided improvement (if enabled)
        if self.enable_coverage_guided:
            logger.info(f"📊 Applying Phase 3: Coverage-guided improvement for {module_name}")
            improved_content = self._generate_with_coverage_target(
                module_name,
                module_path,
                source_file,
                source_code,
                test_file,
                test_content,
            )
            if improved_content:
                test_content = improved_content
                logger.info(f"✅ Coverage-guided improvement complete for {module_name}")
            else:
                logger.warning(
                    f"⚠️  Coverage-guided improvement failed, using previous version for {module_name}",
                )

        # Write final test file
        validated_path = _validate_file_path(str(test_file))
        validated_path.write_text(test_content)
        logger.info(f"Wrote test file: {validated_path}")

        # Validate it can be imported
        if not self._validate_test_file(test_file):
            test_file.unlink()
            return None

        return test_file

    def _generate_with_llm(
        self,
        module_name: str,
        module_path: str,
        source_file: Path,
        source_code: str,
    ) -> str | None:
        """Generate comprehensive tests using LLM with Anthropic best practices.

        ENHANCEMENTS (Phase 1):
        - Extended thinking (20K token budget) for thorough test planning
        - Prompt caching for 90% cost reduction
        - Full source code (NO TRUNCATION)
        - Workflow-specific prompts when detected

        Args:
            module_name: Name of module being tested
            module_path: Python import path (e.g., attune.config)
            source_file: Path to source file
            source_code: Source code content (FULL, not truncated)

        Returns:
            Test file content with comprehensive tests, or None if generation failed

        """
        import os

        try:
            import anthropic
        except ImportError:
            logger.error("anthropic package not installed")
            return None

        # Get API key
        api_key = os.getenv("ANTHROPIC_API_KEY")
        if not api_key:
            logger.error("ANTHROPIC_API_KEY not set")
            return None

        # Detect if this is a workflow module
        is_workflow = self._is_workflow_module(source_code, module_path)
        logger.info(
            f"Module {module_name}: workflow={is_workflow}, size={len(source_code)} bytes (FULL)",
        )

        # Build appropriate prompt based on module type
        if is_workflow:
            generation_prompt = self._get_workflow_specific_prompt(
                module_name,
                module_path,
                source_code,
            )
        else:
            generation_prompt = f"""Generate comprehensive behavioral tests for this Python module.

SOURCE FILE: {source_file}
MODULE PATH: {module_path}

SOURCE CODE (COMPLETE):
```python
{source_code}
```

Generate a complete test file that:
1. Uses Given/When/Then behavioral test structure
2. Tests all public functions and classes
3. Includes edge cases and error handling
4. Uses proper mocking for external dependencies
5. Targets 80%+ code coverage for this module
6. Follows pytest conventions

Requirements:
- Import from {module_path} (not from src/)
- Use pytest fixtures where appropriate
- Mock external dependencies (APIs, databases, file I/O)
- Test both success and failure paths
- Include docstrings for all tests
- Use descriptive test names
- Start with copyright header:
\"\"\"Behavioral tests for {module_name}.

Generated by enhanced autonomous test generation system.

Copyright 2026 Smart-AI-Memory
Licensed under Apache 2.0
\"\"\"

Return ONLY the complete Python test file content, no explanations."""

        # Build messages with prompt caching (90% cost reduction on retries)
        messages = [
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": "You are an expert Python test engineer. Here are examples of excellent tests:",
                        "cache_control": {"type": "ephemeral"},
                    },
                    {
                        "type": "text",
                        "text": self._get_example_tests(),
                        "cache_control": {"type": "ephemeral"},
                    },
                    {"type": "text", "text": generation_prompt},
                ],
            },
        ]

        try:
            # Call Anthropic API with extended thinking and caching
            logger.info(
                f"Calling LLM with extended thinking for {module_name} (workflow={is_workflow})",
            )
            client = anthropic.Anthropic(api_key=api_key)
            response = client.messages.create(
                model="claude-sonnet-4-6",  # capable tier
                max_tokens=40000,  # Very generous total budget for comprehensive tests
                thinking={
                    "type": "enabled",
                    "budget_tokens": 20000,  # Generous thinking budget for thorough planning
                },
                messages=messages,
                timeout=900.0,  # 15 minutes timeout for extended thinking + generation
            )

            if not response.content:
                logger.warning(f"Empty LLM response for {module_name}")
                return None

            # Extract test content (thinking comes first, then text)
            test_content = None
            for block in response.content:
                if block.type == "text":
                    test_content = block.text.strip()
                    break

            if not test_content:
                logger.warning(f"No text content in LLM response for {module_name}")
                return None

            logger.info(f"LLM returned {len(test_content)} bytes for {module_name}")

            if len(test_content) < 100:
                logger.warning(f"LLM response too short for {module_name}: {test_content[:200]}")
                return None

            # Clean up response (remove markdown fences if present)
            if test_content.startswith("```python"):
                test_content = test_content[len("```python") :].strip()
            if test_content.endswith("```"):
                test_content = test_content[:-3].strip()

            # Check for truncation indicators
            if response.stop_reason == "max_tokens":
                logger.warning(f"⚠️  LLM response truncated for {module_name} (hit max_tokens)")
                # Response might be incomplete but let validation catch it

            # Quick syntax pre-check before returning
            try:
                import ast

                ast.parse(test_content)
                logger.info(f"✓ Quick syntax check passed for {module_name}")
            except SyntaxError as e:
                logger.error(
                    f"❌ LLM generated invalid syntax for {module_name}: {e.msg} at line {e.lineno}",
                )
                return None

            logger.info(f"Test content cleaned, final size: {len(test_content)} bytes")
            return test_content

        except Exception as e:  # noqa: BLE001
            logger.error(f"LLM generation error for {module_name}: {e}", exc_info=True)
            return None

    def _validate_test_file(self, test_file: Path) -> bool:
        """Validate test file can be imported and has valid syntax.

        Args:
            test_file: Path to test file

        Returns:
            True if valid, False otherwise

        """
        # Step 1: Check for syntax errors with ast.parse (fast)
        try:
            import ast

            content = test_file.read_text()
            ast.parse(content)
            logger.info(f"✓ Syntax check passed for {test_file.name}")
        except SyntaxError as e:
            logger.error(f"❌ Syntax error in {test_file.name} at line {e.lineno}: {e.msg}")
            return False
        except Exception as e:  # noqa: BLE001
            logger.error(f"❌ Cannot parse {test_file.name}: {e}")
            return False

        # Step 2: Check if pytest can collect the tests
        try:
            result = subprocess.run(
                [sys.executable, "-m", "pytest", "--collect-only", str(test_file)],
                capture_output=True,
                text=True,
                timeout=10,
            )

            if result.returncode != 0:
                logger.error(f"❌ Pytest collection failed for {test_file.name}")
                logger.error(f"   Error: {result.stderr[:500]}")
                return False

            logger.info(f"✓ Pytest collection passed for {test_file.name}")
            return True

        except subprocess.TimeoutExpired:
            logger.error(f"❌ Validation timeout for {test_file.name}")
            return False
        except Exception as e:  # noqa: BLE001
            logger.error(f"❌ Validation exception for {test_file}: {e}")
            return False

    def _count_tests(self) -> int:
        """Count total tests in generated files.

        Returns:
            Number of tests

        """
        try:
            result = subprocess.run(
                [sys.executable, "-m", "pytest", "--collect-only", "-q", str(self.output_dir)],
                capture_output=True,
                text=True,
                timeout=30,
            )
            # Parse output like "123 tests collected"
            for line in result.stdout.split("\n"):
                if "tests collected" in line:
                    return int(line.split()[0])
            return 0
        except Exception:  # noqa: BLE001
            return 0


def run_batch_generation(
    batch_num: int,
    modules_json: str,
    enable_refinement: bool = True,
    enable_coverage_guided: bool = False,
) -> None:
    """Run test generation for a batch.

    Args:
        batch_num: Batch number
        modules_json: JSON string of modules to process
        enable_refinement: Enable Phase 2 multi-turn refinement (default: True)
        enable_coverage_guided: Enable Phase 3 coverage-guided generation (default: False)

    """
    # Parse modules
    modules = json.loads(modules_json)

    # Create agent with Phase 2 & 3 configuration
    agent_id = f"test-gen-batch{batch_num}"
    generator = AutonomousTestGenerator(
        agent_id,
        batch_num,
        modules,
        enable_refinement=enable_refinement,
        enable_coverage_guided=enable_coverage_guided,
    )

    # Generate tests
    print(f"Starting autonomous test generation for batch {batch_num}")
    print(f"Modules to process: {len(modules)}")
    print(f"Agent ID: {agent_id}")
    print("\nENHANCEMENTS:")
    print("  Phase 1: Extended thinking + Prompt caching + Workflow detection")
    print(f"  Phase 2: Multi-turn refinement = {'ENABLED' if enable_refinement else 'DISABLED'}")
    print(f"  Phase 3: Coverage-guided = {'ENABLED' if enable_coverage_guided else 'DISABLED'}")
    print("\nMonitor at: http://localhost:8000\n")

    results = generator.generate_all()

    # Report results
    print(f"\n{'='*60}")
    print(f"Batch {batch_num} Complete!")
    print(f"{'='*60}")
    print(f"Modules processed: {results['completed']}/{results['total_modules']}")
    print(f"Tests generated: {results['tests_generated']}")
    print(f"Files created: {len(results['files_created'])}")
    print(f"Failed: {results['failed']}")


if __name__ == "__main__":
    import sys

    if len(sys.argv) < 3:
        print(
            "Usage: python -m attune.workflows.autonomous_test_gen <batch_num> <modules_json> [--no-refinement] [--coverage-guided]",
        )
        print("\nOptions:")
        print("  --no-refinement     Disable Phase 2 multi-turn refinement")
        print("  --coverage-guided   Enable Phase 3 coverage-guided generation (slower)")
        sys.exit(1)

    batch_num = int(sys.argv[1])
    modules_json = sys.argv[2]

    # Parse optional flags
    enable_refinement = "--no-refinement" not in sys.argv
    enable_coverage_guided = "--coverage-guided" in sys.argv

    run_batch_generation(batch_num, modules_json, enable_refinement, enable_coverage_guided)
