"""
Brynq Task Granularity Engine (Phase 75)
Auto-breaks large tasks into atomic subtasks using keyword extraction and
pattern matching. Tracks subtask completion and auto-completes parents.

Storage:
  state/broker/subtasks/{parent_id}.json
"""

import json
import re
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional

from .server import BROKER_DIR, _write_json_sync, _read_jsonl_sync, _append_jsonl_sync

SUBTASKS_DIR = BROKER_DIR / "subtasks"

# Technical keywords that add to complexity
TECHNICAL_KEYWORDS = {
    "api", "database", "migration", "auth", "authentication", "authorization",
    "deploy", "deployment", "test", "testing", "refactor", "security",
    "performance", "optimization", "cache", "caching", "queue", "async",
    "concurrency", "distributed", "microservice", "integration", "webhook",
    "encryption", "ssl", "tls", "oauth", "jwt", "graphql", "rest",
    "websocket", "docker", "kubernetes", "ci", "cd", "pipeline",
    "monitoring", "logging", "backup", "recovery", "scaling",
}


def _ensure_dirs():
    SUBTASKS_DIR.mkdir(parents=True, exist_ok=True)


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def estimate_complexity(description: str) -> int:
    """Score task complexity 1-10 based on description length, number of
    requirements, and technical keywords.

    Returns:
        Integer complexity score from 1 to 10.
    """
    if not description or not description.strip():
        return 1

    score = 0.0

    # Length factor (0-3 points)
    word_count = len(description.split())
    if word_count > 200:
        score += 3
    elif word_count > 100:
        score += 2
    elif word_count > 30:
        score += 1

    # Requirement count (0-3 points) — look for numbered items, bullets, "and" conjunctions
    numbered = len(re.findall(r"(?m)^\s*\d+[\.\)]\s", description))
    bullets = len(re.findall(r"(?m)^\s*[-*]\s", description))
    and_count = len(re.findall(r"\band\b", description.lower()))
    requirement_indicators = numbered + bullets + (and_count // 2)
    if requirement_indicators >= 8:
        score += 3
    elif requirement_indicators >= 4:
        score += 2
    elif requirement_indicators >= 2:
        score += 1

    # Technical keyword density (0-4 points)
    desc_lower = description.lower()
    keyword_hits = sum(1 for kw in TECHNICAL_KEYWORDS if kw in desc_lower)
    if keyword_hits >= 8:
        score += 4
    elif keyword_hits >= 5:
        score += 3
    elif keyword_hits >= 3:
        score += 2
    elif keyword_hits >= 1:
        score += 1

    return max(1, min(10, int(round(score))))


def suggest_decomposition(description: str) -> List[str]:
    """Return suggested subtask titles without creating them.

    Uses pattern matching to extract subtasks from:
    - Numbered lists ("1. Do X", "2. Do Y")
    - Bullet lists ("- Do X", "* Do Y")
    - "and" conjunctions in requirements
    - Sentence boundaries with action verbs
    """
    if not description or not description.strip():
        return []

    suggestions: List[str] = []

    # Pattern 1: Numbered lists
    numbered = re.findall(r"(?m)^\s*\d+[\.\)]\s*(.+)", description)
    if numbered:
        suggestions.extend(item.strip().rstrip(".") for item in numbered)

    # Pattern 2: Bullet lists
    bullets = re.findall(r"(?m)^\s*[-*]\s+(.+)", description)
    if bullets:
        suggestions.extend(item.strip().rstrip(".") for item in bullets)

    # Pattern 3: If no lists found, try splitting on "and" conjunctions
    # in sentences with action verbs
    if not suggestions:
        action_verbs = r"(?:create|build|implement|add|write|set up|configure|design|test|deploy|fix|update|migrate|refactor)"
        # Look for patterns like "implement X and Y"
        and_splits = re.findall(
            rf"(?i){action_verbs}\s+(.+?)(?:\s+and\s+(.+?))?(?:\.|$)",
            description,
        )
        for groups in and_splits:
            for item in groups:
                item = item.strip().rstrip(".")
                if item and len(item) > 3:
                    suggestions.append(item)

    # Pattern 4: If still nothing, split by sentences with action verbs
    if not suggestions:
        sentences = re.split(r"[.!]\s+", description)
        action_pattern = r"(?i)^(?:create|build|implement|add|write|set up|configure|design|test|deploy|fix|update|migrate|refactor)\b"
        for sentence in sentences:
            sentence = sentence.strip().rstrip(".")
            if re.match(action_pattern, sentence) and len(sentence) > 5:
                suggestions.append(sentence)

    # Deduplicate while preserving order
    seen = set()
    unique = []
    for s in suggestions:
        key = s.lower()
        if key not in seen:
            seen.add(key)
            unique.append(s)

    return unique


def decompose_task(title: str, description: str,
                   max_subtasks: int = 10) -> List[Dict[str, Any]]:
    """Analyze a task description and break it into subtasks.

    Returns:
        List of subtask dicts with title, description, order.
    """
    suggestions = suggest_decomposition(description)

    if not suggestions:
        # If no decomposition found, return a single subtask matching the parent
        return [{
            "title": title,
            "description": description,
            "order": 1,
        }]

    subtasks = []
    for i, suggestion in enumerate(suggestions[:max_subtasks], 1):
        subtasks.append({
            "title": suggestion,
            "description": f"Subtask {i} of '{title}': {suggestion}",
            "order": i,
        })

    return subtasks


def create_subtasks(parent_task_id: str,
                    subtasks: List[Dict[str, Any]]) -> Dict[str, Any]:
    """Create linked subtasks from a decomposition.

    Args:
        parent_task_id: The parent task's ID.
        subtasks: List of dicts with at least 'title'.

    Returns:
        Dict with parent_task_id and list of created subtask records.
    """
    _ensure_dirs()
    path = SUBTASKS_DIR / f"{parent_task_id}.json"

    created = []
    for i, st in enumerate(subtasks, 1):
        subtask_id = f"{parent_task_id}-sub-{i}"
        record = {
            "subtask_id": subtask_id,
            "parent_task_id": parent_task_id,
            "title": st.get("title", f"Subtask {i}"),
            "description": st.get("description", ""),
            "order": st.get("order", i),
            "status": "pending",
            "created_at": _now_iso(),
            "completed_at": None,
        }
        created.append(record)

    data = {
        "parent_task_id": parent_task_id,
        "subtasks": created,
        "created_at": _now_iso(),
        "updated_at": _now_iso(),
    }
    _write_json_sync(path, data)
    return data


def get_subtasks(parent_task_id: str) -> List[Dict[str, Any]]:
    """List subtasks of a parent task."""
    _ensure_dirs()
    path = SUBTASKS_DIR / f"{parent_task_id}.json"
    if not path.exists():
        return []
    try:
        data = json.loads(path.read_text("utf-8"))
        return data.get("subtasks", [])
    except (json.JSONDecodeError, OSError):
        return []


def complete_subtask(subtask_id: str) -> Optional[Dict[str, Any]]:
    """Mark a subtask as done. If all subtasks are done, auto-complete parent.

    Args:
        subtask_id: The subtask ID (format: {parent_id}-sub-{n}).

    Returns:
        Updated subtask record, or None if not found.
    """
    _ensure_dirs()

    # Extract parent ID from subtask ID
    parts = subtask_id.rsplit("-sub-", 1)
    if len(parts) != 2:
        return None
    parent_id = parts[0]

    path = SUBTASKS_DIR / f"{parent_id}.json"
    if not path.exists():
        return None

    try:
        data = json.loads(path.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return None

    subtasks = data.get("subtasks", [])
    found = None
    for st in subtasks:
        if st.get("subtask_id") == subtask_id:
            st["status"] = "completed"
            st["completed_at"] = _now_iso()
            found = st
            break

    if found is None:
        return None

    # Check if all subtasks are complete
    all_done = all(st.get("status") == "completed" for st in subtasks)
    if all_done:
        data["all_complete"] = True
        data["completed_at"] = _now_iso()

        # Try to update parent task status
        tasks_dir = BROKER_DIR / "tasks"
        parent_task_path = tasks_dir / f"{parent_id}.json"
        if parent_task_path.exists():
            try:
                parent_task = json.loads(parent_task_path.read_text("utf-8"))
                parent_task["status"] = "completed"
                parent_task["completed_at"] = _now_iso()
                _write_json_sync(parent_task_path, parent_task)
            except (json.JSONDecodeError, OSError):
                pass

    data["updated_at"] = _now_iso()
    _write_json_sync(path, data)
    return found


def get_completion_progress(parent_task_id: str) -> Dict[str, Any]:
    """Returns completion progress based on subtask status.

    Returns:
        Dict with total, completed, pending, percent_complete.
    """
    subtasks = get_subtasks(parent_task_id)
    if not subtasks:
        return {"total": 0, "completed": 0, "pending": 0, "percent_complete": 0.0}

    completed = sum(1 for st in subtasks if st.get("status") == "completed")
    total = len(subtasks)
    pending = total - completed
    pct = round((completed / total) * 100, 1) if total > 0 else 0.0

    return {
        "total": total,
        "completed": completed,
        "pending": pending,
        "percent_complete": pct,
    }
