"""
Example-Driven Knowledge Base Module

Manages complete, runnable code examples with test cases, pitfalls, and live references.
Provides example search, retrieval, and validation capabilities.
"""

import csv
import json
import re
from pathlib import Path
from typing import List, Dict, Any, Optional
from dataclasses import dataclass


# Example type priorities for boosting
EXAMPLE_TYPE_BOOST = {
    'quickstart': 1.0,    # Base priority
    'integration': 1.2,   # Higher for complex queries
    'debugging': 1.3,     # Highest for error queries
    'best-practice': 1.1, # Moderate boost
    'security': 1.4       # Highest for security queries
}


@dataclass
class Example:
    """Represents a complete code example"""
    id: str
    domain: str
    example_type: str
    scenario: str
    problem: str
    solution_code: str
    explanation: str
    test_inputs: str  # JSON string
    expected_outputs: str  # JSON string
    pitfalls: str
    live_example_url: str
    related_snippets: str
    tags: str
    difficulty: str

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary representation"""
        return {
            'id': self.id,
            'domain': self.domain,
            'example_type': self.example_type,
            'scenario': self.scenario,
            'problem': self.problem,
            'solution_code': self.solution_code,
            'explanation': self.explanation,
            'test_inputs': self.test_inputs,
            'expected_outputs': self.expected_outputs,
            'pitfalls': self.pitfalls,
            'live_example_url': self.live_example_url,
            'related_snippets': self.related_snippets,
            'tags': self.tags,
            'difficulty': self.difficulty
        }

    def get_test_inputs(self) -> Dict:
        """Parse test_inputs JSON"""
        try:
            return json.loads(self.test_inputs) if self.test_inputs else {}
        except json.JSONDecodeError:
            return {}

    def get_expected_outputs(self) -> Dict:
        """Parse expected_outputs JSON"""
        try:
            return json.loads(self.expected_outputs) if self.expected_outputs else {}
        except json.JSONDecodeError:
            return {}

    def get_related_snippet_refs(self) -> List[Dict[str, str]]:
        """
        Parse related_snippets into list of references

        Format: "domain.csv:id,domain2.csv:id2"
        Returns: [{"file": "domain.csv", "id": "id"}, ...]
        """
        if not self.related_snippets:
            return []

        refs = []
        for ref in self.related_snippets.split(','):
            ref = ref.strip()
            if ':' in ref:
                file, snippet_id = ref.split(':', 1)
                refs.append({'file': file, 'id': snippet_id})

        return refs


def load_examples(
    csv_path: Optional[Path] = None,
    domain: Optional[str] = None,
    example_type: Optional[str] = None,
    difficulty: Optional[str] = None
) -> List[Example]:
    """
    Load examples from CSV file with optional filtering

    Args:
        csv_path: Path to examples.csv. If None, uses default location.
        domain: Filter by domain (defi, nfts, etc.)
        example_type: Filter by example type (quickstart, integration, etc.)
        difficulty: Filter by difficulty (beginner, intermediate, advanced)

    Returns:
        List of Example objects
    """
    if csv_path is None:
        script_dir = Path(__file__).parent
        csv_path = script_dir.parent / 'data' / 'examples.csv'

    if not csv_path.exists():
        return []

    examples = []

    with open(csv_path, 'r', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        for row in reader:
            example = Example(
                id=row['id'],
                domain=row['domain'],
                example_type=row['example_type'],
                scenario=row['scenario'],
                problem=row['problem'],
                solution_code=row['solution_code'],
                explanation=row['explanation'],
                test_inputs=row.get('test_inputs', ''),
                expected_outputs=row.get('expected_outputs', ''),
                pitfalls=row.get('pitfalls', ''),
                live_example_url=row.get('live_example_url', ''),
                related_snippets=row.get('related_snippets', ''),
                tags=row.get('tags', ''),
                difficulty=row.get('difficulty', 'intermediate')
            )

            # Apply filters
            if domain and example.domain != domain:
                continue
            if example_type and example.example_type != example_type:
                continue
            if difficulty and example.difficulty != difficulty:
                continue

            examples.append(example)

    return examples


def example_to_searchable_text(example: Example) -> str:
    """
    Convert example to searchable text for BM25 indexing

    Args:
        example: Example object

    Returns:
        Concatenated searchable text
    """
    import re

    # Normalize tags: split by comma to make individual keywords searchable
    tags_normalized = example.tags.replace(',', ' ')

    # Normalize scenario: replace hyphens with spaces so "wallet-connect-flow" matches "connect"
    scenario_normalized = example.scenario.replace('-', ' ')

    # Extract source project names from explanation and solution_code
    # Look for patterns like "from sbtc-market-frontend", "STX City", etc.
    source_projects = []
    combined_text = f"{example.explanation} {example.solution_code[:500]}"  # First 500 chars of code

    # Match common patterns (case-insensitive)
    project_patterns = [
        # Match: "from PROJECT-frontend/backend"
        r'from\s+([a-z0-9-]+(?:frontend|backend|app|market|demo|project))',
        # Match: "PROJECT/src/"
        r'([a-z0-9-]+(?:frontend|backend|app|market|demo|project))/src',
        # Match: "PROJECT patterns"
        r'([a-z0-9-]+(?:frontend|backend|app|market|demo|project))\s+patterns',
        # Match: "Pattern from PROJECT"
        r'Pattern from\s+([a-z0-9-]+)',
        # Match: "Source: PROJECT"
        r'Source:\s+([a-z0-9-]+)',
        # Match: "STX City", "Stacks Punks", etc (two-word project names)
        r'\b(stx\s+city|stacks\s+punks|bitcoin\s+monkeys|gamma\s+marketplace)\b',
    ]

    for pattern in project_patterns:
        matches = re.findall(pattern, combined_text, re.IGNORECASE)
        source_projects.extend([m.lower() for m in matches])

    # Extract individual words from project names
    # e.g., "sbtc-market-frontend" → ["sbtc", "market"]
    # e.g., "stx city" → ["stx", "city"]
    project_keywords = []
    for project in source_projects:
        # Remove common suffixes
        project_clean = project.replace('-frontend', '').replace('-backend', '').replace('-app', '')
        # Split by hyphens and spaces
        words = re.split(r'[-\s]+', project_clean)
        project_keywords.extend(words)

    # Join unique keywords, removing empty strings
    project_keywords_str = ' '.join(set(w for w in project_keywords if w))

    return ' '.join([
        scenario_normalized,
        example.problem,
        example.explanation,
        tags_normalized,
        example.domain,
        example.example_type,
        project_keywords_str  # Add extracted project keywords
    ])


def normalize_query(query: str) -> str:
    """
    Normalize search query to handle common variations and abbreviations

    Args:
        query: Original search query

    Returns:
        Expanded query with common variations
    """
    import re

    query_lower = query.lower()
    expanded_terms = [query]  # Always include original query

    # SIP standard variations
    sip_variations = {
        'sip10': ['sip010', 'sip-010', 'fungible-token'],
        'sip9': ['sip009', 'sip-009', 'nft'],
        'sip013': ['sip-013', 'transfer-memo'],
        'sip016': ['sip-016', 'token-metadata'],
    }

    # Common abbreviations and project names
    abbreviations = {
        'ft': ['fungible-token', 'sip010', 'token'],
        'nft': ['non-fungible-token', 'sip009', 'sip-009'],
        'pc': ['post-condition', 'postcondition'],
        'dao': ['decentralized-autonomous-organization'],
        'dex': ['decentralized-exchange', 'swap', 'liquidity'],
        'sbtc': ['sbtc-market', 'bitcoin', 'bridge'],
        'pyth': ['pyth-oracle', 'oracle', 'price-feed'],
        'stxcity': ['stx city', 'stx-city', 'bonding-curve'],  # NOTE: Added "stx city" with space
    }

    # Check each word in the query
    words = query_lower.split()
    for word in words:
        # Check SIP variations
        if word in sip_variations:
            expanded_terms.extend(sip_variations[word])

        # Check abbreviations
        if word in abbreviations:
            expanded_terms.extend(abbreviations[word])

        # Handle zero-padded numbers (sip10 → sip010, sip9 → sip009)
        sip_match = re.match(r'sip(\d{1,2})$', word)
        if sip_match:
            num = sip_match.group(1)
            # Add zero-padded version
            padded = f'sip{int(num):03d}'
            if padded not in expanded_terms:
                expanded_terms.append(padded)
            # Also add hyphenated version
            hyphenated = f'sip-{int(num):03d}'
            if hyphenated not in expanded_terms:
                expanded_terms.append(hyphenated)

    # Join all expanded terms
    return ' '.join(expanded_terms)


def search_examples(
    query: str,
    domain: Optional[str] = None,
    max_results: int = 5,
    example_type: Optional[str] = None,
    difficulty: Optional[str] = None
) -> List[Dict[str, Any]]:
    """
    Search examples using BM25 algorithm with type boosting

    Args:
        query: Search query
        domain: Filter by domain
        max_results: Maximum results to return
        example_type: Filter by example type
        difficulty: Filter by difficulty level

    Returns:
        List of matching examples with scores
    """
    from core import BM25  # Import BM25 from existing core module

    # Normalize query to handle common variations
    normalized_query = normalize_query(query)

    # Load examples
    examples = load_examples(domain=domain, example_type=example_type, difficulty=difficulty)

    if not examples:
        return []

    # Create searchable documents
    documents = [example_to_searchable_text(ex) for ex in examples]

    # BM25 scoring with normalized query
    bm25 = BM25(documents)
    scores = [(i, bm25.score(normalized_query, i)) for i in range(len(documents))]

    # Apply example type boosting based on query intent
    query_lower = query.lower()

    # Boost debugging examples for error/failure queries
    if any(kw in query_lower for kw in ['debug', 'error', 'fail', 'why', 'issue', 'problem']):
        for i, (idx, score) in enumerate(scores):
            if examples[idx].example_type == 'debugging':
                scores[i] = (idx, score * EXAMPLE_TYPE_BOOST['debugging'])

    # Boost security examples for security queries
    elif any(kw in query_lower for kw in ['secure', 'safe', 'vulnerability', 'attack', 'exploit']):
        for i, (idx, score) in enumerate(scores):
            if examples[idx].example_type == 'security':
                scores[i] = (idx, score * EXAMPLE_TYPE_BOOST['security'])

    # Boost integration examples for complex queries
    elif any(kw in query_lower for kw in ['integrate', 'combine', 'together', 'workflow']):
        for i, (idx, score) in enumerate(scores):
            if examples[idx].example_type == 'integration':
                scores[i] = (idx, score * EXAMPLE_TYPE_BOOST['integration'])

    # Sort by score (descending)
    scores.sort(key=lambda x: x[1], reverse=True)

    # Format results
    results = []
    for idx, score in scores[:max_results]:
        if score > 0:
            result = examples[idx].to_dict()
            result['_score'] = round(score, 3)
            result['_type'] = 'example'
            results.append(result)

    return results


def detect_example_preference(query: str) -> bool:
    """
    Detect if query is asking for examples vs code snippets

    Args:
        query: Search query

    Returns:
        True if examples should be preferred
    """
    example_keywords = [
        'how do i', 'how to', 'example of', 'show me',
        'why does', 'integrate', 'debugging', 'fails',
        'best practice', 'secure way', 'production',
        'complete', 'full code', 'working'
    ]

    query_lower = query.lower()
    return any(kw in query_lower for kw in example_keywords)


def get_related_snippets(example: Example) -> List[Dict[str, Any]]:
    """
    Fetch related code snippets referenced in example

    Args:
        example: Example object with related_snippets field

    Returns:
        List of related snippet records from CSV files
    """
    refs = example.get_related_snippet_refs()
    if not refs:
        return []

    script_dir = Path(__file__).parent
    data_dir = script_dir.parent / 'data'

    snippets = []

    for ref in refs:
        csv_file = ref['file']
        snippet_id = ref['id']

        csv_path = data_dir / csv_file

        if not csv_path.exists():
            continue

        try:
            with open(csv_path, 'r', encoding='utf-8') as f:
                reader = csv.DictReader(f)
                for row in reader:
                    if row.get('id') == snippet_id:
                        snippet = dict(row)
                        snippet['_source_file'] = csv_file
                        snippets.append(snippet)
                        break
        except Exception:
            continue

    return snippets


def format_example_for_display(example: Dict[str, Any]) -> str:
    """
    Format example for CLI display

    Args:
        example: Example dictionary

    Returns:
        Formatted string for display
    """
    lines = []

    # Header
    lines.append(f"\n{'=' * 80}")
    lines.append(f"Example: {example['scenario']}")
    lines.append(f"Domain: {example['domain']} | Type: {example['example_type']} | Difficulty: {example['difficulty']}")
    lines.append(f"Score: {example.get('_score', 0):.3f}")
    lines.append(f"{'=' * 80}\n")

    # Problem
    lines.append(f"Problem:\n{example['problem']}\n")

    # Solution
    lines.append(f"Solution:")
    lines.append(f"{example['solution_code']}\n")

    # Explanation
    if example.get('explanation'):
        lines.append(f"Explanation:\n{example['explanation']}\n")

    # Test inputs/outputs
    if example.get('test_inputs'):
        lines.append(f"Test Inputs: {example['test_inputs']}")
    if example.get('expected_outputs'):
        lines.append(f"Expected Outputs: {example['expected_outputs']}\n")

    # Pitfalls
    if example.get('pitfalls'):
        lines.append(f"⚠️  Common Pitfalls:\n{example['pitfalls']}\n")

    # Live example
    if example.get('live_example_url'):
        lines.append(f"🔗 Live Example: {example['live_example_url']}\n")

    # Related snippets
    if example.get('related_snippets'):
        lines.append(f"Related: {example['related_snippets']}\n")

    # Tags
    if example.get('tags'):
        lines.append(f"Tags: {example['tags']}")

    lines.append(f"\n{'=' * 80}\n")

    return '\n'.join(lines)


if __name__ == '__main__':
    # Test the module
    import sys

    if len(sys.argv) > 1:
        query = ' '.join(sys.argv[1:])
        results = search_examples(query, max_results=3)

        if results:
            print(f"Found {len(results)} examples for '{query}':\n")
            for result in results:
                print(format_example_for_display(result))
        else:
            print(f"No examples found for '{query}'")
    else:
        # Load and show statistics
        examples = load_examples()
        print(f"Total examples loaded: {len(examples)}")

        if examples:
            domains = {}
            types = {}
            difficulties = {}

            for ex in examples:
                domains[ex.domain] = domains.get(ex.domain, 0) + 1
                types[ex.example_type] = types.get(ex.example_type, 0) + 1
                difficulties[ex.difficulty] = difficulties.get(ex.difficulty, 0) + 1

            print(f"\nBy domain:")
            for domain, count in sorted(domains.items()):
                print(f"  {domain}: {count}")

            print(f"\nBy type:")
            for ex_type, count in sorted(types.items()):
                print(f"  {ex_type}: {count}")

            print(f"\nBy difficulty:")
            for diff, count in sorted(difficulties.items()):
                print(f"  {diff}: {count}")
