"""Tests for brynq.task_granularity — Task Granularity Engine (Phase 75)."""

import json
import pytest
from pathlib import Path


@pytest.fixture(autouse=True)
def _isolate_granularity(tmp_path, monkeypatch):
    """Redirect task granularity storage to temp directory."""
    subtasks_dir = tmp_path / "subtasks"
    subtasks_dir.mkdir()
    tasks_dir = tmp_path / "tasks"
    tasks_dir.mkdir()
    monkeypatch.setattr("brynq.task_granularity.SUBTASKS_DIR", subtasks_dir)
    monkeypatch.setattr("brynq.task_granularity.BROKER_DIR", tmp_path)


# ── estimate_complexity ───────────────────────────────────────────────


class TestEstimateComplexity:
    def test_empty_description(self):
        from brynq.task_granularity import estimate_complexity
        assert estimate_complexity("") == 1

    def test_simple_task(self):
        from brynq.task_granularity import estimate_complexity
        score = estimate_complexity("Fix the typo in the readme")
        assert 1 <= score <= 3

    def test_medium_task(self):
        from brynq.task_granularity import estimate_complexity
        desc = "Create a REST API endpoint with authentication and database integration"
        score = estimate_complexity(desc)
        assert score >= 2

    def test_complex_task(self):
        from brynq.task_granularity import estimate_complexity
        desc = (
            "1. Design and implement a distributed microservice architecture\n"
            "2. Set up Kubernetes deployment with CI/CD pipeline\n"
            "3. Implement OAuth authentication and authorization\n"
            "4. Add performance monitoring and logging\n"
            "5. Write comprehensive integration testing\n"
            "6. Configure database migration and backup recovery\n"
            "7. Set up SSL/TLS encryption and security hardening\n"
            "8. Deploy to production with scaling and caching\n"
        )
        score = estimate_complexity(desc)
        assert score >= 7

    def test_score_range(self):
        from brynq.task_granularity import estimate_complexity
        assert 1 <= estimate_complexity("x") <= 10
        long_desc = "implement deploy test refactor " * 100
        assert 1 <= estimate_complexity(long_desc) <= 10

    def test_technical_keywords_increase_score(self):
        from brynq.task_granularity import estimate_complexity
        simple = "Write a document"
        technical = "Deploy the API with Docker, Kubernetes, and set up CI/CD"
        assert estimate_complexity(technical) > estimate_complexity(simple)


# ── suggest_decomposition ─────────────────────────────────────────────


class TestSuggestDecomposition:
    def test_empty_description(self):
        from brynq.task_granularity import suggest_decomposition
        assert suggest_decomposition("") == []

    def test_numbered_list(self):
        from brynq.task_granularity import suggest_decomposition
        desc = "1. Create the database schema\n2. Build the API\n3. Write tests"
        suggestions = suggest_decomposition(desc)
        assert len(suggestions) == 3
        assert "Create the database schema" in suggestions

    def test_bullet_list(self):
        from brynq.task_granularity import suggest_decomposition
        desc = "- Set up auth\n- Build dashboard\n- Deploy to prod"
        suggestions = suggest_decomposition(desc)
        assert len(suggestions) == 3

    def test_asterisk_bullets(self):
        from brynq.task_granularity import suggest_decomposition
        desc = "* First task\n* Second task"
        suggestions = suggest_decomposition(desc)
        assert len(suggestions) == 2

    def test_action_verb_sentences(self):
        from brynq.task_granularity import suggest_decomposition
        desc = "Create the user model. Build the login page. Test the auth flow."
        suggestions = suggest_decomposition(desc)
        assert len(suggestions) >= 2

    def test_deduplication(self):
        from brynq.task_granularity import suggest_decomposition
        desc = "1. Build the API\n- Build the API\n2. Write tests"
        suggestions = suggest_decomposition(desc)
        # Should deduplicate
        api_count = sum(1 for s in suggestions if "Build the API" in s)
        assert api_count == 1

    def test_no_patterns_found(self):
        from brynq.task_granularity import suggest_decomposition
        desc = "Just a plain sentence with no clear subtasks."
        suggestions = suggest_decomposition(desc)
        # May or may not find suggestions, but should not crash
        assert isinstance(suggestions, list)


# ── decompose_task ────────────────────────────────────────────────────


class TestDecomposeTask:
    def test_basic_decomposition(self):
        from brynq.task_granularity import decompose_task
        desc = "1. Design the schema\n2. Implement the API\n3. Write tests"
        subtasks = decompose_task("Build Feature", desc)
        assert len(subtasks) == 3
        assert subtasks[0]["order"] == 1

    def test_max_subtasks(self):
        from brynq.task_granularity import decompose_task
        desc = "\n".join(f"{i}. Task item {i}" for i in range(1, 20))
        subtasks = decompose_task("Big Task", desc, max_subtasks=5)
        assert len(subtasks) <= 5

    def test_no_decomposition_returns_single(self):
        from brynq.task_granularity import decompose_task
        subtasks = decompose_task("Simple", "A simple task with no subtasks.")
        assert len(subtasks) >= 1
        assert subtasks[0]["title"] == "Simple"

    def test_subtask_structure(self):
        from brynq.task_granularity import decompose_task
        desc = "1. Do X\n2. Do Y"
        subtasks = decompose_task("Parent", desc)
        for st in subtasks:
            assert "title" in st
            assert "description" in st
            assert "order" in st


# ── create_subtasks ───────────────────────────────────────────────────


class TestCreateSubtasks:
    def test_creates_file(self):
        from brynq.task_granularity import create_subtasks, SUBTASKS_DIR
        subtasks = [{"title": "Sub 1"}, {"title": "Sub 2"}]
        result = create_subtasks("parent-1", subtasks)
        assert (SUBTASKS_DIR / "parent-1.json").exists()
        assert len(result["subtasks"]) == 2

    def test_subtask_ids(self):
        from brynq.task_granularity import create_subtasks
        subtasks = [{"title": "A"}, {"title": "B"}, {"title": "C"}]
        result = create_subtasks("p1", subtasks)
        ids = [s["subtask_id"] for s in result["subtasks"]]
        assert ids == ["p1-sub-1", "p1-sub-2", "p1-sub-3"]

    def test_initial_status_pending(self):
        from brynq.task_granularity import create_subtasks
        result = create_subtasks("p2", [{"title": "X"}])
        assert result["subtasks"][0]["status"] == "pending"

    def test_parent_task_id_stored(self):
        from brynq.task_granularity import create_subtasks
        result = create_subtasks("p3", [{"title": "X"}])
        assert result["parent_task_id"] == "p3"


# ── get_subtasks ──────────────────────────────────────────────────────


class TestGetSubtasks:
    def test_get_existing(self):
        from brynq.task_granularity import create_subtasks, get_subtasks
        create_subtasks("gs1", [{"title": "A"}, {"title": "B"}])
        subtasks = get_subtasks("gs1")
        assert len(subtasks) == 2

    def test_get_nonexistent_returns_empty(self):
        from brynq.task_granularity import get_subtasks
        assert get_subtasks("no-such-parent") == []


# ── complete_subtask ──────────────────────────────────────────────────


class TestCompleteSubtask:
    def test_complete_one(self):
        from brynq.task_granularity import create_subtasks, complete_subtask, get_subtasks
        create_subtasks("cs1", [{"title": "A"}, {"title": "B"}])
        result = complete_subtask("cs1-sub-1")
        assert result is not None
        assert result["status"] == "completed"
        assert result["completed_at"] is not None

    def test_complete_all_marks_parent(self):
        from brynq.task_granularity import create_subtasks, complete_subtask, SUBTASKS_DIR
        create_subtasks("cs2", [{"title": "A"}, {"title": "B"}])
        complete_subtask("cs2-sub-1")
        complete_subtask("cs2-sub-2")
        data = json.loads((SUBTASKS_DIR / "cs2.json").read_text("utf-8"))
        assert data.get("all_complete") is True

    def test_complete_nonexistent_returns_none(self):
        from brynq.task_granularity import complete_subtask
        assert complete_subtask("no-parent-sub-1") is None

    def test_complete_invalid_format_returns_none(self):
        from brynq.task_granularity import complete_subtask
        assert complete_subtask("bad-format") is None

    def test_auto_completes_parent_task(self, tmp_path):
        from brynq.task_granularity import create_subtasks, complete_subtask, BROKER_DIR
        tasks_dir = BROKER_DIR / "tasks"
        tasks_dir.mkdir(parents=True, exist_ok=True)
        parent_task = {"task_id": "auto-p", "status": "in_progress", "title": "Parent"}
        (tasks_dir / "auto-p.json").write_text(json.dumps(parent_task))
        create_subtasks("auto-p", [{"title": "Only"}])
        complete_subtask("auto-p-sub-1")
        updated = json.loads((tasks_dir / "auto-p.json").read_text("utf-8"))
        assert updated["status"] == "completed"

    def test_partial_completion_no_parent_update(self, tmp_path):
        from brynq.task_granularity import create_subtasks, complete_subtask, BROKER_DIR
        tasks_dir = BROKER_DIR / "tasks"
        tasks_dir.mkdir(parents=True, exist_ok=True)
        parent_task = {"task_id": "partial-p", "status": "in_progress", "title": "Parent"}
        (tasks_dir / "partial-p.json").write_text(json.dumps(parent_task))
        create_subtasks("partial-p", [{"title": "A"}, {"title": "B"}])
        complete_subtask("partial-p-sub-1")
        updated = json.loads((tasks_dir / "partial-p.json").read_text("utf-8"))
        assert updated["status"] == "in_progress"


# ── get_completion_progress ───────────────────────────────────────────


class TestGetCompletionProgress:
    def test_no_subtasks(self):
        from brynq.task_granularity import get_completion_progress
        progress = get_completion_progress("none")
        assert progress["total"] == 0
        assert progress["percent_complete"] == 0.0

    def test_partial_progress(self):
        from brynq.task_granularity import create_subtasks, complete_subtask, get_completion_progress
        create_subtasks("prog1", [{"title": "A"}, {"title": "B"}, {"title": "C"}, {"title": "D"}])
        complete_subtask("prog1-sub-1")
        complete_subtask("prog1-sub-2")
        progress = get_completion_progress("prog1")
        assert progress["total"] == 4
        assert progress["completed"] == 2
        assert progress["pending"] == 2
        assert progress["percent_complete"] == 50.0

    def test_full_progress(self):
        from brynq.task_granularity import create_subtasks, complete_subtask, get_completion_progress
        create_subtasks("prog2", [{"title": "A"}, {"title": "B"}])
        complete_subtask("prog2-sub-1")
        complete_subtask("prog2-sub-2")
        progress = get_completion_progress("prog2")
        assert progress["percent_complete"] == 100.0
        assert progress["pending"] == 0

    def test_zero_progress(self):
        from brynq.task_granularity import create_subtasks, get_completion_progress
        create_subtasks("prog3", [{"title": "A"}, {"title": "B"}])
        progress = get_completion_progress("prog3")
        assert progress["completed"] == 0
        assert progress["percent_complete"] == 0.0
