#!/usr/bin/env python3
"""
Architectural Pattern Detection (C3.7)

Detects high-level architectural patterns by analyzing multi-file relationships,
directory structures, and framework conventions.

Detected Patterns:
- MVC (Model-View-Controller)
- MVVM (Model-View-ViewModel)
- MVP (Model-View-Presenter)
- Repository Pattern
- Service Layer Pattern
- Layered Architecture (3-tier, N-tier)
- Clean Architecture
- Hexagonal/Ports & Adapters

Credits:
- Architectural pattern definitions from industry standards
- Framework detection based on official documentation
"""

import logging
from collections import defaultdict
from dataclasses import dataclass
from pathlib import Path

logger = logging.getLogger(__name__)


@dataclass
class ArchitecturalPattern:
    """Detected architectural pattern"""

    pattern_name: str  # e.g., "MVC", "MVVM", "Repository"
    confidence: float  # 0.0-1.0
    evidence: list[str]  # List of evidence supporting detection
    components: dict[str, list[str]]  # Component type -> file paths
    framework: str | None = None  # Detected framework (Django, Spring, etc.)
    description: str = ""  # Human-readable description


@dataclass
class ArchitecturalReport:
    """Complete architectural analysis report"""

    patterns: list[ArchitecturalPattern]
    directory_structure: dict[str, int]  # Directory name -> file count
    total_files_analyzed: int
    frameworks_detected: list[str]
    ai_analysis: dict | None = None  # AI enhancement (C3.6 integration)

    def to_dict(self) -> dict:
        """Export to dictionary"""
        return {
            "patterns": [
                {
                    "pattern_name": p.pattern_name,
                    "confidence": p.confidence,
                    "evidence": p.evidence,
                    "components": p.components,
                    "framework": p.framework,
                    "description": p.description,
                }
                for p in self.patterns
            ],
            "directory_structure": self.directory_structure,
            "total_files_analyzed": self.total_files_analyzed,
            "frameworks_detected": self.frameworks_detected,
            "ai_analysis": self.ai_analysis,
        }


class ArchitecturalPatternDetector:
    """
    Detect high-level architectural patterns.

    Analyzes entire codebase structure, not individual files.
    """

    # Common directory patterns for architectures
    MVC_DIRS = {"models", "views", "controllers", "model", "view", "controller"}
    MVVM_DIRS = {"models", "views", "viewmodels", "viewmodel"}
    LAYERED_DIRS = {"presentation", "business", "data", "dal", "bll", "ui"}
    CLEAN_ARCH_DIRS = {"domain", "application", "infrastructure", "presentation"}
    REPO_DIRS = {"repositories", "repository"}
    SERVICE_DIRS = {"services", "service"}

    # Framework detection patterns
    FRAMEWORK_MARKERS = {
        # Game Engines (checked first to avoid false positives)
        "Unity": [
            "Assembly-CSharp.csproj",
            "UnityEngine.dll",
            "ProjectSettings/ProjectVersion.txt",
            ".unity",
            "Library/",
            "UnityEngine",  # Import: using UnityEngine; in C# scripts
            "Packages/manifest.json",  # Unity Package Manager manifest
        ],
        "Unreal": ["Source/", ".uproject", "Config/DefaultEngine.ini", "Binaries/", "Content/"],
        "Godot": ["project.godot", ".godot", ".tscn", ".tres", ".gd"],
        # Web Frameworks
        "Django": ["django", "manage.py", "settings.py", "urls.py"],
        "Flask": ["flask", "app.py", "wsgi.py"],
        "Spring": [
            "springframework",
            "org.springframework",
            "@Controller",
            "@Service",
            "@Repository",
        ],
        "ASP.NET": [
            "Microsoft.AspNetCore",
            "System.Web",
            "Controllers",
            "Models",
            "Views",
            ".cshtml",
            "Startup.cs",
        ],
        "Rails": [
            "rails",
            "action",
            "app/models",
            "app/views",
            "app/controllers",
            "config/routes.rb",
        ],
        "Angular": [
            "@angular",
            "angular",
            "app.module.ts",
            "@Component",
            "@Injectable",
            "angular.json",
        ],
        "React": ["react", "package.json", "components"],
        "Vue.js": ["vue", ".vue", "components"],
        "Express": ["express", "app.js", "routes"],
        "Laravel": ["laravel", "illuminate", "artisan", "app/Http/Controllers", "app/Models"],
    }

    # Web frameworks should only match for web-language projects
    _WEB_FRAMEWORKS = {"React", "Vue.js", "Express", "Angular"}
    _WEB_LANGUAGES = {"JavaScript", "TypeScript", "Python", "PHP", "Ruby"}

    def __init__(self, enhance_with_ai: bool = True, agent: str | None = None):
        """
        Initialize detector.

        Args:
            enhance_with_ai: Enable AI enhancement for detected patterns (C3.6)
            agent: Local CLI agent name (e.g., "kimi", "claude")
        """
        self.enhance_with_ai = enhance_with_ai
        self.ai_enhancer = None

        if self.enhance_with_ai:
            try:
                from skill_seekers.cli.ai_enhancer import AIEnhancer

                self.ai_enhancer = AIEnhancer(agent=agent)
            except Exception as e:
                logger.warning(f"⚠️  Failed to initialize AI enhancer: {e}")
                self.enhance_with_ai = False

    def analyze(self, directory: Path, files_analysis: list[dict]) -> ArchitecturalReport:
        """
        Analyze codebase for architectural patterns.

        Args:
            directory: Root directory of codebase
            files_analysis: List of analyzed files from CodeAnalyzer

        Returns:
            ArchitecturalReport with detected patterns
        """
        logger.info(f"🏗️  Analyzing architectural patterns in {directory}")

        # Build directory structure map
        dir_structure = self._analyze_directory_structure(directory)

        # Detect frameworks
        frameworks = self._detect_frameworks(directory, files_analysis)

        # Detect architectural patterns
        patterns = []

        patterns.extend(self._detect_mvc(dir_structure, files_analysis, frameworks))
        patterns.extend(self._detect_mvvm(dir_structure, files_analysis, frameworks))
        patterns.extend(self._detect_repository(dir_structure, files_analysis))
        patterns.extend(self._detect_service_layer(dir_structure, files_analysis))
        patterns.extend(self._detect_layered_architecture(dir_structure, files_analysis))
        patterns.extend(self._detect_clean_architecture(dir_structure, files_analysis))

        report = ArchitecturalReport(
            patterns=patterns,
            directory_structure=dir_structure,
            total_files_analyzed=len(files_analysis),
            frameworks_detected=frameworks,
        )

        # Enhance with AI if enabled (C3.6)
        if self.enhance_with_ai and self.ai_enhancer and patterns:
            report.ai_analysis = self._enhance_with_ai(report)

        logger.info(f"✅ Detected {len(patterns)} architectural patterns")
        return report

    def _analyze_directory_structure(self, directory: Path) -> dict[str, int]:
        """Analyze directory structure and count files"""
        structure = defaultdict(int)

        for path in directory.rglob("*"):
            if path.is_file():
                # Get relative directory path
                rel_dir = path.parent.relative_to(directory)
                dir_name = str(rel_dir).lower()

                # Extract top-level and leaf directory names
                parts = Path(dir_name).parts
                if parts:
                    structure[parts[0]] += 1  # Top-level dir
                    if len(parts) > 1:
                        structure[parts[-1]] += 1  # Leaf dir

        return dict(structure)

    def _detect_frameworks(self, directory: Path, files: list[dict]) -> list[str]:
        """Detect frameworks being used"""
        detected = []

        # Check file paths from analyzed files
        all_paths = [str(f.get("file", "")) for f in files]
        all_content = " ".join(all_paths)

        # Extract all imports from ALL languages (fixes #239 - extended to multi-language)
        all_imports = []
        for file_data in files:
            if file_data.get("imports"):
                all_imports.extend(file_data["imports"])

        # Create searchable import string
        import_content = " ".join(all_imports)
        logger.debug(
            f"Collected {len(all_imports)} imports from {len([f for f in files if f.get('imports')])} files for framework detection"
        )

        # Also check actual directory structure for game engine markers
        # (project.godot, .unity, .uproject are config files, not in analyzed files)
        dir_files = []
        try:
            # Get all files and directories in the root (non-recursive for performance)
            for item in directory.iterdir():
                dir_files.append(item.name)
        except Exception as e:
            logger.warning(f"Could not scan directory for framework markers: {e}")

        dir_content = " ".join(dir_files)

        # Check game engines FIRST (priority detection)
        for framework in ["Unity", "Unreal", "Godot"]:
            if framework in self.FRAMEWORK_MARKERS:
                markers = self.FRAMEWORK_MARKERS[framework]
                # Check analyzed file paths, directory structure, AND imports
                file_matches = sum(1 for marker in markers if marker.lower() in all_content.lower())
                dir_matches = sum(1 for marker in markers if marker.lower() in dir_content.lower())
                import_matches = sum(
                    1 for marker in markers if marker.lower() in import_content.lower()
                )

                # Import-based detection is high confidence (single match sufficient)
                # Path/directory-based detection requires 2+ matches to avoid false positives
                if import_matches >= 1 or (file_matches + dir_matches) >= 2:
                    detected.append(framework)
                    logger.info(
                        f"  📦 Detected framework: {framework} "
                        f"(imports:{import_matches} path:{file_matches} dir:{dir_matches})"
                    )
                    # Return early to prevent web framework false positives
                    return detected

        # Determine primary language to filter out impossible framework matches
        # e.g., C#/C++ projects should not match React/Vue.js/Express
        lang_counts: dict[str, int] = {}
        for file_data in files:
            lang = file_data.get("language", "")
            if lang:
                lang_counts[lang] = lang_counts.get(lang, 0) + 1
        primary_lang = max(lang_counts, key=lang_counts.get) if lang_counts else ""

        skip_web = primary_lang and primary_lang not in self._WEB_LANGUAGES

        # Check other frameworks (including imports - fixes #239)
        for framework, markers in self.FRAMEWORK_MARKERS.items():
            if framework in ["Unity", "Unreal", "Godot"]:
                continue  # Already checked

            # Skip web frameworks for non-web language projects
            if skip_web and framework in self._WEB_FRAMEWORKS:
                continue

            # Check in file paths, directory structure, AND imports
            path_matches = sum(1 for marker in markers if marker.lower() in all_content.lower())
            dir_matches = sum(1 for marker in markers if marker.lower() in dir_content.lower())
            import_matches = sum(
                1 for marker in markers if marker.lower() in import_content.lower()
            )

            # Strategy: Prioritize import-based detection (more accurate)
            # If we have import matches, they're strong signals - use them alone
            # Otherwise, require 2+ matches from paths/dirs
            if import_matches >= 1:
                # Import-based detection (high confidence)
                detected.append(framework)
                logger.info(f"  📦 Detected framework: {framework} (imports:{import_matches})")
            elif (path_matches + dir_matches) >= 2:
                # Path/directory-based detection (requires 2+ matches)
                detected.append(framework)
                logger.info(
                    f"  📦 Detected framework: {framework} (path:{path_matches} dir:{dir_matches})"
                )

        return detected

    def _detect_mvc(
        self, dirs: dict[str, int], files: list[dict], frameworks: list[str]
    ) -> list[ArchitecturalPattern]:
        """Detect MVC pattern"""
        patterns = []

        # Check for MVC directory structure
        mvc_dir_matches = sum(1 for d in self.MVC_DIRS if d in dirs)
        has_mvc_structure = mvc_dir_matches >= 2

        if not has_mvc_structure:
            return patterns

        # Build evidence
        evidence = []
        components = defaultdict(list)

        # Find MVC files
        for file in files:
            file_path = str(file.get("file", "")).lower()

            if "model" in file_path and ("models/" in file_path or "/model/" in file_path):
                components["Models"].append(file.get("file", ""))
                if len(components["Models"]) == 1:
                    evidence.append("Models directory with model classes")

            if "view" in file_path and ("views/" in file_path or "/view/" in file_path):
                components["Views"].append(file.get("file", ""))
                if len(components["Views"]) == 1:
                    evidence.append("Views directory with view files")

            if "controller" in file_path and (
                "controllers/" in file_path or "/controller/" in file_path
            ):
                components["Controllers"].append(file.get("file", ""))
                if len(components["Controllers"]) == 1:
                    evidence.append("Controllers directory with controller classes")

        # Calculate confidence
        has_models = len(components["Models"]) > 0
        has_views = len(components["Views"]) > 0
        has_controllers = len(components["Controllers"]) > 0

        if sum([has_models, has_views, has_controllers]) >= 2:
            confidence = 0.6 + (sum([has_models, has_views, has_controllers]) * 0.15)

            # Boost confidence if framework detected
            framework = None
            for fw in ["Django", "Flask", "Spring", "ASP.NET", "Rails", "Laravel"]:
                if fw in frameworks:
                    confidence = min(0.95, confidence + 0.1)
                    framework = fw
                    evidence.append(f"{fw} framework detected (uses MVC)")
                    break

            patterns.append(
                ArchitecturalPattern(
                    pattern_name="MVC (Model-View-Controller)",
                    confidence=confidence,
                    evidence=evidence,
                    components=dict(components),
                    framework=framework,
                    description="Separates application into Models (data), Views (UI), and Controllers (logic)",
                )
            )

        return patterns

    def _detect_mvvm(
        self, dirs: dict[str, int], files: list[dict], frameworks: list[str]
    ) -> list[ArchitecturalPattern]:
        """Detect MVVM pattern"""
        patterns = []

        # Look for ViewModels directory or classes ending with ViewModel
        has_viewmodel_dir = "viewmodels" in dirs or "viewmodel" in dirs
        viewmodel_files = [f for f in files if "viewmodel" in str(f.get("file", "")).lower()]

        if not (has_viewmodel_dir or len(viewmodel_files) >= 2):
            return patterns

        evidence = []
        components = defaultdict(list)

        # Find MVVM files
        for file in files:
            file_path = str(file.get("file", "")).lower()
            classes = file.get("classes", [])

            if "model" in file_path and "viewmodel" not in file_path:
                components["Models"].append(file.get("file", ""))

            if "view" in file_path:
                components["Views"].append(file.get("file", ""))

            if "viewmodel" in file_path or any(
                "viewmodel" in c.get("name", "").lower() for c in classes
            ):
                components["ViewModels"].append(file.get("file", ""))

        if len(components["ViewModels"]) >= 2:
            evidence.append(
                f"ViewModels directory with {len(components['ViewModels'])} ViewModel classes"
            )

        if len(components["Views"]) >= 2:
            evidence.append(f"Views directory with {len(components['Views'])} view files")

        if len(components["Models"]) >= 1:
            evidence.append(f"Models directory with {len(components['Models'])} model files")

        # Calculate confidence
        has_models = len(components["Models"]) > 0
        has_views = len(components["Views"]) > 0
        has_viewmodels = len(components["ViewModels"]) >= 2

        if has_viewmodels and (has_models or has_views):
            confidence = 0.7 if (has_models and has_views and has_viewmodels) else 0.6

            framework = None
            for fw in ["ASP.NET", "Angular", "Vue.js"]:
                if fw in frameworks:
                    confidence = min(0.95, confidence + 0.1)
                    framework = fw
                    evidence.append(f"{fw} framework detected (supports MVVM)")
                    break

            patterns.append(
                ArchitecturalPattern(
                    pattern_name="MVVM (Model-View-ViewModel)",
                    confidence=confidence,
                    evidence=evidence,
                    components=dict(components),
                    framework=framework,
                    description="ViewModels provide data-binding between Views and Models",
                )
            )

        return patterns

    def _detect_repository(
        self, dirs: dict[str, int], files: list[dict]
    ) -> list[ArchitecturalPattern]:
        """Detect Repository pattern"""
        patterns = []

        # Look for repositories directory or classes ending with Repository
        has_repo_dir = any(d in dirs for d in self.REPO_DIRS)
        repo_files = [
            f
            for f in files
            if "repository" in str(f.get("file", "")).lower()
            or any("repository" in c.get("name", "").lower() for c in f.get("classes", []))
        ]

        if not (has_repo_dir or len(repo_files) >= 2):
            return patterns

        evidence = []
        components = defaultdict(list)

        for file in repo_files:
            components["Repositories"].append(file.get("file", ""))

        if len(components["Repositories"]) >= 2:
            evidence.append(
                f"Repository pattern: {len(components['Repositories'])} repository classes"
            )
            evidence.append("Repositories abstract data access logic")

            patterns.append(
                ArchitecturalPattern(
                    pattern_name="Repository Pattern",
                    confidence=0.75,
                    evidence=evidence,
                    components=dict(components),
                    description="Encapsulates data access logic in repository classes",
                )
            )

        return patterns

    def _detect_service_layer(
        self, dirs: dict[str, int], files: list[dict]
    ) -> list[ArchitecturalPattern]:
        """Detect Service Layer pattern"""
        patterns = []

        has_service_dir = any(d in dirs for d in self.SERVICE_DIRS)
        service_files = [
            f
            for f in files
            if "service" in str(f.get("file", "")).lower()
            or any("service" in c.get("name", "").lower() for c in f.get("classes", []))
        ]

        if not (has_service_dir or len(service_files) >= 3):
            return patterns

        evidence = []
        components = defaultdict(list)

        for file in service_files:
            components["Services"].append(file.get("file", ""))

        if len(components["Services"]) >= 3:
            evidence.append(f"Service layer: {len(components['Services'])} service classes")
            evidence.append("Services encapsulate business logic")

            patterns.append(
                ArchitecturalPattern(
                    pattern_name="Service Layer Pattern",
                    confidence=0.75,
                    evidence=evidence,
                    components=dict(components),
                    description="Encapsulates business logic in service classes",
                )
            )

        return patterns

    def _detect_layered_architecture(
        self, dirs: dict[str, int], _files: list[dict]
    ) -> list[ArchitecturalPattern]:
        """Detect Layered Architecture (3-tier, N-tier)"""
        patterns = []

        layered_matches = sum(1 for d in self.LAYERED_DIRS if d in dirs)

        if layered_matches < 2:
            return patterns

        evidence = []
        _components = defaultdict(list)
        layers_found = []

        if "presentation" in dirs or "ui" in dirs:
            layers_found.append("Presentation Layer")
            evidence.append("Presentation/UI layer detected")

        if "business" in dirs or "bll" in dirs:
            layers_found.append("Business Logic Layer")
            evidence.append("Business logic layer detected")

        if "data" in dirs or "dal" in dirs:
            layers_found.append("Data Access Layer")
            evidence.append("Data access layer detected")

        if len(layers_found) >= 2:
            confidence = 0.65 + (len(layers_found) * 0.1)

            patterns.append(
                ArchitecturalPattern(
                    pattern_name=f"Layered Architecture ({len(layers_found)}-tier)",
                    confidence=min(confidence, 0.9),
                    evidence=evidence,
                    components={"Layers": layers_found},
                    description=f"Separates concerns into {len(layers_found)} distinct layers",
                )
            )

        return patterns

    def _detect_clean_architecture(
        self, dirs: dict[str, int], _files: list[dict]
    ) -> list[ArchitecturalPattern]:
        """Detect Clean Architecture"""
        patterns = []

        clean_matches = sum(1 for d in self.CLEAN_ARCH_DIRS if d in dirs)

        if clean_matches < 3:
            return patterns

        evidence = []
        components = defaultdict(list)

        if "domain" in dirs:
            evidence.append("Domain layer (core business logic)")
            components["Domain"].append("domain/")

        if "application" in dirs:
            evidence.append("Application layer (use cases)")
            components["Application"].append("application/")

        if "infrastructure" in dirs:
            evidence.append("Infrastructure layer (external dependencies)")
            components["Infrastructure"].append("infrastructure/")

        if "presentation" in dirs:
            evidence.append("Presentation layer (UI/API)")
            components["Presentation"].append("presentation/")

        if len(components) >= 3:
            patterns.append(
                ArchitecturalPattern(
                    pattern_name="Clean Architecture",
                    confidence=0.85,
                    evidence=evidence,
                    components=dict(components),
                    description="Dependency inversion with domain at center, infrastructure at edges",
                )
            )

        return patterns

    def _enhance_with_ai(self, report: ArchitecturalReport) -> dict:
        """Enhance architectural analysis with AI insights"""
        if not self.ai_enhancer:
            return {}

        # Prepare summary for AI
        summary = f"""Detected {len(report.patterns)} architectural patterns:
{chr(10).join(f"- {p.pattern_name} (confidence: {p.confidence:.2f})" for p in report.patterns)}

Frameworks: {", ".join(report.frameworks_detected) if report.frameworks_detected else "None"}
Total files: {report.total_files_analyzed}

Provide brief architectural insights and recommendations."""

        try:
            response = self.ai_enhancer._call_claude(summary, max_tokens=500)
            return {"insights": response} if response else {}
        except Exception as e:
            logger.warning(f"⚠️  AI enhancement failed: {e}")
            return {}
