"""
Fallback analyzer — generates a basic HLD analysis without an LLM,
using only the code graph structure.

Extracted from LLMClient to respect the Single Responsibility Principle.
"""

from __future__ import annotations

import logging
from pathlib import PurePosixPath

from hld_generator.config import HLDConfig
from hld_generator.graph import CodeGraph
from hld_generator.llm import LLMAnalysis

logger = logging.getLogger(__name__)


class FallbackAnalyzer:
    """Generate a basic analysis from graph data only (no LLM)."""

    def __init__(self, config: HLDConfig) -> None:
        self.config = config

    def analyse(self, cg: CodeGraph) -> LLMAnalysis:
        """Generate a basic analysis without LLM using graph data only."""
        analysis = LLMAnalysis()
        analysis.project_name = "Codebase Analysis"
        analysis.summary = (
            f"A {'/'.join(dict(cg.languages).keys())} project with "
            f"{len(cg.modules)} modules across {len(cg.packages)} packages."
        )
        analysis.tech_stack = {"languages": list(cg.languages.keys())}

        # Create basic components from packages
        for pkg, members in cg.packages.items():
            all_classes = []
            all_endpoints = []
            for mid in members:
                mod = cg.modules[mid]
                all_classes.extend(mod.classes)
                all_endpoints.extend(mod.endpoints)

            layer = self.guess_layer(pkg, all_endpoints)
            analysis.components.append({
                "name": pkg,
                "purpose": f"Contains {len(members)} module(s)"
                           + (f" with classes: {', '.join(all_classes[:5])}" if all_classes else ""),
                "key_modules": members[:10],
                "layer": layer,
            })

        # Generate a basic mermaid diagram
        analysis.mermaid_diagram = self.fallback_mermaid(cg, self.config.max_diagram_edges)

        return analysis

    @staticmethod
    def guess_layer(pkg: str, endpoints: list[str]) -> str:
        """Guess the architectural layer of a package from naming conventions.

        Uses the **leaf directory name** for keyword matching to avoid false
        positives (e.g. ``python_api/models`` should match "models" → Data
        Access, not "api" → API).
        """
        pkg_lower = pkg.lower()
        # Use the leaf directory name for targeted matching so that
        # parent dir names like "python_api" don't poison sub-packages.
        segments = [s for s in pkg_lower.replace("\\", "/").split("/") if s]
        leaf = segments[-1] if segments else pkg_lower

        if endpoints:
            return "API"
        # Go: cmd/ directories are entry points (check all segments, not just leaf)
        if "cmd" in segments:
            return "Entry Point"
        if leaf in ("route", "routes", "controller", "controllers", "handler",
                     "handlers", "api", "view", "views", "endpoint", "endpoints"):
            return "API"
        if leaf in ("server", "transport", "grpc", "http", "middleware"):
            return "API"
        if leaf in ("model", "models", "schema", "schemas", "entity", "entities",
                     "repo", "repository", "database", "db", "migration", "migrations"):
            return "Data Access"
        if leaf in ("service", "services", "usecase", "usecases", "domain",
                     "core", "logic"):
            return "Business Logic"
        # Split compound leaf names (e.g. "shared_lib" → {"shared", "lib"})
        leaf_parts = set(leaf.split("_"))
        if leaf_parts & {"util", "utils", "helper", "helpers", "common",
                         "shared", "lib", "pkg"}:
            return "Shared/Utils"
        if leaf in ("config", "configs", "setting", "settings", "env"):
            return "Config"
        if leaf in ("test", "tests", "spec", "specs", "fixture", "fixtures",
                     "__tests__"):
            return "Testing"
        if leaf in ("ui", "component", "components", "page", "pages",
                     "template", "templates", "static", "public"):
            return "UI"
        if leaf in ("infra", "infrastructure", "deploy", "deployment",
                     "docker", "k8s", "terraform", "ci"):
            return "Infrastructure"
        if leaf in ("generator", "output", "render", "cli"):
            return "Business Logic"
        # Fallback: check full path for broader matches (hooks, frontend, etc.)
        if any(k in pkg_lower for k in ("component", "hook", "frontend")):
            return "UI"
        return "Business Logic"

    @staticmethod
    def safe_mermaid_id(raw: str) -> str:
        """Make a string safe for use as a Mermaid node ID.

        Mermaid IDs must start with a letter, so prefix with 'n_' if needed.
        """
        safe = raw.replace("/", "_").replace(".", "_").replace("-", "_")
        safe = safe.replace("(", "").replace(")", "").replace(" ", "_")
        if not safe or not safe[0].isalpha():
            safe = f"n_{safe}"
        return safe

    @staticmethod
    def safe_mermaid_label(raw: str) -> str:
        """Escape characters that break Mermaid label syntax."""
        return raw.replace('"', '&quot;').replace(']', '&#93;')

    @staticmethod
    def fallback_mermaid(cg: CodeGraph, max_edges: int = 60) -> str:
        """Generate a simple mermaid diagram from the dependency graph."""
        lines = ["graph TD"]

        if not cg.packages:
            lines.append('    empty["No modules found"]')
            return "\n".join(lines)

        # Add nodes per package
        for pkg, members in sorted(cg.packages.items()):
            safe_pkg = FallbackAnalyzer.safe_mermaid_id(pkg)
            pkg_label = FallbackAnalyzer.safe_mermaid_label(pkg)
            lines.append(f'    subgraph {safe_pkg}["{pkg_label}"]')
            for mid in members[:8]:
                name = PurePosixPath(mid).stem
                safe_id = FallbackAnalyzer.safe_mermaid_id(mid)
                safe_name = FallbackAnalyzer.safe_mermaid_label(name)
                lines.append(f'        {safe_id}["{safe_name}"]')
            if len(members) > 8:
                lines.append(f'        {safe_pkg}_more["... +{len(members) - 8} more"]')
            lines.append("    end")

        # Add edges (configurable limit)
        for src, dst in list(cg.graph.edges)[:max_edges]:
            safe_src = FallbackAnalyzer.safe_mermaid_id(src)
            safe_dst = FallbackAnalyzer.safe_mermaid_id(dst)
            lines.append(f"    {safe_src} --> {safe_dst}")

        return "\n".join(lines)
