"""
Inline API - Real-time code validation for AI workflows.

Usage:
    from aicontract import check, check_file, ai_check, score, suggest
"""

import ast
import logging
from pathlib import Path
from typing import Optional, Callable
from dataclasses import dataclass

try:
    from ..rules import Violation, calculate_suspicion_score
    from ..rules.base_rules import get_rules
    from ..core.registry import Registry, FunctionContract, DomainContract
    from .config import Config, get_config
except ImportError:
    try:
        from aicontract.rules import Violation, calculate_suspicion_score
        from aicontract.rules.base_rules import get_rules
        from aicontract.core.registry import Registry, FunctionContract, DomainContract
        from aicontract.utils.config import Config, get_config
    except ImportError:
        from validator import Violation, calculate_suspicion_score, get_rules
        from registry import Registry, FunctionContract, DomainContract
        from config import Config, get_config

logger = logging.getLogger(__name__)

THRESHOLD_VALID = 50
LINENO_FALLBACK = 30


@dataclass
class CheckResult:
    """Result of inline code check."""

    violations: list
    suspicion: dict
    is_valid: bool
    message: str


def _create_registry(domain: str = "default") -> Registry:
    """Create a minimal registry for inline checks."""
    registry = Registry()
    domain_contract = DomainContract(
        name=domain,
        owner=None,
        owns=[],
        touches=[],
    )
    registry.domains = {domain: domain_contract}
    return registry


def _create_contract(
    node: ast.FunctionDef | ast.AsyncFunctionDef, domain: str, source: str
) -> FunctionContract:
    """Create a FunctionContract from AST node."""
    _extract_func_source(source, node)
    return FunctionContract(
        name=node.name,
        domain=domain,
        file="<inline>",
        line=node.lineno,
        touches=[],
        called_by=[],
        is_async=isinstance(node, ast.AsyncFunctionDef),
        risk="low",
        replaces="",
        registered_at="",
    )


def _extract_func_source(
    source: str, node: ast.FunctionDef | ast.AsyncFunctionDef
) -> str:
    """Extract function source from full source."""
    end = getattr(node, "end_lineno", node.lineno + LINENO_FALLBACK)
    return "\n".join(source.splitlines()[node.lineno - 1 : end])


def _validate_node(
    node: ast.FunctionDef | ast.AsyncFunctionDef,
    registry: Registry,
    domain: str,
    full_source: str,
    config: Config = None,
) -> list[Violation]:
    """Validate a single function node."""
    if config is None:
        config = get_config()

    # Check if should ignore
    if node.name.startswith("_") and node.name.endswith("__"):
        return []

    if config.should_ignore_function(node.name):
        return []

    func_source = _extract_func_source(full_source, node)
    contract = _create_contract(node, domain, func_source)
    rules = get_rules()
    violations = []

    for rule in rules:
        rule_id = getattr(rule, "__name__", "unknown")

        # Check if rule should be ignored by function name pattern
        if config.should_ignore_rule(rule_id):
            continue

        try:
            # Use full_source for module-level rules, func_source for function-level
            rule_name = rule.__name__
            if rule_name in (
                "rule_duplicate_import",
                "rule_god_module",
                "rule_db_in_loop",
                "rule_http_in_loop",
                "rule_sql_injection",
                "rule_never_called",
                "rule_hardcoded_secret",
                "rule_async_in_sync_chain",
            ):
                source_for_rule = full_source
            else:
                source_for_rule = func_source
            result = rule(contract, registry, source_for_rule)
            if result:
                if isinstance(result, list):
                    violations.extend(result)
                else:
                    violations.append(result)
        except Exception as e:
            logger.debug(f"Rule {rule.__name__} failed: {e}")

    # Filter violations by rule_id if config says so
    if config.ignore_rules:
        violations = [v for v in violations if v.rule_id not in config.ignore_rules]

    return violations


def check(source: str, domain: str = "default", config: Config = None) -> CheckResult:
    """
    Analyze a code string immediately.

    Args:
        source: Python code as string
        domain: Optional domain name for validation
        config: Optional configuration (uses global if not provided)

    Returns:
        CheckResult with violations, suspicion score, and validation status
    """
    if config is None:
        config = get_config()

    registry = _create_registry(domain)

    tree = _parse(source)
    if not tree:
        return CheckResult(
            violations=[],
            suspicion={
                "total": 100,
                "level": "danger",
                "message": "Syntax error in code",
            },
            is_valid=False,
            message="Syntax error",
        )

    violations = []
    for node in ast.walk(tree):
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
            violations.extend(_validate_node(node, registry, domain, source, config))

    suspicion = calculate_suspicion_score(violations)
    is_valid = suspicion["total"] < THRESHOLD_VALID

    return CheckResult(
        violations=violations,
        suspicion=suspicion,
        is_valid=is_valid,
        message=suspicion["message"],
    )


def check_file(filepath: str, domain: str = "default") -> CheckResult:
    """
    Analyze a file immediately.

    Args:
        filepath: Path to Python file
        domain: Optional domain name for validation

    Returns:
        CheckResult with violations, suspicion score, and validation status
    """
    path = Path(filepath)
    if not path.exists():
        return CheckResult(
            violations=[],
            suspicion={
                "total": 100,
                "level": "danger",
                "message": f"File not found: {filepath}",
            },
            is_valid=False,
            message="File not found",
        )

    source = path.read_text(encoding="utf-8")
    return check(source, domain)


def score(source: str) -> int:
    """
    Get 0-100 suspicion score for code.

    Args:
        source: Python code as string

    Returns:
        Integer 0-100 (0 = clean, 100 = very suspicious)
    """
    result = check(source)
    return result.suspicion["total"]


def ai_check(fn: Callable) -> Callable:
    """
    Decorator to validate a function after definition.

    Usage:
        @ai_check
        def my_function():
            # code here
            pass

    Returns original function with validation side-effect.
    """
    source = ""
    try:
        import inspect

        source = inspect.getsource(fn)
    except Exception:
        pass

    if source:
        result = check(source)
        if not result.is_valid:
            print(f"[aicontract] Warning: {result.message}")
            for v in result.violations[:3]:
                print(f"  - {v.rule_id}: {v.message}")

    return fn


def suggest(task: str, target_fn: Optional[str] = None, project_dir: str = ".") -> str:
    """
    Generate context for AI prompts before writing code.

    Args:
        task: What you want the AI to implement
        target_fn: Optional target function name
        project_dir: Optional project directory for real analysis

    Returns:
        Context string to prepend to AI prompt
    """
    # If project_dir provided, try to use real ContextGenerator
    if project_dir and Path(project_dir).exists():
        try:
            import context_gen
            import sql_adapter
            import migration_guard
            import session_memory

            # Initialize components
            sql = None
            try:
                sql = sql_adapter.SQLAdapter(project_dir)
            except Exception:
                pass

            mg = None
            try:
                mg = migration_guard.MigrationGuard(project_dir)
            except Exception:
                pass

            memory_file = str(Path(project_dir) / ".aicontract_memory.yaml")
            memory = None
            try:
                memory = session_memory.SessionMemory(memory_file)
            except Exception:
                pass

            gen = context_gen.ContextGenerator(
                project_dir=project_dir,
                memory=memory,
                sql_adapter=sql,
                migration_guard=mg,
            )

            return gen.wrap(task, target_fn=target_fn)
        except Exception:
            pass

    # Fallback to static template
    suggestions = []

    suggestions.append("# Context for AI code generation")
    suggestions.append("# Add this before prompting your AI coding assistant")
    suggestions.append("")

    if target_fn:
        suggestions.append(f"## Target: {target_fn}")

    suggestions.append(f"## Task: {task}")
    suggestions.append("")
    suggestions.append("## Validation Rules:")
    suggestions.append("- Use type hints for all function parameters and returns")
    suggestions.append("- Add docstrings explaining parameters and returns")
    suggestions.append("- Handle edge cases and errors explicitly")
    suggestions.append("- Avoid magic numbers; use constants")
    suggestions.append("- No bare except clauses")
    suggestions.append("- Use parameterized queries for SQL")
    suggestions.append("- Keep functions under 40 lines")
    suggestions.append("- Maximum 5 parameters per function")
    suggestions.append("")
    suggestions.append("## Suspicious Patterns to Avoid:")
    suggestions.append("- Hallucinated imports (verify package exists)")
    suggestions.append("- Hallucinated methods (check API docs)")
    suggestions.append("- Missing input validation")
    suggestions.append("- Hardcoded secrets")
    suggestions.append("- SQL injection risks (use parameterized queries)")
    suggestions.append("- DB/HTTP calls in loops")
    suggestions.append("")

    return "\n".join(suggestions)


def _parse(source: str):
    """Parse source code to AST."""
    try:
        return ast.parse(source)
    except SyntaxError:
        return None


__all__ = [
    "check",
    "check_file",
    "ai_check",
    "score",
    "suggest",
    "CheckResult",
]
