"""Tests for promptkit.linter module."""

from typing import List

import pytest

from promptkit.linter import LintReport, LintResult, PromptLinter, Severity
from promptkit.prompt import Prompt


class TestSeverityEnum:
    """Tests for Severity enum."""

    def test_values(self) -> None:
        assert Severity.ERROR.value == "error"
        assert Severity.WARNING.value == "warning"
        assert Severity.INFO.value == "info"


class TestLintResult:
    """Tests for LintResult dataclass."""

    def test_to_dict(self) -> None:
        r = LintResult(rule="test-rule", severity=Severity.ERROR, message="bad")
        d = r.to_dict()
        assert d["rule"] == "test-rule"
        assert d["severity"] == "error"
        assert d["message"] == "bad"

    def test_optional_fields(self) -> None:
        r = LintResult(rule="r", severity=Severity.INFO, message="m", line=5, suggestion="fix it")
        assert r.line == 5
        assert r.suggestion == "fix it"


class TestLintReport:
    """Tests for LintReport properties."""

    def test_empty_report_passed(self) -> None:
        report = LintReport(prompt_name="test", version=1)
        assert report.passed is True
        assert report.score == 100.0

    def test_errors_property(self) -> None:
        report = LintReport(prompt_name="test", version=1, results=[
            LintResult(rule="r1", severity=Severity.ERROR, message="e1"),
            LintResult(rule="r2", severity=Severity.WARNING, message="w1"),
            LintResult(rule="r3", severity=Severity.ERROR, message="e2"),
        ])
        assert len(report.errors) == 2

    def test_warnings_property(self) -> None:
        report = LintReport(prompt_name="test", version=1, results=[
            LintResult(rule="r", severity=Severity.WARNING, message="w"),
        ])
        assert len(report.warnings) == 1

    def test_infos_property(self) -> None:
        report = LintReport(prompt_name="test", version=1, results=[
            LintResult(rule="r", severity=Severity.INFO, message="i"),
        ])
        assert len(report.infos) == 1

    def test_failed_when_errors_exist(self) -> None:
        report = LintReport(prompt_name="test", version=1, results=[
            LintResult(rule="r", severity=Severity.ERROR, message="e"),
        ])
        assert report.passed is False

    def test_to_dict(self) -> None:
        report = LintReport(prompt_name="test", version=1, score=95.0)
        d = report.to_dict()
        assert d["prompt_name"] == "test"
        assert d["score"] == 95.0
        assert "summary" in d


class TestPromptLinterInit:
    """Tests for PromptLinter construction."""

    def test_default_rules(self) -> None:
        linter = PromptLinter()
        assert linter.rules["no-empty"] is True
        assert len(linter.rules) == 12

    def test_custom_rules(self) -> None:
        linter = PromptLinter(rules={"no-empty": False})
        assert linter.rules["no-empty"] is False

    def test_custom_thresholds(self) -> None:
        linter = PromptLinter(thresholds={"min_length": 50})
        assert linter.thresholds["min_length"] == 50


class TestPromptLinterLintContent:
    """Tests for lint_content — the core linting engine."""

    def test_empty_content(self) -> None:
        linter = PromptLinter()
        report = linter.lint_content("")
        assert not report.passed
        rule_names = [r.rule for r in report.results]
        assert "no-empty" in rule_names

    def test_short_content(self) -> None:
        linter = PromptLinter()
        report = linter.lint_content("Hi")
        rule_names = [r.rule for r in report.results]
        assert "min-length" in rule_names

    def test_long_content(self) -> None:
        linter = PromptLinter(thresholds={"max_length": 50})
        report = linter.lint_content("x" * 100)
        rule_names = [r.rule for r in report.results]
        assert "max-length" in rule_names

    def test_trailing_whitespace(self) -> None:
        linter = PromptLinter()
        report = linter.lint_content("Line with trailing space   \nAnother line")
        rule_names = [r.rule for r in report.results]
        assert "no-trailing-whitespace" in rule_names

    def test_double_spaces(self) -> None:
        linter = PromptLinter()
        report = linter.lint_content("This has  double  spaces in the sentence for testing purposes.")
        rule_names = [r.rule for r in report.results]
        assert "no-double-spaces" in rule_names

    def test_has_instruction_detected(self) -> None:
        linter = PromptLinter()
        report = linter.lint_content(
            "You are a helpful assistant. Please respond in JSON format. "
            "Always provide clear answers."
        )
        rule_names = [r.rule for r in report.results]
        assert "has-instruction" not in rule_names

    def test_no_instruction_detected(self) -> None:
        linter = PromptLinter()
        report = linter.lint_content("Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
        rule_names = [r.rule for r in report.results]
        assert "has-instruction" in rule_names

    def test_typo_variables_single_brace(self) -> None:
        linter = PromptLinter()
        report = linter.lint_content("Hello {name}, welcome to the system. Please follow instructions carefully.")
        rule_names = [r.rule for r in report.results]
        assert "no-typo-variables" in rule_names

    def test_balanced_braces_ok(self) -> None:
        linter = PromptLinter()
        report = linter.lint_content(
            "You are a {{role}}. Please help with {{task}}. Always respond clearly."
        )
        rule_names = [r.rule for r in report.results]
        assert "balanced-braces" not in rule_names

    def test_unbalanced_braces(self) -> None:
        linter = PromptLinter()
        report = linter.lint_content("Hello {{ name }. Please respond clearly and always help the user.")
        rule_names = [r.rule for r in report.results]
        assert "balanced-braces" in rule_names

    def test_todo_marker(self) -> None:
        linter = PromptLinter()
        report = linter.lint_content(
            "You are an assistant. TODO: add more instructions. Please always respond clearly."
        )
        rule_names = [r.rule for r in report.results]
        assert "no-todo" in rule_names

    def test_has_context_sufficient(self) -> None:
        linter = PromptLinter()
        report = linter.lint_content(
            "You are a senior Python developer. Help the user debug their code. "
            "Always provide explanations and follow best practices."
        )
        rule_names = [r.rule for r in report.results]
        assert "has-context" not in rule_names

    def test_has_context_insufficient(self) -> None:
        linter = PromptLinter()
        report = linter.lint_content("Help me")
        rule_names = [r.rule for r in report.results]
        assert "has-context" in rule_names

    def test_ambiguity_detected(self) -> None:
        linter = PromptLinter()
        report = linter.lint_content(
            "You should maybe do something nice with stuff. Please respond to the user and help them."
        )
        rule_names = [r.rule for r in report.results]
        assert "no-ambiguity" in rule_names

    def test_mixed_heading_styles(self) -> None:
        linter = PromptLinter()
        content = "# Heading One\n\nSome text\n\nHeading Two\n===========\n\nPlease respond clearly and always help the user."
        report = linter.lint_content(content)
        rule_names = [r.rule for r in report.results]
        assert "consistent-format" in rule_names

    def test_mixed_bullet_styles(self) -> None:
        linter = PromptLinter()
        content = "You are an assistant. Please follow:\n- Rule one\n* Rule two\n- Rule three\n\nAlways respond clearly."
        report = linter.lint_content(content)
        rule_names = [r.rule for r in report.results]
        assert "consistent-format" in rule_names


class TestPromptLinterLintPrompt:
    """Tests for lint(prompt) method."""

    def test_lint_prompt_with_version(self) -> None:
        p = Prompt(name="test")
        p.add_version("You are a helpful assistant. Please respond clearly and follow all instructions.")
        linter = PromptLinter()
        report = linter.lint(p, version=1)
        assert report.prompt_name == "test"
        assert report.version == 1

    def test_lint_prompt_no_versions(self) -> None:
        p = Prompt(name="empty")
        linter = PromptLinter()
        report = linter.lint(p)
        assert not report.passed
        assert report.score == 0.0

    def test_lint_defaults_to_latest(self) -> None:
        p = Prompt(name="test")
        p.add_version("You are a helpful assistant. Please respond clearly and always help the user.")
        p.add_version("You are an expert assistant. Please respond clearly and always help the user.")
        linter = PromptLinter()
        report = linter.lint(p)
        assert report.version == 2


class TestPromptLinterScoring:
    """Tests for the scoring system."""

    def test_perfect_score(self) -> None:
        linter = PromptLinter()
        # Well-formed prompt: has instructions, sufficient context, no issues
        content = (
            "You are a senior software engineer.\n"
            "Please review the following code and provide detailed feedback.\n"
            "Always explain your reasoning step by step.\n"
            "Respond in a structured format with sections for issues, suggestions, and summary."
        )
        report = linter.lint_content(content)
        # Should have few or no issues
        assert report.score >= 80.0

    def test_error_penalty(self) -> None:
        linter = PromptLinter()
        # empty content gives error → -20 per error
        report = linter.lint_content("")
        assert report.score < 100.0

    def test_score_never_negative(self) -> None:
        linter = PromptLinter()
        # Trigger many issues
        report = linter.lint_content("")
        assert report.score >= 0.0

    def test_disabled_rule_not_checked(self) -> None:
        linter = PromptLinter(rules={"no-empty": False})
        report = linter.lint_content("")
        rule_names = [r.rule for r in report.results]
        assert "no-empty" not in rule_names
