"""Dynamic prompt construction for fix generation.

Builds error-type-specific prompts with few-shot examples
and chain-of-thought guidance.
"""

from __future__ import annotations

from typing import Dict, List, Optional

from autodebug.prompts.examples import (
    load_examples_for_type,
    load_negative_examples_for_type,
)


class PromptBuilder:
    """Builds structured prompts for LLM fix generation.

    Constructs prompts with:
    - Error-type-specific few-shot examples
    - Negative examples (what NOT to do)
    - Chain-of-thought reasoning guides
    - Constraints and requirements
    """

    def __init__(self):
        """Initialize the prompt builder."""
        self.constraints = self._load_constraints()
        self.reasoning_guides = self._load_reasoning_guides()

    def build_fix_prompt(
        self,
        error_type: str,
        error_message: str,
        code_context: str,
        stack_trace: str,
        io_transcript: Optional[str] = None,
        similar_fixes: Optional[List[Dict]] = None,
        file_path: Optional[str] = None,
        function_source: Optional[str] = None,
    ) -> str:
        """Build error-type-specific prompt with few-shot examples.

        Args:
            error_type: Exception type (e.g., "KeyError")
            error_message: Exception message
            code_context: Code around the error location
            stack_trace: Formatted stack trace
            io_transcript: Optional I/O transcript
            similar_fixes: Optional list of similar past fixes
            file_path: Optional file path for context
            function_source: Optional full function source

        Returns:
            Complete prompt string
        """
        # Load examples for this error type
        examples = load_examples_for_type(error_type, limit=3)
        negative_examples = load_negative_examples_for_type(error_type, limit=2)

        sections = [
            self._build_system_context(),
            self._build_few_shot_section(examples),
            self._build_negative_examples(negative_examples),
            self._build_constraints_section(error_type),
            self._build_chain_of_thought_guide(error_type),
            self._build_current_error(
                error_type, error_message, code_context, stack_trace,
                file_path=file_path, function_source=function_source
            ),
        ]

        # Add optional sections
        if io_transcript:
            sections.append(self._build_io_context(io_transcript))
        if similar_fixes:
            sections.append(self._build_similar_fixes(similar_fixes))

        sections.append(self._build_task_instruction())

        return "\n\n".join(filter(None, sections))

    def _build_system_context(self) -> str:
        """Build the system context section."""
        return """# Expert Python Debugger

You are an expert Python debugger. Your task is to analyze a failing test or runtime error, identify the root cause, and provide a minimal fix.

**Key principles:**
- Focus on the ACTUAL bug, not just adding error handling
- Prefer minimal changes that fix the root cause
- Consider edge cases and null checks
- Preserve existing behavior for valid inputs"""

    def _build_few_shot_section(self, examples: List[Dict]) -> str:
        """Format few-shot examples."""
        if not examples:
            return ""

        section = "## Examples of Good Fixes\n"

        for i, ex in enumerate(examples, 1):
            section += f"\n### Example {i}\n"
            section += f"**Error:** {ex['error']['type']}: {ex['error']['message']}\n"
            section += f"**Context:** {ex['error'].get('context', 'N/A')}\n"
            section += f"**Code:**\n```python\n{ex['error']['code'].strip()}\n```\n"
            section += f"**Root Cause:** {ex['analysis']['root_cause']}\n"
            section += f"**Reasoning:** {ex['analysis']['reasoning']}\n"
            section += f"**Fix Strategy:** {ex['fix']['strategy']}\n"
            section += f"**Diff:**\n```diff\n{ex['fix']['diff'].strip()}\n```\n"
            section += f"**Explanation:** {ex['fix']['explanation']}\n"

        return section

    def _build_negative_examples(self, negatives: List[Dict]) -> str:
        """Show what NOT to do."""
        if not negatives:
            return ""

        section = "## Common Mistakes to Avoid\n"

        for neg in negatives:
            section += f"\n**Bad Fix:**\n```python\n{neg['bad_fix'].strip()}\n```\n"
            section += f"**Why it's bad:** {neg['why_bad']}\n"

        return section

    def _build_constraints_section(self, error_type: str) -> str:
        """Error-type-specific constraints."""
        constraints = self.constraints.get(error_type, self.constraints.get("default", {}))

        must_rules = constraints.get("must", [
            "Address the actual root cause",
            "Preserve behavior for valid inputs",
            "Use valid unified diff format",
        ])

        must_not_rules = constraints.get("must_not", [
            "Silently ignore errors",
            "Use bare except clauses",
            "Make unnecessary changes",
        ])

        return f"""## Constraints

**Your fix MUST:**
{chr(10).join(f'- {c}' for c in must_rules)}

**Your fix MUST NOT:**
{chr(10).join(f'- {c}' for c in must_not_rules)}"""

    def _build_chain_of_thought_guide(self, error_type: str) -> str:
        """Error-type-specific reasoning guide."""
        guide = self.reasoning_guides.get(error_type)
        if guide:
            return f"## How to Approach This {error_type}\n\n{guide}"
        return ""

    def _build_current_error(
        self,
        error_type: str,
        error_message: str,
        code_context: str,
        stack_trace: str,
        file_path: Optional[str] = None,
        function_source: Optional[str] = None,
    ) -> str:
        """Build the current error section."""
        section = f"""## Current Error to Fix

**Exception:** {error_type}: {error_message}
"""
        if file_path:
            section += f"**File:** {file_path}\n"

        section += f"""
**Stack Trace:**
```
{stack_trace.strip()}
```

**Code Context:**
```python
{code_context.strip()}
```"""

        if function_source:
            section += f"""

**Full Function:**
```python
{function_source.strip()}
```"""

        return section

    def _build_io_context(self, io_transcript: str) -> str:
        """Build I/O context section."""
        return f"""## I/O Transcript

The following external calls were made before the error:

```
{io_transcript.strip()[:2000]}
```"""

    def _build_similar_fixes(self, similar_fixes: List[Dict]) -> str:
        """Build similar fixes section."""
        if not similar_fixes:
            return ""

        section = "## Similar Fixes That Worked\n"

        for i, fix in enumerate(similar_fixes[:2], 1):
            section += f"\n### Similar Fix {i}\n"
            section += f"**Error:** {fix.get('error_type', 'Unknown')}\n"
            section += f"**Strategy:** {fix.get('strategy', 'Unknown')}\n"
            section += f"**Outcome:** {fix.get('outcome', 'Success')}\n"

        return section

    def _build_task_instruction(self) -> str:
        """Build the task instruction."""
        return """## Task

Analyze the error and provide a fix. Your response must include:

1. **Analysis**: Identify the root cause and explain your reasoning
2. **Fix**: Provide a complete unified diff that can be applied with `patch -p1`
3. **Verification**: Confirm the fix addresses the root cause

The diff must be valid unified diff format with:
- `--- a/filename` header
- `+++ b/filename` header
- `@@ -line,count +line,count @@` hunk headers
- Context lines (unchanged), `-` lines (removed), `+` lines (added)"""

    def _load_constraints(self) -> Dict[str, Dict]:
        """Load error-type-specific constraints."""
        return {
            "KeyError": {
                "must": [
                    "Use .get() for optional keys",
                    "Provide clear error messages for required keys",
                    "Handle nested access safely",
                ],
                "must_not": [
                    "Return None silently for required keys",
                    "Use bare except to catch KeyError",
                ],
            },
            "TypeError": {
                "must": [
                    "Check for None before attribute/method access",
                    "Validate types at function boundaries",
                    "Convert types explicitly when needed",
                ],
                "must_not": [
                    "Use isinstance() excessively",
                    "Convert everything to string blindly",
                ],
            },
            "AttributeError": {
                "must": [
                    "Check for None before accessing attributes",
                    "Verify object types at boundaries",
                ],
                "must_not": [
                    "Use hasattr() without handling the False case",
                    "Use getattr() with None default for required attrs",
                ],
            },
            "ValueError": {
                "must": [
                    "Validate input before processing",
                    "Provide clear error messages about expected format",
                ],
                "must_not": [
                    "Default to 0 or empty string on validation failure",
                    "Use overly permissive parsing",
                ],
            },
            "AssertionError": {
                "must": [
                    "Fix the root cause of the wrong value, not just the assertion",
                    "Ensure deterministic data ordering when comparing sequences",
                    "Isolate test state to prevent pollution from other tests",
                ],
                "must_not": [
                    "Sort both sides of the assertion to mask ordering bugs",
                    "Add @pytest.mark.flaky or @retry to suppress failures",
                    "Weaken assertions (e.g., changing == to 'in')",
                ],
            },
            "TimeoutError": {
                "must": [
                    "Mock external dependencies instead of making real calls",
                    "Use explicit wait conditions instead of sleep()",
                    "Set appropriate timeouts based on operation complexity",
                ],
                "must_not": [
                    "Add time.sleep() to 'fix' timing issues",
                    "Catch TimeoutError and skip/pass the test",
                    "Increase timeout to unreasonable values",
                ],
            },
            "default": {
                "must": [
                    "Address the actual root cause",
                    "Preserve behavior for valid inputs",
                    "Use valid unified diff format",
                ],
                "must_not": [
                    "Silently ignore errors",
                    "Use bare except clauses",
                    "Make unnecessary changes",
                ],
            },
        }

    def _load_reasoning_guides(self) -> Dict[str, str]:
        """Load error-type-specific reasoning guides."""
        return {
            "KeyError": """1. What key is missing? Identify the exact key from the error message
2. What object was being accessed? Find the dict/object in the code
3. Where did this object come from? (API response / config / user input)
4. Is this key sometimes missing legitimately? If yes: use .get() / If no: validate earlier
5. What should happen when missing? (Error with message / Default value / Skip)""",

            "TypeError": """1. What types were involved? Expected vs Actual from the message
2. Where did the wrong type come from? Trace the data flow
3. Is the type annotation correct? Check if code or annotation is wrong
4. Should we convert or reject? Depends on whether conversion is safe
5. Where should validation occur? At input boundary, not deep in code""",

            "AttributeError": """1. What attribute is missing? From the error message
2. What is the object's actual type? 'NoneType' = the object is None
3. Where should the object have been created/assigned?
4. Is None a valid state? If yes: check for it / If no: fix the source
5. Could this be a typo in the attribute name?""",

            "ValueError": """1. What value caused the error? From the error message
2. What was the expected format/range? From the code
3. Where did the invalid value come from? Trace the data flow
4. Is this user input or internal data? Determines validation location
5. What should happen with invalid values? Reject or convert?""",

            "IndexError": """1. What index was accessed? Often [0] on empty list
2. What is the list's actual length? Could be 0
3. Where does the list come from? Could be empty legitimately
4. Is empty list valid? If yes: handle it / If no: validate earlier
5. Could negative indexing cause this? [-1] on empty list fails too""",

            "AssertionError": """1. What was asserted? Compare expected vs actual values
2. Is the assertion checking the right thing? Sometimes the assertion is wrong
3. Is the data non-deterministic? (ordering, timing, random values, timestamps)
4. Could shared state from another test pollute this assertion?
5. Is this a flaky test? Check if the values are close but not equal (floating point, timing)
6. Should the assertion be more tolerant? (set comparison instead of list, approximate match)""",

            "TimeoutError": """1. What operation timed out? Identify the specific call
2. Is it calling an external service? If yes: mock it instead
3. Is the timeout value appropriate? Check against actual operation time
4. Could the timeout be eliminated? Maybe the operation can be awaited directly
5. Is this a resource contention issue? Multiple tests fighting for same lock/port""",
        }
