"""
Regex-based fallback parser.

Used when tree-sitter grammars are not available for a language, or when
tree-sitter fails to install.  Covers the most common patterns across
popular languages.
"""

from __future__ import annotations

import bisect
import re
import logging
from pathlib import Path

from hld_generator.parsers.base import (
    EntityType,
    ImportInfo,
    ParsedEntity,
    ParsedFile,
    extract_summary,
)

logger = logging.getLogger(__name__)

# ── Regex patterns per language family ─────────────────────────────────────────

_PATTERNS: dict[str, dict[str, list[re.Pattern[str]]]] = {
    "python": {
        "class": [re.compile(r"^class\s+(\w+)\s*[\(:]", re.MULTILINE)],
        "function": [re.compile(r"^[ \t]*def\s+(\w+)\s*\(", re.MULTILINE)],
        "import": [
            re.compile(r"^from\s+([\w.]+)\s+import\s+(.+)", re.MULTILINE),
            re.compile(r"^import\s+([\w., ]+)", re.MULTILINE),
        ],
        "decorator": [re.compile(r"^@(\w[\w.]*)", re.MULTILINE)],
    },
    "javascript": {
        "class": [re.compile(r"\bclass\s+(\w+)", re.MULTILINE)],
        "function": [
            re.compile(r"\bfunction\s+(\w+)\s*\(", re.MULTILINE),
            re.compile(r"(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\(", re.MULTILINE),
            re.compile(r"(\w+)\s*:\s*(?:async\s*)?\(", re.MULTILINE),
        ],
        "import": [
            re.compile(r"""import\s+.*?\s+from\s+['"]([^'"]+)['"]""", re.MULTILINE),
            re.compile(r"""require\s*\(\s*['"]([^'"]+)['"]\s*\)""", re.MULTILINE),
        ],
        "export": [
            re.compile(r"export\s+(?:default\s+)?(?:class|function|const|let|var)\s+(\w+)", re.MULTILINE),
        ],
    },
    "typescript": {
        "class": [re.compile(r"\bclass\s+(\w+)", re.MULTILINE)],
        "function": [
            re.compile(r"\bfunction\s+(\w+)\s*[\(<]", re.MULTILINE),
            re.compile(r"(?:const|let|var)\s+(\w+)\s*(?::\s*\w+)?\s*=\s*(?:async\s*)?\(", re.MULTILINE),
        ],
        "import": [
            re.compile(r"""import\s+.*?\s+from\s+['"]([^'"]+)['"]""", re.MULTILINE),
        ],
        "interface": [re.compile(r"\binterface\s+(\w+)", re.MULTILINE)],
        "export": [
            re.compile(r"export\s+(?:default\s+)?(?:class|function|const|let|var|interface|type|enum)\s+(\w+)", re.MULTILINE),
        ],
    },
    "java": {
        "class": [
            re.compile(r"\b(?:public|private|protected)?\s*(?:abstract|final)?\s*class\s+(\w+)", re.MULTILINE),
            re.compile(r"\binterface\s+(\w+)", re.MULTILINE),
        ],
        "function": [
            re.compile(
                r"\b(?:public|private|protected)\s+(?:static\s+)?(?:[\w<>\[\]]+)\s+(\w+)\s*\(",
                re.MULTILINE,
            )
        ],
        "import": [re.compile(r"^import\s+([\w.*]+)\s*;", re.MULTILINE)],
    },
    "go": {
        "class": [re.compile(r"type\s+(\w+)\s+struct\b", re.MULTILINE)],
        "function": [
            re.compile(r"func\s+(\w+)\s*\(", re.MULTILINE),
            re.compile(r"func\s+\(\w+\s+\*?\w+\)\s+(\w+)\s*\(", re.MULTILINE),
        ],
        "import": [
            re.compile(r"""^import\s+"([^"]+)""", re.MULTILINE),
            re.compile(r"""^\s+"([^"]+)"$""", re.MULTILINE),  # inside import() block (require line to end with quote)
        ],
        "interface": [re.compile(r"type\s+(\w+)\s+interface\b", re.MULTILINE)],
    },
    "rust": {
        "class": [
            re.compile(r"\bstruct\s+(\w+)", re.MULTILINE),
            re.compile(r"\benum\s+(\w+)", re.MULTILINE),
            re.compile(r"\btrait\s+(\w+)", re.MULTILINE),
        ],
        "function": [re.compile(r"\bfn\s+(\w+)\s*[\(<]", re.MULTILINE)],
        "import": [re.compile(r"\buse\s+([\w:]+)", re.MULTILINE)],
    },
    "c": {
        "class": [re.compile(r"typedef\s+struct\s+\w*\s*\{[^}]*\}\s*(\w+)", re.DOTALL)],
        "function": [
            re.compile(r"^[\w*]+\s+(\w+)\s*\([^)]*\)\s*\{", re.MULTILINE),
        ],
        "import": [re.compile(r'#include\s*[<"]([^>"]+)[>"]', re.MULTILINE)],
    },
    "cpp": {
        "class": [
            re.compile(r"\bclass\s+(\w+)", re.MULTILINE),
            re.compile(r"\bstruct\s+(\w+)", re.MULTILINE),
        ],
        "function": [
            re.compile(r"^[\w:*&<>]+\s+(\w[\w:]*)\s*\([^)]*\)\s*(?:const\s*)?\{", re.MULTILINE),
        ],
        "import": [
            re.compile(r'#include\s*[<"]([^>"]+)[>"]', re.MULTILINE),
            re.compile(r"using\s+namespace\s+(\w+)", re.MULTILINE),
        ],
    },
    "ruby": {
        "class": [
            re.compile(r"\bclass\s+(\w+)", re.MULTILINE),
            re.compile(r"\bmodule\s+(\w+)", re.MULTILINE),
        ],
        "function": [re.compile(r"\bdef\s+(\w+[\w?!]*)", re.MULTILINE)],
        "import": [re.compile(r"""require\s+['"]([^'"]+)['"]""", re.MULTILINE)],
    },
    "csharp": {
        "class": [
            re.compile(r"\bclass\s+(\w+)", re.MULTILINE),
            re.compile(r"\binterface\s+(\w+)", re.MULTILINE),
        ],
        "function": [
            re.compile(
                r"\b(?:public|private|protected|internal)\s+(?:static\s+)?(?:async\s+)?[\w<>\[\]]+\s+(\w+)\s*\(",
                re.MULTILINE,
            )
        ],
        "import": [re.compile(r"using\s+([\w.]+)\s*;", re.MULTILINE)],
    },
    "php": {
        "class": [re.compile(r"\bclass\s+(\w+)", re.MULTILINE)],
        "function": [re.compile(r"\bfunction\s+(\w+)\s*\(", re.MULTILINE)],
        "import": [re.compile(r"\buse\s+([\w\\]+)", re.MULTILINE)],
    },
    "kotlin": {
        "class": [
            re.compile(r"\bclass\s+(\w+)", re.MULTILINE),
            re.compile(r"\binterface\s+(\w+)", re.MULTILINE),
            re.compile(r"\bobject\s+(\w+)", re.MULTILINE),
        ],
        "function": [re.compile(r"\bfun\s+(\w+)\s*[\(<]", re.MULTILINE)],
        "import": [re.compile(r"^import\s+([\w.]+)", re.MULTILINE)],
    },
    "swift": {
        "class": [
            re.compile(r"\bclass\s+(\w+)", re.MULTILINE),
            re.compile(r"\bstruct\s+(\w+)", re.MULTILINE),
            re.compile(r"\bprotocol\s+(\w+)", re.MULTILINE),
        ],
        "function": [re.compile(r"\bfunc\s+(\w+)\s*[\(<]", re.MULTILINE)],
        "import": [re.compile(r"^import\s+(\w+)", re.MULTILINE)],
    },
    "scala": {
        "class": [
            re.compile(r"\b(?:case\s+)?class\s+(\w+)", re.MULTILINE),
            re.compile(r"\bobject\s+(\w+)", re.MULTILINE),
            re.compile(r"\btrait\s+(\w+)", re.MULTILINE),
        ],
        "function": [re.compile(r"\bdef\s+(\w+)\s*[\[\(]", re.MULTILINE)],
        "import": [re.compile(r"^import\s+([\w.{},\s]+)", re.MULTILINE)],
    },
}

# Endpoint patterns (framework-specific, applied across languages)
_ENDPOINT_PATTERNS: list[re.Pattern[str]] = [
    # Python: Flask/FastAPI method decorators — @app.get("/path"), @bp.post("/path")
    re.compile(r"""@\w+\.\s*(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]""", re.IGNORECASE),
    # Python: Flask .route() decorator — @app.route("/path", methods=["GET"])
    # Captures (path, methods_str_or_None) — handled specially in _extract_endpoints
    re.compile(r"""@\w+\.route\s*\(\s*['"]([^'"]+)['"](?:.*?methods\s*=\s*\[([^\]]*)\])?"""),
    # Python: Django urls
    re.compile(r"""path\s*\(\s*['"]([^'"]+)['"]"""),
    # JS/TS: Express (require no @ prefix to avoid matching Python decorators)
    re.compile(r"""(?<!@)(?:app|router)\.(get|post|put|delete|patch|all)\s*\(\s*['"]([^'"]+)['"]""", re.IGNORECASE),
    # Java: Spring method-specific mappings — @GetMapping, @PostMapping, etc.
    re.compile(r"""@(Get|Post|Put|Delete|Patch)Mapping\s*\(\s*(?:value\s*=\s*)?['"]([^'"]+)['"]"""),
    # Java: Spring generic RequestMapping (method unknown)
    re.compile(r"""@RequestMapping\s*\(\s*(?:value\s*=\s*)?['"]([^'"]+)['"]"""),
    # Go: stdlib HTTP router (Handle/HandleFunc only — not .Get which matches Header.Get)
    re.compile(r"""\.(?:Handle|HandleFunc)\s*\(\s*['"]([^'"]+)['"]"""),
    # Go: framework routers (Gin, Chi, Echo) — uppercase method names
    re.compile(r"""\.(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s*\(\s*['"](/[^'"]+)['"]"""),
]


class RegexParser:
    """Fallback parser using regex patterns — works without tree-sitter."""

    @staticmethod
    def _build_line_offsets(source: str) -> list[int]:
        """Build a sorted list of byte offsets where each newline occurs.

        Used by _offset_to_line() to convert a match offset to a line number
        in O(log n) instead of O(n).
        """
        offsets = []
        idx = -1
        while True:
            idx = source.find("\n", idx + 1)
            if idx == -1:
                break
            offsets.append(idx)
        return offsets

    @staticmethod
    def _offset_to_line(offsets: list[int], offset: int) -> int:
        """Convert a character offset to a 1-based line number using bisect."""
        return bisect.bisect_right(offsets, offset - 1) + 1

    def parse_file(self, file_path: Path, language: str) -> ParsedFile:
        result = ParsedFile(file_path=str(file_path), language=language)

        try:
            source = file_path.read_text(encoding="utf-8", errors="replace")
            result.line_count = source.count("\n") + 1
        except OSError as exc:
            result.errors.append(f"Cannot read file: {exc}")
            return result

        result.raw_summary = extract_summary(source)

        # Pre-compute newline offsets for O(log n) line lookups
        line_offsets = self._build_line_offsets(source)

        patterns = _PATTERNS.get(language, {})
        if not patterns:
            # Try generic extraction for unknown languages
            patterns = self._guess_patterns(source)

        # Pre-split lines once for O(1) line lookups in _extract_functions
        source_lines = source.split("\n")

        self._extract_classes(result, source, patterns, line_offsets)
        self._extract_functions(result, source, patterns, line_offsets, source_lines)
        self._extract_imports(result, source, patterns)
        self._extract_exports(result, source, patterns)
        self._extract_interfaces(result, source, patterns, line_offsets)
        self._extract_endpoints(result, source, line_offsets)
        self._detect_main_guard(result, source)

        return result

    # ── extraction helpers ─────────────────────────────────────────────────

    def _extract_classes(
        self, result: ParsedFile, source: str, patterns: dict,
        line_offsets: list[int] | None = None,
    ) -> None:
        for pat in patterns.get("class", []):
            for m in pat.finditer(source):
                line = self._offset_to_line(line_offsets, m.start()) if line_offsets else source[: m.start()].count("\n") + 1
                result.entities.append(
                    ParsedEntity(
                        name=m.group(1),
                        entity_type=EntityType.CLASS,
                        line_start=line,
                        line_end=line,
                    )
                )

    def _extract_functions(
        self, result: ParsedFile, source: str, patterns: dict,
        line_offsets: list[int] | None = None,
        source_lines: list[str] | None = None,
    ) -> None:
        for pat in patterns.get("function", []):
            for m in pat.finditer(source):
                name = m.group(1)
                line = self._offset_to_line(line_offsets, m.start()) if line_offsets else source[: m.start()].count("\n") + 1
                # Rough heuristic: indented → method
                line_text = source_lines[line - 1] if source_lines and line <= len(source_lines) else ""
                indent = len(line_text) - len(line_text.lstrip())
                parent = None
                etype = EntityType.FUNCTION
                if indent > 0:
                    # Find nearest class above this line
                    for ent in reversed(result.entities):
                        if ent.entity_type == EntityType.CLASS and ent.line_start < line:
                            parent = ent.name
                            etype = EntityType.METHOD
                            break
                result.entities.append(
                    ParsedEntity(
                        name=name,
                        entity_type=etype,
                        line_start=line,
                        line_end=line,
                        parent=parent,
                    )
                )

    def _extract_imports(
        self, result: ParsedFile, source: str, patterns: dict
    ) -> None:
        for pat in patterns.get("import", []):
            for m in pat.finditer(source):
                groups = m.groups()
                module = groups[0].strip()
                names = []
                if len(groups) > 1 and groups[1]:
                    names = [n.strip() for n in groups[1].split(",")]
                result.imports.append(
                    ImportInfo(
                        module=module,
                        names=names,
                        is_relative=module.startswith("."),
                    )
                )

    def _extract_exports(
        self, result: ParsedFile, source: str, patterns: dict
    ) -> None:
        for pat in patterns.get("export", []):
            for m in pat.finditer(source):
                result.exports.append(m.group(1))

    def _extract_interfaces(
        self, result: ParsedFile, source: str, patterns: dict,
        line_offsets: list[int] | None = None,
    ) -> None:
        for pat in patterns.get("interface", []):
            for m in pat.finditer(source):
                line = self._offset_to_line(line_offsets, m.start()) if line_offsets else source[: m.start()].count("\n") + 1
                result.entities.append(
                    ParsedEntity(
                        name=m.group(1),
                        entity_type=EntityType.INTERFACE,
                        line_start=line,
                        line_end=line,
                    )
                )

    def _extract_endpoints(self, result: ParsedFile, source: str, line_offsets: list[int] | None = None) -> None:
        # Built-in patterns + plugin-registered patterns
        all_patterns = list(_ENDPOINT_PATTERNS)
        try:
            from hld_generator.plugins import registry
            for pat, _name in registry.custom_endpoint_patterns:
                all_patterns.append(pat)
        except ImportError:
            pass

        for pat in all_patterns:
            for m in pat.finditer(source):
                groups = m.groups()
                line = self._offset_to_line(line_offsets, m.start()) if line_offsets else source[: m.start()].count("\n") + 1
                # Flask .route() pattern: (path, methods_str_or_None)
                # Detect by checking if the match starts with @…route
                if ".route" in m.group(0):
                    route = groups[0]
                    methods_str = groups[1] if len(groups) > 1 and groups[1] else None
                    if methods_str:
                        # Parse methods=["GET", "POST"] → individual endpoints
                        for meth in re.findall(r"[A-Z]+", methods_str.upper()):
                            result.entities.append(
                                ParsedEntity(
                                    name=f"{meth} {route}",
                                    entity_type=EntityType.ENDPOINT,
                                    line_start=line,
                                    line_end=line,
                                )
                            )
                    else:
                        result.entities.append(
                            ParsedEntity(
                                name=f"ANY {route}",
                                entity_type=EntityType.ENDPOINT,
                                line_start=line,
                                line_end=line,
                            )
                        )
                    continue

                # Standard patterns: (method, path) or just (path,)
                route = groups[-1]
                method = groups[0].upper() if len(groups) > 1 else "ANY"
                result.entities.append(
                    ParsedEntity(
                        name=f"{method} {route}",
                        entity_type=EntityType.ENDPOINT,
                        line_start=line,
                        line_end=line,
                    )
                )

    @staticmethod
    def _detect_main_guard(result: ParsedFile, source: str) -> None:
        """Detect Python's ``if __name__ == '__main__':`` and add a synthetic main function.

        This ensures the file is recognised as an entry point by the graph analyser.
        """
        if result.language != "python":
            return
        if not re.search(r'''if\s+__name__\s*==\s*['"]__main__['"]''', source):
            return
        # Only add if 'main' not already in the extracted functions
        func_names = {
            e.name for e in result.entities
            if e.entity_type in (EntityType.FUNCTION, EntityType.METHOD)
        }
        if "main" not in func_names:
            result.entities.append(
                ParsedEntity(
                    name="main",
                    entity_type=EntityType.FUNCTION,
                    line_start=0,
                    line_end=0,
                )
            )

    @staticmethod
    def _guess_patterns(source: str) -> dict[str, list[re.Pattern[str]]]:
        """Very basic generic extraction for unrecognised languages."""
        return {
            "class": [re.compile(r"\bclass\s+(\w+)", re.MULTILINE)],
            "function": [
                re.compile(r"\b(?:def|func|function|fn)\s+(\w+)\s*[\(\[]", re.MULTILINE)
            ],
            "import": [
                re.compile(r"^(?:import|require|use|include)\s+['\"]?([^\s'\"]+)", re.MULTILINE)
            ],
        }
