#!/usr/bin/env python3
"""
Luzia QA Validator - Ensures code and documentation stay in sync

Validates:
1. All route_* functions have corresponding KG entities
2. All projects in config.json are documented
3. Cross-references resolve
4. No stale/orphaned documentation

Integrates with luzia qa command.
"""

import ast
import json
import re
from pathlib import Path
from typing import Dict, List, Tuple, Any
from datetime import datetime

# Import our KG module
import sys
sys.path.insert(0, str(Path(__file__).parent))
from knowledge_graph import KnowledgeGraph, KG_PATHS

LUZIA_PATH = Path("/opt/server-agents/orchestrator/bin/luzia")
CONFIG_PATH = Path("/opt/server-agents/orchestrator/config.json")


class QAValidator:
    """Validates code-documentation synchronization."""

    def __init__(self):
        self.issues: List[Dict] = []
        self.warnings: List[Dict] = []
        self.info: List[Dict] = []

    def _add_issue(self, category: str, message: str, details: str = None):
        self.issues.append({
            "category": category,
            "message": message,
            "details": details,
            "severity": "error"
        })

    def _add_warning(self, category: str, message: str, details: str = None):
        self.warnings.append({
            "category": category,
            "message": message,
            "details": details,
            "severity": "warning"
        })

    def _add_info(self, category: str, message: str, details: str = None):
        self.info.append({
            "category": category,
            "message": message,
            "details": details,
            "severity": "info"
        })

    # --- Code Analysis ---

    def extract_routes(self) -> List[Dict]:
        """Extract all route_* functions from luzia script."""
        routes = []

        if not LUZIA_PATH.exists():
            self._add_issue("code", "Luzia script not found", str(LUZIA_PATH))
            return routes

        content = LUZIA_PATH.read_text()

        # Find all route_* function definitions with their docstrings
        # Pattern: def route_name(...): followed by optional newline and """docstring"""
        pattern = r'def (route_\w+)\([^)]*\)[^:]*:\s*\n?\s*"""(.*?)"""'
        matches = re.findall(pattern, content, re.DOTALL)

        for name, docstring in matches:
            # Extract command pattern from docstring (Handler: luzia xxx)
            cmd_match = re.search(r'Handler:\s*(.+?)(?:\n|$)', docstring)
            command = cmd_match.group(1).strip() if cmd_match else ""

            routes.append({
                "function": name,
                "command": command,
                "docstring": docstring.strip()[:200],
            })

        return routes

    def extract_router_patterns(self) -> List[str]:
        """Extract registered routes from Router class."""
        patterns = []

        if not LUZIA_PATH.exists():
            return patterns

        content = LUZIA_PATH.read_text()

        # Find self.routes list
        pattern = r'self\.routes\s*=\s*\[(.*?)\]'
        match = re.search(pattern, content, re.DOTALL)

        if match:
            routes_block = match.group(1)
            # Extract route handler names
            handler_pattern = r'\(self\._match_\w+,\s*(route_\w+|self\._route_\w+),'
            patterns = re.findall(handler_pattern, routes_block)

        return patterns

    def validate_routes(self) -> bool:
        """Validate all route functions are registered."""
        routes = self.extract_routes()
        registered = self.extract_router_patterns()

        # Check each route function is registered
        for route in routes:
            func = route["function"]
            if func not in registered and f"self._{func}" not in registered:
                # Internal routes start with _route_ instead of route_
                if not func.startswith("_"):
                    self._add_warning(
                        "routes",
                        f"Route function '{func}' not registered in Router",
                        route["docstring"][:100]
                    )

        self._add_info("routes", f"Found {len(routes)} route functions, {len(registered)} registered")
        return len(self.issues) == 0

    # --- Documentation Validation ---

    def validate_command_docs(self) -> bool:
        """Validate all commands are documented in KG."""
        try:
            kg = KnowledgeGraph("sysadmin")
        except Exception as e:
            self._add_warning("kg", f"Could not open sysadmin KG: {e}")
            return True  # Not an error if KG doesn't exist yet

        routes = self.extract_routes()
        documented = {e["name"] for e in kg.list_entities("command")}

        for route in routes:
            cmd_name = route["function"].replace("route_", "luzia_")
            if cmd_name not in documented:
                self._add_warning(
                    "docs",
                    f"Command '{route['function']}' not documented in KG",
                    f"Add with: luzia docs add sysadmin {cmd_name} command ..."
                )

        return len(self.issues) == 0

    def validate_project_docs(self) -> bool:
        """Validate all projects in config are documented."""
        if not CONFIG_PATH.exists():
            self._add_issue("config", "Config file not found", str(CONFIG_PATH))
            return False

        try:
            config = json.loads(CONFIG_PATH.read_text())
        except Exception as e:
            self._add_issue("config", f"Could not parse config: {e}")
            return False

        try:
            kg = KnowledgeGraph("projects")
        except Exception as e:
            self._add_warning("kg", f"Could not open projects KG: {e}")
            return True

        projects = config.get("projects", {}).keys()
        documented = {e["name"] for e in kg.list_entities("project")}

        for project in projects:
            if project not in documented:
                self._add_warning(
                    "docs",
                    f"Project '{project}' not documented in KG",
                    f"Add with: luzia docs add projects {project} project ..."
                )

        self._add_info("projects", f"Found {len(projects)} projects in config")
        return len(self.issues) == 0

    # --- Syntax Validation ---

    def validate_python_syntax(self) -> bool:
        """Validate Python syntax of luzia script."""
        if not LUZIA_PATH.exists():
            self._add_issue("syntax", "Luzia script not found")
            return False

        try:
            content = LUZIA_PATH.read_text()
            ast.parse(content)
            self._add_info("syntax", "Python syntax valid")
            return True
        except SyntaxError as e:
            self._add_issue("syntax", f"Syntax error: {e.msg}", f"Line {e.lineno}: {e.text}")
            return False

    # --- Full Validation ---

    def validate_all(self) -> Dict:
        """Run all validations."""
        self.issues = []
        self.warnings = []
        self.info = []

        results = {
            "syntax": self.validate_python_syntax(),
            "routes": self.validate_routes(),
            "command_docs": self.validate_command_docs(),
            "project_docs": self.validate_project_docs(),
        }

        return {
            "passed": len(self.issues) == 0,
            "results": results,
            "issues": self.issues,
            "warnings": self.warnings,
            "info": self.info,
            "summary": {
                "errors": len(self.issues),
                "warnings": len(self.warnings),
                "info": len(self.info),
            },
            "timestamp": datetime.now().isoformat(),
        }

    # --- Auto-sync ---

    def sync_routes_to_kg(self) -> Dict:
        """Sync route functions to sysadmin KG."""
        try:
            kg = KnowledgeGraph("sysadmin")
        except Exception as e:
            return {"error": str(e)}

        routes = self.extract_routes()
        added = 0
        updated = 0

        for route in routes:
            name = route["function"].replace("route_", "luzia_")
            existing = kg.get_entity(name)

            # Build content from docstring
            content = f"Command: {route['command']}\n\n{route['docstring']}"

            kg.add_entity(
                name=name,
                entity_type="command",
                content=content,
                metadata={"function": route["function"], "auto_synced": True},
                source="luzia_script"
            )

            if existing:
                updated += 1
            else:
                added += 1

        return {
            "added": added,
            "updated": updated,
            "total": len(routes),
        }

    def sync_projects_to_kg(self) -> Dict:
        """Sync projects from config to KG."""
        if not CONFIG_PATH.exists():
            return {"error": "Config not found"}

        try:
            config = json.loads(CONFIG_PATH.read_text())
            kg = KnowledgeGraph("projects")
        except Exception as e:
            return {"error": str(e)}

        projects = config.get("projects", {})
        added = 0
        updated = 0

        for name, info in projects.items():
            existing = kg.get_entity(name)

            content = f"Description: {info.get('description', 'N/A')}\n"
            content += f"Focus: {info.get('focus', 'N/A')}\n"
            content += f"Path: {info.get('path', f'/home/{name}')}"

            kg.add_entity(
                name=name,
                entity_type="project",
                content=content,
                metadata=info,
                source="config.json"
            )

            if existing:
                updated += 1
            else:
                added += 1

        return {
            "added": added,
            "updated": updated,
            "total": len(projects),
        }


def run_qa(sync: bool = False, verbose: bool = False) -> int:
    """Run QA validation and optionally sync."""
    validator = QAValidator()

    print("\n=== Luzia QA Validation ===\n")

    results = validator.validate_all()

    # Show results
    for category, passed in results["results"].items():
        status = "[OK]" if passed else "[FAIL]"
        print(f"  {status} {category}")

    # Show issues
    if results["issues"]:
        print("\nErrors:")
        for issue in results["issues"]:
            print(f"  [!] {issue['category']}: {issue['message']}")
            if verbose and issue.get("details"):
                print(f"      {issue['details']}")

    if results["warnings"]:
        print("\nWarnings:")
        for warn in results["warnings"]:
            print(f"  [?] {warn['category']}: {warn['message']}")
            if verbose and warn.get("details"):
                print(f"      {warn['details']}")

    if verbose and results["info"]:
        print("\nInfo:")
        for info in results["info"]:
            print(f"  [i] {info['category']}: {info['message']}")

    print(f"\nSummary: {results['summary']['errors']} errors, {results['summary']['warnings']} warnings")

    # Sync if requested
    if sync:
        print("\n--- Syncing to Knowledge Graph ---")

        route_result = validator.sync_routes_to_kg()
        if "error" in route_result:
            print(f"  Routes: Error - {route_result['error']}")
        else:
            print(f"  Routes: {route_result['added']} added, {route_result['updated']} updated")

        project_result = validator.sync_projects_to_kg()
        if "error" in project_result:
            print(f"  Projects: Error - {project_result['error']}")
        else:
            print(f"  Projects: {project_result['added']} added, {project_result['updated']} updated")

    return 0 if results["passed"] else 1


# --- CLI ---

if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description="Luzia QA Validator")
    parser.add_argument("--sync", action="store_true", help="Sync code to KG")
    parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
    parser.add_argument("--json", action="store_true", help="Output as JSON")
    parser.add_argument("--learn", action="store_true", help="Extract learnings on QA pass")

    args = parser.parse_args()

    if args.learn:
        # Use learning integration for QA + learning extraction
        from qa_learning_integration import run_integrated_qa
        exit(run_integrated_qa(verbose=args.verbose, sync=args.sync))
    elif args.json:
        validator = QAValidator()
        results = validator.validate_all()
        print(json.dumps(results, indent=2))
    else:
        exit(run_qa(sync=args.sync, verbose=args.verbose))
