"""Entity and relation extractor: detects libraries, files, services, etc. from conversations."""

import re
from dataclasses import dataclass
from memotrail.utils import get_logger

logger = get_logger("memotrail.extractors.entities")


@dataclass
class ExtractedEntity:
    """An entity extracted from conversation."""
    name: str
    entity_type: str  # library, file, concept, service, language, framework
    description: str | None = None


@dataclass
class ExtractedRelation:
    """A relation between two entities extracted from conversation."""
    source: str
    target: str
    relation_type: str  # uses, depends_on, replaces, part_of, related_to
    context: str | None = None


# ── Entity extraction patterns ──────────────────────────────

# Library/package patterns
_LIBRARY_PATTERNS = [
    # "installed X", "pip install X", "npm install X"
    re.compile(r"(?:pip|pip3|npm|yarn|pnpm|cargo|go get|gem|composer)\s+(?:install|add|i)\s+([\w@/.-]+)", re.IGNORECASE),
    # "import X", "from X import"
    re.compile(r"(?:from|import)\s+([\w.]+)", re.IGNORECASE),
    # "using X library/package"
    re.compile(r"using\s+([\w.-]+)\s+(?:library|package|module|crate|gem)", re.IGNORECASE),
    # "the X library/package/framework"
    re.compile(r"the\s+([\w.-]+)\s+(?:library|package|framework|sdk)", re.IGNORECASE),
]

# Framework patterns
_FRAMEWORK_PATTERNS = [
    re.compile(r"(?:using|with|chose|switched to)\s+(React|Vue|Angular|Svelte|Next\.?js|Nuxt|Django|Flask|FastAPI|Express|Rails|Spring|Laravel|Remix|Astro)\b", re.IGNORECASE),
    re.compile(r"(React|Vue|Angular|Svelte|Next\.?js|Nuxt|Django|Flask|FastAPI|Express|Rails|Spring|Laravel)\s+(?:app|project|application|server|frontend|backend)", re.IGNORECASE),
]

# Language patterns
_LANGUAGE_PATTERNS = [
    re.compile(r"(?:written in|using|switched to|migrated to)\s+(Python|JavaScript|TypeScript|Rust|Go|Java|Ruby|C\+\+|C#|PHP|Swift|Kotlin)\b", re.IGNORECASE),
    re.compile(r"(Python|JavaScript|TypeScript|Rust|Go|Java|Ruby|C\+\+|C#|PHP|Swift|Kotlin)\s+(?:code|file|module|project|script)", re.IGNORECASE),
]

# Service/tool patterns
_SERVICE_PATTERNS = [
    re.compile(r"(?:using|deployed to|hosted on|connected to|configured)\s+(PostgreSQL|MySQL|MongoDB|Redis|Elasticsearch|Docker|Kubernetes|AWS|GCP|Azure|Vercel|Netlify|Heroku|Supabase|Firebase|S3|CloudFlare|Nginx|Apache)\b", re.IGNORECASE),
    re.compile(r"(PostgreSQL|MySQL|MongoDB|Redis|Elasticsearch|Docker|Kubernetes|AWS|GCP|Azure|Vercel|Netlify|Heroku|Supabase|Firebase|S3|CloudFlare|Nginx|Apache)\s+(?:instance|cluster|server|service|database|bucket|container)", re.IGNORECASE),
]

# File patterns
_FILE_PATTERN = re.compile(
    r"[`'\"]?([\w/.-]+\.(?:py|js|ts|tsx|jsx|go|rs|java|rb|md|yaml|yml|json|toml|sql|html|css|scss|vue|svelte))[`'\"]?"
)

# ── Relation extraction patterns ────────────────────────────

_RELATION_PATTERNS = [
    # "X uses Y", "X depends on Y"
    (re.compile(r"([\w.-]+)\s+(?:uses?|utilizes?|leverages?)\s+([\w.-]+)", re.IGNORECASE), "uses"),
    # "X depends on Y"
    (re.compile(r"([\w.-]+)\s+(?:depends? on|requires?)\s+([\w.-]+)", re.IGNORECASE), "depends_on"),
    # "replaced X with Y", "switched from X to Y"
    (re.compile(r"(?:replaced?|switched?|migrated?)\s+(?:from\s+)?([\w.-]+)\s+(?:with|to)\s+([\w.-]+)", re.IGNORECASE), "replaces"),
    # "X is part of Y"
    (re.compile(r"([\w.-]+)\s+is\s+(?:part of|inside|within|in)\s+([\w.-]+)", re.IGNORECASE), "part_of"),
    # "installed X for Y"
    (re.compile(r"installed?\s+([\w.-]+)\s+for\s+([\w.-]+)", re.IGNORECASE), "uses"),
]

# Known entities for normalization
_KNOWN_ENTITIES: dict[str, str] = {
    "react": "library", "vue": "framework", "angular": "framework",
    "svelte": "framework", "nextjs": "framework", "next.js": "framework",
    "django": "framework", "flask": "framework", "fastapi": "framework",
    "express": "framework", "rails": "framework", "spring": "framework",
    "laravel": "framework", "postgresql": "service", "postgres": "service",
    "mysql": "service", "mongodb": "service", "redis": "service",
    "elasticsearch": "service", "docker": "service", "kubernetes": "service",
    "python": "language", "javascript": "language", "typescript": "language",
    "rust": "language", "go": "language", "java": "language", "ruby": "language",
    "aws": "service", "gcp": "service", "azure": "service",
    "vercel": "service", "netlify": "service", "heroku": "service",
    "supabase": "service", "firebase": "service",
    "chromadb": "library", "sqlite": "service",
    "pytest": "library", "jest": "library", "mocha": "library",
    "webpack": "library", "vite": "library", "esbuild": "library",
    "tailwind": "library", "bootstrap": "library",
}

# Words to skip (too generic to be entities)
_SKIP_WORDS = {
    "the", "this", "that", "from", "with", "using", "import", "file",
    "code", "test", "data", "type", "name", "path", "true", "false",
    "none", "null", "class", "function", "method", "variable",
    "string", "number", "list", "dict", "array", "object",
    "http", "https", "www", "com", "org",
}


def extract_entities(messages: list[dict]) -> list[ExtractedEntity]:
    """Extract entities from conversation messages.

    Uses pattern matching — no LLM API calls needed.

    Args:
        messages: List of {"role": str, "content": str}

    Returns:
        List of extracted entities (deduplicated)
    """
    entities: dict[str, ExtractedEntity] = {}

    all_text = "\n".join(m.get("content", "") for m in messages)

    # Extract libraries
    for pattern in _LIBRARY_PATTERNS:
        for match in pattern.finditer(all_text):
            name = match.group(1).strip().rstrip(".,;:")
            _add_entity(entities, name, "library")

    # Extract frameworks
    for pattern in _FRAMEWORK_PATTERNS:
        for match in pattern.finditer(all_text):
            name = match.group(1).strip()
            _add_entity(entities, name, "framework")

    # Extract languages
    for pattern in _LANGUAGE_PATTERNS:
        for match in pattern.finditer(all_text):
            name = match.group(1).strip()
            _add_entity(entities, name, "language")

    # Extract services
    for pattern in _SERVICE_PATTERNS:
        for match in pattern.finditer(all_text):
            name = match.group(1).strip()
            _add_entity(entities, name, "service")

    # Extract files
    for match in _FILE_PATTERN.finditer(all_text):
        name = match.group(1).strip()
        if not name.startswith("http") and len(name) > 3:
            _add_entity(entities, name, "file")

    result = list(entities.values())
    logger.info(f"Extracted {len(result)} entities")
    return result[:50]  # Cap at 50 per session


def extract_relations(messages: list[dict]) -> list[ExtractedRelation]:
    """Extract relations between entities from conversation messages.

    Uses pattern matching — no LLM API calls needed.

    Args:
        messages: List of {"role": str, "content": str}

    Returns:
        List of extracted relations (deduplicated)
    """
    relations: list[ExtractedRelation] = []
    seen: set[str] = set()

    all_text = "\n".join(m.get("content", "") for m in messages)

    for pattern, relation_type in _RELATION_PATTERNS:
        for match in pattern.finditer(all_text):
            source = match.group(1).strip().rstrip(".,;:")
            target = match.group(2).strip().rstrip(".,;:")

            if _should_skip(source) or _should_skip(target):
                continue

            key = f"{source.lower()}:{relation_type}:{target.lower()}"
            if key in seen:
                continue
            seen.add(key)

            # Get surrounding context
            start = max(0, match.start() - 50)
            end = min(len(all_text), match.end() + 50)
            context = all_text[start:end].strip()

            relations.append(ExtractedRelation(
                source=source,
                target=target,
                relation_type=relation_type,
                context=context,
            ))

    logger.info(f"Extracted {len(relations)} relations")
    return relations[:30]  # Cap at 30 per session


def _add_entity(entities: dict[str, ExtractedEntity], name: str, entity_type: str) -> None:
    """Add an entity to the dictionary, normalizing and deduplicating."""
    if _should_skip(name):
        return

    key = name.lower().strip(".-_")
    if not key or len(key) < 2:
        return

    # Check known entities for better type classification
    known_type = _KNOWN_ENTITIES.get(key)
    if known_type:
        entity_type = known_type

    if key not in entities:
        entities[key] = ExtractedEntity(name=name, entity_type=entity_type)


def _should_skip(name: str) -> bool:
    """Check if a name is too generic to be an entity."""
    lower = name.lower().strip(".-_")
    return lower in _SKIP_WORDS or len(lower) < 2
