"""Tests for the laziness auto-detector."""
import pytest

from greymatter_core.laziness_detector import LazinessDetector, LazinessReport


@pytest.fixture
def detector():
    return LazinessDetector()


# ── Code Scanning Tests ──────────────────────────────────────


class TestBareTodo:
    """Tests for bare TODO/FIXME detection."""

    def test_bare_todo_detected(self, detector):
        code = "# TODO: fix this later\nx = 1\n"
        report = detector.scan_code(code)
        assert len(report.findings) == 1
        assert report.findings[0].rule_id == "qs-no-bare-todo"
        assert report.findings[0].severity == "critical"

    def test_bare_fixme_detected(self, detector):
        code = "# FIXME: broken\n"
        report = detector.scan_code(code)
        assert len(report.findings) == 1
        assert report.findings[0].rule_id == "qs-no-bare-todo"

    def test_bare_hack_detected(self, detector):
        code = "# HACK: workaround\n"
        report = detector.scan_code(code)
        assert len(report.findings) == 1

    def test_todo_with_ticket_ok(self, detector):
        code = "# TODO(GH-142): handle retry backoff\n"
        report = detector.scan_code(code)
        assert len(report.findings) == 0

    def test_todo_with_jira_ticket_ok(self, detector):
        code = "# TODO(PROJ-99): implement caching\n"
        report = detector.scan_code(code)
        assert len(report.findings) == 0

    def test_multiple_bare_todos(self, detector):
        code = "# TODO: first\n# FIXME: second\nx = 1\n"
        report = detector.scan_code(code)
        assert len(report.findings) == 2


class TestEmptyExcept:
    """Tests for empty except block detection."""

    def test_except_pass_detected(self, detector):
        code = "try:\n    x = 1\nexcept Exception:\n    pass\n"
        report = detector.scan_code(code)
        assert any(f.rule_id == "qs-no-empty-except" for f in report.findings)

    def test_except_ellipsis_detected(self, detector):
        code = "try:\n    x = 1\nexcept ValueError:\n    ...\n"
        report = detector.scan_code(code)
        assert any(f.rule_id == "qs-no-empty-except" for f in report.findings)

    def test_except_with_logging_ok(self, detector):
        code = "try:\n    x = 1\nexcept Exception as e:\n    logger.error('Failed: %s', e)\n"
        report = detector.scan_code(code)
        assert not any(f.rule_id == "qs-no-empty-except" for f in report.findings)

    def test_except_with_raise_ok(self, detector):
        code = "try:\n    x = 1\nexcept ValueError:\n    raise RuntimeError('bad')\n"
        report = detector.scan_code(code)
        assert not any(f.rule_id == "qs-no-empty-except" for f in report.findings)


class TestStubFunctions:
    """Tests for stub function detection."""

    def test_bare_pass_detected(self, detector):
        code = "def process_data(data):\n    pass\n"
        report = detector.scan_code(code)
        assert any(f.rule_id == "qs-no-stub-functions" for f in report.findings)

    def test_bare_not_implemented_detected(self, detector):
        code = "def process_data(data):\n    raise NotImplementedError()\n"
        report = detector.scan_code(code)
        assert any(f.rule_id == "qs-no-stub-functions" for f in report.findings)

    def test_not_implemented_with_message_ok(self, detector):
        code = 'def process_data(data):\n    raise NotImplementedError("Blocked on API v2")\n'
        report = detector.scan_code(code)
        assert not any(f.rule_id == "qs-no-stub-functions" for f in report.findings)

    def test_real_function_ok(self, detector):
        code = "def process_data(data):\n    return data.strip()\n"
        report = detector.scan_code(code)
        assert not any(f.rule_id == "qs-no-stub-functions" for f in report.findings)

    def test_async_stub_detected(self, detector):
        code = "async def fetch(url):\n    pass\n"
        report = detector.scan_code(code)
        assert any(f.rule_id == "qs-no-stub-functions" for f in report.findings)

    def test_function_with_docstring_then_pass(self, detector):
        code = 'def process(data):\n    """Process the data."""\n    pass\n'
        report = detector.scan_code(code)
        assert any(f.rule_id == "qs-no-stub-functions" for f in report.findings)


class TestPrintInProduction:
    """Tests for print() in production code detection."""

    def test_print_detected(self, detector):
        code = 'print("hello")\n'
        report = detector.scan_code(code, filename="module.py")
        assert any(f.rule_id == "qs-no-print-in-production" for f in report.findings)

    def test_print_in_test_ok(self, detector):
        code = 'print("debug")\n'
        report = detector.scan_code(code, filename="test_module.py")
        assert not any(f.rule_id == "qs-no-print-in-production" for f in report.findings)

    def test_print_in_main_ok(self, detector):
        code = 'if __name__ == "__main__":\n    print("running")\n'
        report = detector.scan_code(code, filename="cli.py")
        assert not any(f.rule_id == "qs-no-print-in-production" for f in report.findings)

    def test_logger_ok(self, detector):
        code = 'logger.info("Processing %s", item)\n'
        report = detector.scan_code(code, filename="module.py")
        assert not any(f.rule_id == "qs-no-print-in-production" for f in report.findings)


class TestCommentedOutCode:
    """Tests for commented-out code block detection."""

    def test_three_lines_detected(self, detector):
        code = "# def old_handler(req):\n# def helper():\n# return result\nx = 1\n"
        report = detector.scan_code(code)
        assert any(f.rule_id == "qs-no-commented-out-code" for f in report.findings)

    def test_two_lines_ok(self, detector):
        code = "# def old_handler(req):\n#     return result\nx = 1\n"
        report = detector.scan_code(code)
        assert not any(f.rule_id == "qs-no-commented-out-code" for f in report.findings)

    def test_regular_comments_ok(self, detector):
        code = "# This is a comment\n# About the module\n# Explaining things\nx = 1\n"
        report = detector.scan_code(code)
        assert not any(f.rule_id == "qs-no-commented-out-code" for f in report.findings)

    def test_trailing_block_detected(self, detector):
        code = "x = 1\n# def old():\n#     return None\n# def other():\n"
        report = detector.scan_code(code)
        assert any(f.rule_id == "qs-no-commented-out-code" for f in report.findings)


# ── Conversation Scanning Tests ──────────────────────────────


class TestConversationScanning:
    """Tests for laziness correction pattern detection in conversation text."""

    def test_actually_implement_detected(self, detector):
        text = "Please actually implement the function, don't just stub it."
        report = detector.scan_conversation(text)
        assert len(report.findings) >= 1
        assert report.findings[0].rule_id == "laziness-correction-detected"

    def test_write_real_code_detected(self, detector):
        text = "I need you to write real code, not placeholders."
        report = detector.scan_conversation(text)
        assert len(report.findings) >= 1

    def test_stop_being_lazy_detected(self, detector):
        text = "Stop being lazy and finish the implementation."
        report = detector.scan_conversation(text)
        assert len(report.findings) >= 1

    def test_half_baked_detected(self, detector):
        text = "This is half-baked, needs actual implementation."
        report = detector.scan_conversation(text)
        assert len(report.findings) >= 1

    def test_normal_conversation_ok(self, detector):
        text = "Let's implement the caching layer for the query engine."
        report = detector.scan_conversation(text)
        assert len(report.findings) == 0

    def test_technical_discussion_ok(self, detector):
        text = "The implementation uses a B-tree for indexing.\nWe should add retry logic."
        report = detector.scan_conversation(text)
        assert len(report.findings) == 0

    def test_you_skipped_detected(self, detector):
        text = "You skipped the error handling for the API call."
        report = detector.scan_conversation(text)
        assert len(report.findings) >= 1

    def test_empty_function_detected(self, detector):
        text = "That's an empty function, it needs to actually do something."
        report = detector.scan_conversation(text)
        assert len(report.findings) >= 1

    def test_case_insensitive(self, detector):
        text = "ACTUALLY IMPLEMENT the handler."
        report = detector.scan_conversation(text)
        assert len(report.findings) >= 1


# ── Combined Scanning Tests ──────────────────────────────────


class TestScanAll:
    """Tests for combined code + conversation scanning."""

    def test_both_code_and_conversation(self, detector):
        code = "# TODO: fix\ndef stub():\n    pass\n"
        conversation = "Actually implement the stub function."
        report = detector.scan_all(code=code, conversation=conversation)
        assert len(report.findings) >= 3  # bare todo + stub + correction

    def test_code_only(self, detector):
        code = "# TODO: fix\n"
        report = detector.scan_all(code=code)
        assert len(report.findings) == 1

    def test_conversation_only(self, detector):
        conversation = "Don't just stub it out."
        report = detector.scan_all(conversation=conversation)
        assert len(report.findings) == 1

    def test_clean_code_clean_conversation(self, detector):
        code = "def process(data):\n    return data.strip()\n"
        conversation = "Looks good, the implementation is solid."
        report = detector.scan_all(code=code, conversation=conversation)
        assert len(report.findings) == 0
        assert not report.is_lazy


# ── Report Structure Tests ───────────────────────────────────


class TestReport:
    """Tests for LazinessReport structure and scoring."""

    def test_laziness_score_increases_with_findings(self, detector):
        clean = "x = 1\n"
        lazy = "# TODO: fix\n# FIXME: broken\ndef stub():\n    pass\n"
        clean_report = detector.scan_code(clean)
        lazy_report = detector.scan_code(lazy)
        assert lazy_report.laziness_score > clean_report.laziness_score

    def test_score_capped_at_one(self, detector):
        # Lots of violations
        code = "\n".join(f"# TODO: fix {i}" for i in range(20))
        report = detector.scan_code(code)
        assert report.laziness_score <= 1.0

    def test_is_lazy_on_critical(self, detector):
        code = "# TODO: fix\n"
        report = detector.scan_code(code)
        assert report.is_lazy

    def test_not_lazy_on_clean(self, detector):
        code = "x = 1\ny = 2\n"
        report = detector.scan_code(code)
        assert not report.is_lazy

    def test_to_dict_structure(self, detector):
        code = "# TODO: fix\n"
        report = detector.scan_code(code)
        d = report.to_dict()
        assert "findings" in d
        assert "scanned_lines" in d
        assert "laziness_score" in d
        assert "critical_count" in d
        assert "is_lazy" in d
        assert "total_findings" in d

    def test_finding_to_dict(self, detector):
        code = "# TODO: fix\n"
        report = detector.scan_code(code)
        finding_dict = report.findings[0].to_dict()
        assert "rule_id" in finding_dict
        assert "category" in finding_dict
        assert "severity" in finding_dict
        assert "line" in finding_dict
        assert "text" in finding_dict
        assert "description" in finding_dict
        assert "suggestion" in finding_dict


# ── Anti-Laziness Standards Integration Tests ────────────────


class TestStandardsIntegration:
    """Tests for anti-laziness standards in the standards module."""

    def test_anti_laziness_standards_exist(self):
        from greymatter_core.standards import ANTI_LAZINESS_STANDARDS
        assert len(ANTI_LAZINESS_STANDARDS) == 5

    def test_anti_laziness_ids(self):
        from greymatter_core.standards import ANTI_LAZINESS_STANDARDS
        ids = {s["id"] for s in ANTI_LAZINESS_STANDARDS}
        assert "qs-no-bare-todo" in ids
        assert "qs-no-empty-except" in ids
        assert "qs-no-stub-functions" in ids
        assert "qs-no-print-in-production" in ids
        assert "qs-no-commented-out-code" in ids

    def test_anti_laziness_seeded(self):
        from greymatter_core.db import SQLiteDatabase
        from greymatter_core.standards import StandardsManager
        db = SQLiteDatabase(":memory:")
        mgr = StandardsManager(db)
        count = mgr.seed_defaults()
        # Should include both DEFAULT_STANDARDS and ANTI_LAZINESS_STANDARDS
        active = mgr.get_active()
        laziness_ids = {"qs-no-bare-todo", "qs-no-empty-except", "qs-no-stub-functions",
                        "qs-no-print-in-production", "qs-no-commented-out-code"}
        active_ids = {s["id"] for s in active}
        assert laziness_ids.issubset(active_ids)
        db.close()

    def test_laziness_standards_in_payload(self):
        import json
        from greymatter_core.db import SQLiteDatabase
        from greymatter_core.standards import StandardsManager
        db = SQLiteDatabase(":memory:")
        mgr = StandardsManager(db)
        mgr.seed_defaults()
        payload = json.loads(mgr.get_context_payload())
        rules = [p["rule"] for p in payload]
        assert any("TODO" in r for r in rules)
        assert any("except" in r.lower() for r in rules)
        db.close()
