"""Suggest dependencies for new tasks based on title and file overlap."""

from __future__ import annotations

import logging

import asyncpg

from loom.validator import normalise, compute_file_overlap

log = logging.getLogger(__name__)


def _title_overlap(tokens_a: list[str], tokens_b: list[str]) -> float:
    """Jaccard similarity on normalized title tokens."""
    if not tokens_a or not tokens_b:
        return 0.0
    set_a = set(tokens_a)
    set_b = set(tokens_b)
    intersection = set_a & set_b
    union = set_a | set_b
    return len(intersection) / len(union) if union else 0.0


async def suggest_dependencies(
    pool: asyncpg.Pool,
    project_id: str,
    title: str,
    context: dict | None = None,
    limit: int = 5,
    threshold: float = 0.2,
) -> list[dict]:
    """Suggest existing tasks that might be dependencies for a new task.

    Scoring heuristics:
    - Title token overlap (Jaccard on normalized tokens) — weight 0.5
    - File overlap (Jaccard on context.files lists) — weight 0.5
    - Only tasks with score > threshold are returned.
    - Done/dead-lettered tasks are excluded.

    Returns:
        List of dicts with keys: task_id, title, score, reason
    """
    # Get candidate tasks (not done, not dead-lettered, not idea)
    async with pool.acquire() as conn:
        rows = await conn.fetch(
            """
            SELECT id, title, context, status FROM tasks
            WHERE project_id = $1::uuid
              AND status NOT IN ('done', 'idea')
              AND (dead_letter IS NULL OR dead_letter = false)
            ORDER BY created_at DESC
            LIMIT 200
            """,
            project_id,
        )

    if not rows:
        return []

    new_tokens = normalise(title)
    new_files: list[str] = []
    if context and isinstance(context, dict):
        files = context.get("files")
        if isinstance(files, list):
            new_files = files

    suggestions: list[dict] = []

    for row in rows:
        task_id = row["id"]
        task_title = row["title"]
        task_context = row["context"]

        # Parse context (may be JSON string or dict)
        task_ctx: dict = {}
        if isinstance(task_context, dict):
            task_ctx = task_context
        elif isinstance(task_context, str):
            import json
            try:
                task_ctx = json.loads(task_context)
            except (json.JSONDecodeError, ValueError):
                pass

        # Score: title overlap
        task_tokens = normalise(task_title)
        title_score = _title_overlap(new_tokens, task_tokens)

        # Score: file overlap
        task_files: list[str] = []
        if isinstance(task_ctx, dict):
            f = task_ctx.get("files")
            if isinstance(f, list):
                task_files = f
        file_score = compute_file_overlap(new_files, task_files)

        # Weighted score
        score = (title_score * 0.5) + (file_score * 0.5)

        if score > threshold:
            # Build reason
            reasons = []
            if title_score > 0:
                reasons.append(f"title overlap: {title_score:.2f}")
            if file_score > 0:
                reasons.append(f"file overlap: {file_score:.2f}")

            suggestions.append({
                "task_id": task_id,
                "title": task_title,
                "score": round(score, 3),
                "reason": ", ".join(reasons),
            })

    # Sort by score descending, limit
    suggestions.sort(key=lambda s: s["score"], reverse=True)
    return suggestions[:limit]
