"""
Utility for parsing @ mentions from Markdown message content and linking resources.

This module parses Markdown content generated by the TipTap editor to extract
mentions of documents, annotations, and users from markdown link syntax [text](url).
It then populates the appropriate relationship fields on ChatMessage instances.

Security: Markdown is safer than HTML (no script injection), and we only parse
internal URLs that match our routing patterns.

Part of Issue #623 - @ Mentions Feature (Extended)
"""

import logging
import re
from urllib.parse import parse_qs, urlparse

from django.contrib.auth import get_user_model
from django.db import transaction

logger = logging.getLogger(__name__)

User = get_user_model()


def parse_mentions_from_content(markdown_content: str) -> dict[str, set[str]]:
    """
    Parse Markdown content for mention links in [text](url) format.

    Extracts resource slugs/IDs from URL patterns:
    - /users/{userSlug} → user
    - /c/{userIdent}/{corpusIdent} → corpus
    - /d/{userIdent}/{docIdent} → document (standalone)
    - /d/{userIdent}/{corpusIdent}/{docIdent} → document (in corpus)
    - /d/...?ann={annotationId} → annotation
    - /agents/{agentSlug} → agent (global)
    - /c/{userIdent}/{corpusIdent}/agents/{agentSlug} → agent (corpus-scoped)

    Args:
        markdown_content: Markdown string from TipTap editor containing mentions

    Returns:
        Dictionary with sets of slugs/IDs for each resource type:
        {
            'users': {slug1, slug2, ...},
            'documents': {slug1, slug2, ...},
            'annotations': {id1, id2, ...},
            'corpuses': {slug1, slug2, ...},
            'agents': {slug1, slug2, ...}
        }

    Example:
        >>> md = '[@john](/users/john-doe) mentioned [@corpus:my-corpus](/c/john-doe/my-corpus)'
        >>> parse_mentions_from_content(md)
        {'users': {'john-doe'}, 'documents': set(), 'annotations': set(), 'corpuses': {'my-corpus'}, 'agents': set()}
    """
    mentioned: dict[str, set[str]] = {
        "users": set(),
        "documents": set(),
        "annotations": set(),
        "corpuses": set(),
        "agents": set(),
    }

    # Regex to find markdown links: [text](url)
    link_pattern = r"\[([^\]]+)\]\(([^)]+)\)"
    matches = re.finditer(link_pattern, markdown_content)

    for match in matches:
        url = match.group(2)

        # Parse URL to extract slugs/IDs
        parsed = urlparse(url)
        path = parsed.path
        query_params = parse_qs(parsed.query)

        # User: /users/{userSlug}
        if path.startswith("/users/"):
            parts = path.split("/")
            if len(parts) >= 3:
                user_slug = parts[2]
                mentioned["users"].add(user_slug)
                logger.debug(f"Found user mention: {user_slug}")

        # Corpus: /c/{userIdent}/{corpusIdent}
        elif path.startswith("/c/"):
            parts = path.split("/")
            if len(parts) >= 4:
                corpus_slug = parts[3]
                mentioned["corpuses"].add(corpus_slug)
                logger.debug(f"Found corpus mention: {corpus_slug}")

        # Document or Annotation: /d/{userIdent}/...
        elif path.startswith("/d/"):
            # Check for annotation query param
            if "ann" in query_params:
                ann_id = query_params["ann"][0]
                mentioned["annotations"].add(ann_id)
                logger.debug(f"Found annotation mention: {ann_id}")
            else:
                # Document: /d/{userIdent}/{corpusIdent}/{docIdent} or /d/{userIdent}/{docIdent}
                parts = path.split("/")
                if len(parts) >= 5:
                    # With corpus: /d/{userIdent}/{corpusIdent}/{docIdent}
                    doc_slug = parts[4]
                    mentioned["documents"].add(doc_slug)
                    logger.debug(f"Found document mention: {doc_slug}")
                elif len(parts) >= 4:
                    # Without corpus: /d/{userIdent}/{docIdent}
                    doc_slug = parts[3]
                    mentioned["documents"].add(doc_slug)
                    logger.debug(f"Found document mention: {doc_slug}")

        # Global Agent: /agents/{agentSlug}
        elif path.startswith("/agents/"):
            parts = path.split("/")
            if len(parts) >= 3:
                agent_slug = parts[2]
                mentioned["agents"].add(agent_slug)
                logger.debug(f"Found global agent mention: {agent_slug}")

    # Also check for corpus-scoped agents in corpus URLs
    # Pattern: /c/{userIdent}/{corpusIdent}/agents/{agentSlug}
    for match in re.finditer(link_pattern, markdown_content):
        url = match.group(2)
        parsed = urlparse(url)
        path = parsed.path

        if path.startswith("/c/") and "/agents/" in path:
            parts = path.split("/")
            # /c/{userIdent}/{corpusIdent}/agents/{agentSlug} → 6 parts
            if len(parts) >= 6 and parts[4] == "agents":
                agent_slug = parts[5]
                mentioned["agents"].add(agent_slug)
                logger.debug(f"Found corpus-scoped agent mention: {agent_slug}")

    logger.debug(
        f"Parsed mentions: {len(mentioned['users'])} users, "
        f"{len(mentioned['documents'])} docs, "
        f"{len(mentioned['annotations'])} annotations, "
        f"{len(mentioned['corpuses'])} corpuses, "
        f"{len(mentioned['agents'])} agents"
    )

    return mentioned


@transaction.atomic
def link_message_to_resources(
    chat_message, mentioned_ids: dict[str, set[str]]
) -> dict[str, int]:
    """
    Populate ChatMessage relationship fields based on parsed mentions.

    This function resolves the slugs/IDs from parse_mentions_from_content() into
    actual model instances and creates the appropriate database relationships.

    Args:
        chat_message: ChatMessage instance to link resources to
        mentioned_ids: Dictionary from parse_mentions_from_content()
                      Contains slugs for documents/corpuses/agents, IDs for annotations

    Returns:
        Dictionary with counts of successfully linked resources:
        {
            'documents_linked': int,
            'annotations_linked': int,
            'users_mentioned': int,  # Currently just counted, not linked
            'corpuses_mentioned': int,  # Currently just counted, not linked
            'agents_linked': int
        }

    Note:
        - Only the FIRST mentioned document is set as source_document (FK field)
        - Documents and corpuses are looked up by slug (from URL paths)
        - ALL mentioned annotations are linked via ManyToMany (by ID from query params)
        - ALL mentioned agents are linked via ManyToMany (by slug from URL paths)
        - Users and corpuses are counted but not currently linked
          (could be used for notifications/analytics in future)
    """
    from opencontractserver.annotations.models import Annotation
    from opencontractserver.documents.models import Document

    result = {
        "documents_linked": 0,
        "annotations_linked": 0,
        "users_mentioned": len(mentioned_ids.get("users", set())),
        "corpuses_mentioned": len(mentioned_ids.get("corpuses", set())),
        "agents_linked": 0,
    }

    # Link document (ForeignKey - choose first if multiple mentioned)
    if mentioned_ids.get("documents"):
        # Extract first document slug from set
        doc_slug = list(mentioned_ids["documents"])[0]

        try:
            # Lookup by slug (extracted from URL path)
            document = Document.objects.get(slug=doc_slug)
            chat_message.source_document = document
            chat_message.save(update_fields=["source_document"])
            result["documents_linked"] = 1
            logger.debug(f"Linked message {chat_message.pk} to document {document.pk}")
        except Document.DoesNotExist:
            logger.warning(
                f"Document with slug '{doc_slug}' not found or not accessible"
            )
        except Exception as e:  # pragma: no cover
            logger.error(f"Error linking document '{doc_slug}' to message: {e}")

    # Link annotations (ManyToMany - link all mentioned)
    if mentioned_ids.get("annotations"):
        try:
            # Filter to only annotations that exist and user can access
            # Note: Annotations inherit permissions from document + corpus
            # so we trust that the user could only mention accessible annotations
            existing_annotations = Annotation.objects.filter(
                pk__in=mentioned_ids["annotations"]
            )

            # Use .set() to replace any existing annotations
            chat_message.source_annotations.set(existing_annotations)
            result["annotations_linked"] = existing_annotations.count()

            logger.debug(
                f"Linked message {chat_message.pk} to "
                f"{result['annotations_linked']} annotations"
            )
        except Exception as e:  # pragma: no cover
            logger.error(f"Error linking annotations to message: {e}")

    # Link agents (ManyToMany - link all mentioned)
    if mentioned_ids.get("agents"):
        from django.db.models import Q

        from opencontractserver.agents.models import AgentConfiguration

        try:
            agent_slugs = list(mentioned_ids["agents"])

            # Get conversation's corpus for scoped agent lookup
            corpus = getattr(chat_message.conversation, "chat_with_corpus", None)

            # SECURITY: Only link agents that the user has permission to access
            # Use visible_to_user() to enforce server-side permission checks
            # (Frontend autocomplete already filters, but we must not trust clients)
            user = chat_message.creator
            base_qs = AgentConfiguration.objects.visible_to_user(user)

            # Find agents by slug - global OR matching corpus (within visible set)
            if corpus:
                agents_qs = base_qs.filter(
                    Q(slug__in=agent_slugs, scope="GLOBAL", is_active=True)
                    | Q(
                        slug__in=agent_slugs,
                        scope="CORPUS",
                        corpus=corpus,
                        is_active=True,
                    )
                )
            else:
                agents_qs = base_qs.filter(
                    slug__in=agent_slugs, scope="GLOBAL", is_active=True
                )

            # Use .set() to replace any existing mentioned agents
            chat_message.mentioned_agents.set(agents_qs)
            result["agents_linked"] = agents_qs.count()

            logger.debug(
                f"Linked message {chat_message.pk} to "
                f"{result['agents_linked']} agents"
            )
        except Exception as e:  # pragma: no cover
            logger.error(f"Error linking agents to message: {e}")

    return result


def extract_mentioned_user_ids(html_content: str) -> set[str]:
    """
    Extract just the user IDs from mentions for notification purposes.

    This is a convenience function for quickly getting user IDs without
    parsing all mention types.

    Args:
        html_content: HTML string from TipTap editor

    Returns:
        Set of user IDs mentioned in the content

    Example:
        >>> html = '<span data-mention-type="user" data-mention-id="456">@john</span>'
        >>> extract_mentioned_user_ids(html)
        {'456'}
    """
    mentioned = parse_mentions_from_content(html_content)
    return mentioned.get("users", set())
