"""
Tests for quality.py — answer quality helpers, continuation detection,
workspace context extraction, and adaptive max-tokens.
"""

import pytest

from api.services.complete.quality import (
    WorkspaceContext,
    extract_workspace_context,
    _looks_incomplete_ending,
    _has_unclosed_code_fence,
    _ends_with_suspicious_fragment,
    _looks_hard_cutoff_tail,
    _infer_task_mode,
)
from api.models import CompleteRequest


def _msg(role, content):
    return CompleteRequest.Message(role=role, content=content)


# ===========================================================================
# WorkspaceContext extraction
# ===========================================================================

class TestWorkspaceContext:
    def test_extract_user_info(self):
        content = (
            "<user_info>\n"
            "OS Version: darwin 25.3.0\n"
            "Shell: zsh\n"
            "Workspace Path: /Users/me/project\n"
            "Is directory a git repo: Yes, at /Users/me/project\n"
            "Today's date: Tuesday Mar 3, 2026\n"
            "</user_info>"
        )
        msgs = [_msg("system", content)]
        ctx = extract_workspace_context(msgs)
        assert ctx.os_name == "darwin 25.3.0"
        assert ctx.shell == "zsh"
        assert ctx.workspace_path == "/Users/me/project"
        assert ctx.is_git_repo is True
        assert ctx.git_root == "/Users/me/project"

    def test_extract_git_status(self):
        content = (
            "<git_status>\n"
            "## main...origin/main\n"
            " M backend/api/routes/complete.py\n"
            "?? new_file.py\n"
            "A  staged.py\n"
            "</git_status>"
        )
        msgs = [_msg("system", content)]
        ctx = extract_workspace_context(msgs)
        assert "backend/api/routes/complete.py" in ctx.git_modified_files
        assert "new_file.py" in ctx.git_untracked_files

    def test_extract_open_files(self):
        content = (
            "<open_and_recently_viewed_files>\n"
            "Recently viewed files (recent at the top, oldest at the bottom):\n"
            "- /Users/me/project/src/app.py (total lines: 100)\n"
            "- /Users/me/project/src/utils.py (total lines: 50)\n"
            "\n"
            "Files that are currently open and visible in the user's IDE:\n"
            "- /Users/me/project/src/app.py (currently focused file, cursor is on line 42, total lines: 100)\n"
            "</open_and_recently_viewed_files>"
        )
        msgs = [_msg("system", content)]
        ctx = extract_workspace_context(msgs)
        assert ctx.focused_file == "/Users/me/project/src/app.py"
        assert ctx.focused_line == 42
        assert ctx.focused_total_lines == 100

    def test_empty_messages(self):
        ctx = extract_workspace_context([])
        assert ctx.workspace_path is None
        assert ctx.focused_file is None
        assert ctx.all_changed_files == []

    def test_investigation_hint_with_data(self):
        ctx = WorkspaceContext()
        ctx.workspace_path = "/project"
        ctx.focused_file = "src/app.py"
        ctx.focused_line = 10
        ctx.os_name = "linux"
        hint = ctx.to_investigation_hint()
        assert hint is not None
        assert "src/app.py" in hint
        assert "WORKSPACE CONTEXT" in hint

    def test_investigation_hint_empty(self):
        ctx = WorkspaceContext()
        assert ctx.to_investigation_hint() is None

    def test_all_changed_files_deduplicates(self):
        ctx = WorkspaceContext()
        ctx.git_modified_files = ["a.py", "b.py"]
        ctx.git_staged_files = ["a.py", "c.py"]
        ctx.git_untracked_files = ["d.py"]
        changed = ctx.all_changed_files
        assert len(changed) == 4
        assert changed == ["a.py", "b.py", "c.py", "d.py"]


# ===========================================================================
# Continuation / cutoff detection
# ===========================================================================

class TestContinuationDetection:
    def test_complete_sentence(self):
        assert _looks_incomplete_ending("This is a complete sentence.") is False

    def test_incomplete_ending_with_conjunction(self):
        assert _looks_incomplete_ending("This is a sentence and") is True

    def test_incomplete_ending_with_preposition(self):
        assert _looks_incomplete_ending("The function returns a value for") is True

    def test_empty_string(self):
        assert _looks_incomplete_ending("") is True

    def test_question_mark_is_complete(self):
        assert _looks_incomplete_ending("Is this correct?") is False

    def test_exclamation_is_complete(self):
        assert _looks_incomplete_ending("Done!") is False


class TestUnclosedCodeFence:
    def test_balanced_fences(self):
        text = "Here is code:\n```python\nx = 1\n```\nDone."
        assert _has_unclosed_code_fence(text) is False

    def test_unclosed_fence(self):
        text = "Here is code:\n```python\nx = 1\n"
        assert _has_unclosed_code_fence(text) is True

    def test_no_fences(self):
        assert _has_unclosed_code_fence("Just text.") is False

    def test_empty(self):
        assert _has_unclosed_code_fence("") is False


class TestSuspiciousFragment:
    def test_normal_ending(self):
        assert _ends_with_suspicious_fragment("This is fine.") is False

    def test_mid_word_cutoff(self):
        # "retu" is 4 chars — the heuristic flags <=2 chars, uppercase, or no-vowel 3-char
        # "FI" (2 chars) triggers; "retu" (4 chars with vowel) does not
        assert _ends_with_suspicious_fragment("The function FI") is True

    def test_common_short_word_ok(self):
        assert _ends_with_suspicious_fragment("I will do it") is False

    def test_uppercase_fragment(self):
        assert _ends_with_suspicious_fragment("The API") is True


class TestHardCutoffTail:
    def test_complete_sentence(self):
        assert _looks_hard_cutoff_tail("All done.") is False

    def test_trailing_comma(self):
        assert _looks_hard_cutoff_tail("First, second,") is True

    def test_trailing_open_bracket(self):
        assert _looks_hard_cutoff_tail("function foo(") is True

    def test_trailing_dash(self):
        assert _looks_hard_cutoff_tail("- item one\n-") is True

    def test_empty(self):
        assert _looks_hard_cutoff_tail("") is True


# ===========================================================================
# Task mode inference
# ===========================================================================

class TestTaskModeInference:
    def test_tool_turn(self):
        msgs = [_msg("user", "fix the bug")]
        assert _infer_task_mode(msgs, enable_tools=True) == "tool_turn"

    def test_explanation_mode(self):
        msgs = [_msg("user", "explain how the authentication system works in detail")]
        assert _infer_task_mode(msgs, enable_tools=False) == "explanation"

    def test_default_mode(self):
        msgs = [_msg("user", "hi")]
        assert _infer_task_mode(msgs, enable_tools=False) == "default"

    def test_long_message_is_explanation(self):
        msgs = [_msg("user", "x" * 250)]
        assert _infer_task_mode(msgs, enable_tools=False) == "explanation"
