"""Detect project context from a chat completion request.

Detection sources (checked in priority order):
  1. ``X-Working-Directory`` header (IDE sends this)
  2. File paths mentioned in message content
  3. System prompt metadata (project name, stack)
  4. Fallback: empty context
"""

from __future__ import annotations

import re
import subprocess
from pathlib import Path

import structlog

from proxym.context._context import ProjectContext

logger = structlog.get_logger()

# Patterns for extracting file paths from messages
_PATH_RE = re.compile(
    r"(?:^|\s)(/(?:home|Users|root)/\S+)(?:\s|$|:\"|')",
)

# Stack detection by file presence
_STACK_MARKERS: dict[str, list[str]] = {
    "python": ["pyproject.toml", "setup.py", "requirements.txt", "Pipfile"],
    "javascript": ["package.json"],
    "typescript": ["tsconfig.json"],
    "rust": ["Cargo.toml"],
    "go": ["go.mod"],
    "java": ["pom.xml", "build.gradle"],
    "ruby": ["Gemfile"],
    "php": ["composer.json"],
}

# Framework detection by dependency/file
_FRAMEWORK_MARKERS: dict[str, list[str]] = {
    "fastapi": ["fastapi"],
    "django": ["django"],
    "flask": ["flask"],
    "react": ["react"],
    "nextjs": ["next"],
    "vue": ["vue"],
    "svelte": ["svelte"],
}


def detect_project(
    messages: list[dict],
    headers: dict[str, str] | None = None,
) -> ProjectContext:
    """Detect project context from request data.

    Parameters
    ----------
    messages : list[dict]
        OpenAI-format chat messages.
    headers : dict | None
        HTTP request headers (lowercase keys).
    """
    headers = headers or {}
    ctx = ProjectContext()

    # 1. X-Working-Directory header
    work_dir = headers.get("x-working-directory", "")
    if work_dir:
        ctx = _from_directory(work_dir)
        if ctx.project_root:
            return ctx

    # 2. File paths from messages
    paths = _extract_paths(messages)
    if paths:
        root = _find_common_root(paths)
        if root:
            ctx = _from_directory(root)
            if ctx.project_root:
                return ctx

    # 3. System prompt heuristics
    system_text = _extract_system_text(messages)
    if system_text:
        ctx = _from_system_prompt(system_text)

    return ctx


def _from_directory(directory: str) -> ProjectContext:
    """Build context from a directory path by inspecting filesystem."""
    root = Path(directory).expanduser()
    if not root.is_dir():
        return ProjectContext()

    git_remote = _get_git_remote(root)
    project_id = ProjectContext.make_project_id(str(root), git_remote)
    stack = _detect_stack(root)

    return ProjectContext(
        project_id=project_id,
        project_root=str(root),
        project_name=root.name,
        stack=stack,
        git_remote=git_remote,
    )


def _get_git_remote(root: Path) -> str:
    """Get the origin remote URL, empty string if not a git repo."""
    try:
        result = subprocess.run(
            ["git", "remote", "get-url", "origin"],
            cwd=root, capture_output=True, text=True, timeout=3,
        )
        if result.returncode == 0:
            return result.stdout.strip()
    except Exception:
        pass
    return ""


def _detect_stack(root: Path) -> list[str]:
    """Detect programming languages and frameworks from marker files."""
    stack: list[str] = []
    for lang, markers in _STACK_MARKERS.items():
        if any((root / m).exists() for m in markers):
            stack.append(lang)

    stack.extend(_detect_frameworks(root))
    return sorted(set(stack))


def _detect_frameworks(root: Path) -> list[str]:
    """Detect frameworks from config file contents."""
    frameworks: list[str] = []
    for config_file in ["pyproject.toml", "package.json"]:
        cfg = root / config_file
        if not cfg.exists():
            continue
        try:
            content = cfg.read_text(errors="replace").lower()
        except OSError:
            continue
        for fw, keywords in _FRAMEWORK_MARKERS.items():
            if any(kw in content for kw in keywords):
                frameworks.append(fw)
    return frameworks


def _extract_paths(messages: list[dict]) -> list[str]:
    """Extract absolute file/directory paths from message content."""
    paths = []
    for msg in messages:
        content = msg.get("content", "")
        if isinstance(content, str):
            paths.extend(_PATH_RE.findall(content))
        elif isinstance(content, list):
            for block in content:
                if isinstance(block, dict) and block.get("type") == "text":
                    paths.extend(_PATH_RE.findall(block.get("text", "")))
    return paths


def _find_common_root(paths: list[str]) -> str:
    """Find the deepest common directory among a list of paths."""
    if not paths:
        return ""
    dirs = [str(Path(p).parent if not Path(p).is_dir() else Path(p)) for p in paths]
    if len(dirs) == 1:
        return dirs[0]

    parts_list = [Path(d).parts for d in dirs]
    common = []
    for parts in zip(*parts_list):
        if len(set(parts)) == 1:
            common.append(parts[0])
        else:
            break
    return str(Path(*common)) if common else ""


def _extract_system_text(messages: list[dict]) -> str:
    """Extract system message text."""
    for msg in messages:
        if msg.get("role") == "system":
            content = msg.get("content", "")
            return content if isinstance(content, str) else ""
    return ""


_PROJECT_NAME_RE = re.compile(
    r"(?:project|repo|repository|codebase)[:\s]+[\"']?(\w[\w.-]+)[\"']?",
    re.IGNORECASE,
)


def _from_system_prompt(text: str) -> ProjectContext:
    """Extract project hints from system prompt text."""
    match = _PROJECT_NAME_RE.search(text)
    if match:
        name = match.group(1)
        return ProjectContext(
            project_id=ProjectContext.make_project_id(name),
            project_name=name,
        )
    return ProjectContext()
