"""
Tests for CLI validate command output formatting.

This module provides comprehensive tests for the rich output
formatting in the validate command.
"""

from __future__ import annotations

import io
import os
import tempfile

import pytest
from rich.console import Console

from torivers_sdk.cli.commands.validate import (
    ValidationError,
    ValidationResult,
    _display_improvement_tips,
    _display_score,
    display_validation_results,
)

# =============================================================================
# Test Fixtures
# =============================================================================


@pytest.fixture
def capture_console() -> Console:
    """Create a console that captures output."""
    return Console(file=io.StringIO(), force_terminal=True, width=100)


@pytest.fixture
def get_output():
    """Factory fixture for getting console output."""

    def _get_output(console: Console) -> str:
        file = console.file
        if isinstance(file, io.StringIO):
            return file.getvalue()
        return ""

    return _get_output


# =============================================================================
# ValidationResult Tests
# =============================================================================


class TestValidationResult:
    """Test ValidationResult dataclass."""

    def test_initial_state(self) -> None:
        """Test ValidationResult initial state."""
        result = ValidationResult()
        assert result.passed is True
        assert result.errors == []
        assert result.warnings == []
        assert result.score == 100.0

    def test_add_error(self) -> None:
        """Test adding an error to ValidationResult."""
        result = ValidationResult()
        result.add_error(
            location="main.py:10",
            message="Test error",
            fix_hint="Fix this",
        )

        assert result.passed is False
        assert len(result.errors) == 1
        assert result.errors[0].location == "main.py:10"
        assert result.errors[0].message == "Test error"
        assert result.errors[0].fix_hint == "Fix this"
        assert result.errors[0].severity == "error"

    def test_add_warning(self) -> None:
        """Test adding a warning to ValidationResult."""
        result = ValidationResult()
        result.add_warning(
            location="config.yaml",
            message="Test warning",
            fix_hint="Consider this",
        )

        assert result.passed is True  # Warnings don't fail validation
        assert len(result.warnings) == 1
        assert result.warnings[0].location == "config.yaml"
        assert result.warnings[0].message == "Test warning"
        assert result.warnings[0].severity == "warning"

    def test_add_multiple_issues(self) -> None:
        """Test adding multiple errors and warnings."""
        result = ValidationResult()
        result.add_error("file1.py", "Error 1")
        result.add_error("file2.py", "Error 2")
        result.add_warning("file3.py", "Warning 1")
        result.add_warning("file4.py", "Warning 2")
        result.add_warning("file5.py", "Warning 3")

        assert result.passed is False
        assert len(result.errors) == 2
        assert len(result.warnings) == 3


class TestScoreCalculation:
    """Test ValidationResult.calculate_score() method."""

    def test_perfect_score(self) -> None:
        """Test perfect score with no issues."""
        result = ValidationResult()
        score = result.calculate_score()
        assert score == 100.0
        assert result.score == 100.0

    def test_score_with_errors_only(self) -> None:
        """Test score deduction with errors."""
        result = ValidationResult()
        result.add_error("file.py", "Error 1")
        result.add_error("file.py", "Error 2")
        score = result.calculate_score()

        # 2 errors * 15 deduction = 30 deduction
        assert score == 70.0

    def test_score_with_warnings_only(self) -> None:
        """Test score deduction with warnings."""
        result = ValidationResult()
        result.add_warning("file.py", "Warning 1")
        result.add_warning("file.py", "Warning 2")
        result.add_warning("file.py", "Warning 3")
        score = result.calculate_score()

        # 3 warnings * 5 deduction = 15 deduction
        assert score == 85.0

    def test_score_with_mixed_issues(self) -> None:
        """Test score deduction with both errors and warnings."""
        result = ValidationResult()
        result.add_error("file.py", "Error 1")
        result.add_warning("file.py", "Warning 1")
        result.add_warning("file.py", "Warning 2")
        score = result.calculate_score()

        # 1 error * 15 + 2 warnings * 5 = 25 deduction
        assert score == 75.0

    def test_score_minimum_zero(self) -> None:
        """Test score never goes below zero."""
        result = ValidationResult()
        # Add many errors to exceed 100 deduction
        for i in range(10):
            result.add_error(f"file{i}.py", f"Error {i}")

        score = result.calculate_score()

        # 10 errors * 15 = 150 deduction, but score capped at 0
        assert score == 0.0


# =============================================================================
# Display Results Tests
# =============================================================================


class TestDisplayValidationResults:
    """Test display_validation_results() function."""

    def test_display_passed_no_warnings(
        self, capture_console: Console, get_output
    ) -> None:
        """Test display with passed validation and no warnings."""
        result = ValidationResult()
        result.calculate_score()

        display_validation_results(result, capture_console)
        output = get_output(capture_console)

        assert "Validation Passed" in output
        assert "100" in output
        assert "Excellent" in output
        # Should not show error or warning tables
        assert "Errors" not in output
        assert "Warnings" not in output

    def test_display_passed_with_warnings(
        self, capture_console: Console, get_output
    ) -> None:
        """Test display with passed validation but with warnings."""
        result = ValidationResult()
        result.add_warning("config.yaml", "Missing optional field")
        result.calculate_score()

        display_validation_results(result, capture_console)
        output = get_output(capture_console)

        assert "Validation Passed with Warnings" in output
        assert "Warnings" in output
        assert "Missing optional field" in output

    def test_display_failed(self, capture_console: Console, get_output) -> None:
        """Test display with failed validation."""
        result = ValidationResult()
        result.add_error("main.py", "Critical error found")
        result.calculate_score()

        display_validation_results(result, capture_console)
        output = get_output(capture_console)

        assert "Validation Failed" in output
        assert "Errors" in output
        assert "Critical error found" in output

    def test_display_error_table_columns(
        self, capture_console: Console, get_output
    ) -> None:
        """Test error table has correct columns."""
        result = ValidationResult()
        result.add_error(
            location="main.py:15",
            message="Import not allowed",
            fix_hint="Use SDK alternative",
        )
        result.calculate_score()

        display_validation_results(result, capture_console)
        output = get_output(capture_console)

        # Check all columns are present
        assert "Location" in output
        assert "Error" in output
        assert "Fix" in output
        # Check data is present
        assert "main.py:15" in output
        assert "Import not allowed" in output
        assert "Use SDK alternative" in output

    def test_display_warning_table_columns(
        self, capture_console: Console, get_output
    ) -> None:
        """Test warning table has correct columns."""
        result = ValidationResult()
        result.add_warning(
            location="automation.yaml",
            message="No input_schema defined",
            fix_hint="Define input_schema",
        )
        result.calculate_score()

        display_validation_results(result, capture_console)
        output = get_output(capture_console)

        # Check all columns are present
        assert "Location" in output
        assert "Warning" in output
        assert "Suggestion" in output
        # Check data is present
        assert "automation.yaml" in output
        assert "No input_schema defined" in output
        assert "Define input_schema" in output

    def test_display_multiple_errors(
        self, capture_console: Console, get_output
    ) -> None:
        """Test display with multiple errors."""
        result = ValidationResult()
        result.add_error("file1.py:10", "Error one")
        result.add_error("file2.py:20", "Error two")
        result.add_error("file3.py:30", "Error three")
        result.calculate_score()

        display_validation_results(result, capture_console)
        output = get_output(capture_console)

        assert "Error one" in output
        assert "Error two" in output
        assert "Error three" in output
        assert "file1.py:10" in output
        assert "file2.py:20" in output
        assert "file3.py:30" in output


class TestDisplayScore:
    """Test _display_score() function."""

    def test_excellent_score_green(self, capture_console: Console, get_output) -> None:
        """Test excellent score (>=90) displays in green."""
        result = ValidationResult()
        result.score = 95.0

        _display_score(result, capture_console)
        output = get_output(capture_console)

        assert "95" in output
        assert "Excellent" in output
        assert "Validation Score:" in output

    def test_good_score_yellow(self, capture_console: Console, get_output) -> None:
        """Test good score (>=80, <90) displays in yellow."""
        result = ValidationResult()
        result.score = 85.0

        _display_score(result, capture_console)
        output = get_output(capture_console)

        assert "85" in output
        assert "Good" in output

    def test_needs_improvement_score(
        self, capture_console: Console, get_output
    ) -> None:
        """Test needs improvement score (>=60, <80)."""
        result = ValidationResult()
        result.score = 70.0

        _display_score(result, capture_console)
        output = get_output(capture_console)

        assert "70" in output
        assert "Needs Improvement" in output

    def test_poor_score_red(self, capture_console: Console, get_output) -> None:
        """Test poor score (<60) displays in red."""
        result = ValidationResult()
        result.score = 40.0

        _display_score(result, capture_console)
        output = get_output(capture_console)

        assert "40" in output
        assert "Poor" in output

    def test_zero_score(self, capture_console: Console, get_output) -> None:
        """Test zero score display."""
        result = ValidationResult()
        result.score = 0.0

        _display_score(result, capture_console)
        output = get_output(capture_console)

        assert "0" in output
        assert "Poor" in output


class TestDisplayImprovementTips:
    """Test _display_improvement_tips() function."""

    def test_tips_panel_displayed(self, capture_console: Console, get_output) -> None:
        """Test improvement tips panel is displayed."""
        result = ValidationResult()
        result.add_error("file.py", "Error 1")
        result.calculate_score()

        _display_improvement_tips(result, capture_console)
        output = get_output(capture_console)

        assert "Tips to Improve Score" in output

    def test_error_count_tip(self, capture_console: Console, get_output) -> None:
        """Test tip shows error count."""
        result = ValidationResult()
        result.add_error("file.py", "Error 1")
        result.add_error("file.py", "Error 2")
        result.calculate_score()

        _display_improvement_tips(result, capture_console)
        output = get_output(capture_console)

        assert "Fix 2 error(s)" in output

    def test_warning_count_tip(self, capture_console: Console, get_output) -> None:
        """Test tip shows warning count."""
        result = ValidationResult()
        result.add_warning("file.py", "Warning 1")
        result.add_warning("file.py", "Warning 2")
        result.add_warning("file.py", "Warning 3")
        result.calculate_score()

        _display_improvement_tips(result, capture_console)
        output = get_output(capture_console)

        assert "Address 3 warning(s)" in output

    def test_security_tip_for_security_errors(
        self, capture_console: Console, get_output
    ) -> None:
        """Test security tip when security errors present."""
        result = ValidationResult()
        result.add_error("file.py", "[S200] Forbidden import: os")
        result.calculate_score()

        _display_improvement_tips(result, capture_console)
        output = get_output(capture_console)

        assert "Security issues detected" in output
        assert "SDK clients" in output

    def test_documentation_tip(self, capture_console: Console, get_output) -> None:
        """Test documentation tip when schema warnings present."""
        result = ValidationResult()
        result.add_warning("automation.yaml", "No input_schema defined")
        result.calculate_score()

        _display_improvement_tips(result, capture_console)
        output = get_output(capture_console)

        assert "documentation and schemas" in output

    def test_test_tip(self, capture_console: Console, get_output) -> None:
        """Test tip when test warnings present."""
        result = ValidationResult()
        result.add_warning("tests/", "No test files found")
        result.calculate_score()

        _display_improvement_tips(result, capture_console)
        output = get_output(capture_console)

        assert "comprehensive tests" in output

    def test_max_five_tips(self, capture_console: Console, get_output) -> None:
        """Test that maximum 5 tips are displayed."""
        result = ValidationResult()
        # Add many errors and warnings to generate many tips
        for i in range(10):
            result.add_error(f"file{i}.py", f"[S{i}] Error {i}")
            result.add_warning(f"file{i}.py", f"Test warning {i}")
            result.add_warning(f"file{i}.py", f"Schema warning {i}")
            result.add_warning(f"file{i}.py", f"Readme warning {i}")
        result.calculate_score()

        _display_improvement_tips(result, capture_console)
        output = get_output(capture_console)

        # Count bullet points (tip markers)
        bullet_count = output.count("\u2022")  # Unicode bullet point
        assert bullet_count <= 5


class TestImprovementTipsThreshold:
    """Test that improvement tips only show for scores below 80."""

    def test_no_tips_for_high_score(self, capture_console: Console, get_output) -> None:
        """Test no tips panel when score >= 80."""
        result = ValidationResult()
        result.add_warning("file.py", "Minor warning")  # 5 point deduction
        result.calculate_score()
        assert result.score >= 80

        display_validation_results(result, capture_console)
        output = get_output(capture_console)

        # Should not show tips panel
        assert "Tips to Improve Score" not in output

    def test_tips_shown_for_low_score(
        self, capture_console: Console, get_output
    ) -> None:
        """Test tips panel shown when score < 80."""
        result = ValidationResult()
        # Add enough warnings to drop below 80
        result.add_warning("file1.py", "Warning 1")
        result.add_warning("file2.py", "Warning 2")
        result.add_warning("file3.py", "Warning 3")
        result.add_warning("file4.py", "Warning 4")
        result.add_warning("file5.py", "Warning 5")
        result.calculate_score()
        assert result.score < 80

        display_validation_results(result, capture_console)
        output = get_output(capture_console)

        # Should show tips panel
        assert "Tips to Improve Score" in output


# =============================================================================
# Integration Tests
# =============================================================================


class TestValidateOutputIntegration:
    """Integration tests for complete validation output."""

    @pytest.fixture(autouse=True)
    def restore_cwd(self):
        """Restore working directory after each test."""
        original_cwd = os.getcwd()
        yield
        try:
            os.chdir(original_cwd)
        except FileNotFoundError:
            os.chdir(tempfile.gettempdir())

    def test_complete_output_structure(
        self, capture_console: Console, get_output
    ) -> None:
        """Test complete validation output has all expected sections."""
        result = ValidationResult()
        # Add enough issues to drop score below 80 so tips show
        result.add_error("main.py:10", "Import error", "Use SDK import")
        result.add_error("main.py:20", "Security error", "Use SDK client")
        result.add_warning("automation.yaml", "Missing schema", "Add schema")
        result.calculate_score()
        assert result.score < 80  # Verify score is below 80

        display_validation_results(result, capture_console)
        output = get_output(capture_console)

        # Check all major sections
        assert "Validation" in output  # Status panel
        assert "Errors" in output  # Error table
        assert "Warnings" in output  # Warning table
        assert "Validation Score:" in output  # Score display
        assert "Tips to Improve Score" in output  # Tips panel

    def test_output_order(self, capture_console: Console, get_output) -> None:
        """Test output sections appear in correct order."""
        result = ValidationResult()
        # Add enough issues to drop score below 80 so tips show
        result.add_error("file.py", "Error message")
        result.add_error("file2.py", "Another error")
        result.add_warning("file.py", "Warning message")
        result.calculate_score()
        assert result.score < 80  # Verify score is below 80

        display_validation_results(result, capture_console)
        output = get_output(capture_console)

        # Find positions of key elements
        status_pos = output.find("Validation Failed")
        error_pos = output.find("Errors")
        warning_pos = output.find("Warnings")
        score_pos = output.find("Validation Score:")
        tips_pos = output.find("Tips to Improve")

        # Verify order: status -> errors -> warnings -> score -> tips
        assert status_pos < error_pos
        assert error_pos < warning_pos
        assert warning_pos < score_pos
        assert score_pos < tips_pos


class TestValidationErrorDataclass:
    """Test ValidationError dataclass."""

    def test_validation_error_fields(self) -> None:
        """Test ValidationError has all expected fields."""
        error = ValidationError(
            location="test.py:5",
            message="Test error message",
            fix_hint="How to fix",
            severity="error",
        )

        assert error.location == "test.py:5"
        assert error.message == "Test error message"
        assert error.fix_hint == "How to fix"
        assert error.severity == "error"

    def test_validation_error_defaults(self) -> None:
        """Test ValidationError default values."""
        error = ValidationError(
            location="test.py",
            message="Test message",
        )

        assert error.fix_hint is None
        assert error.severity == "error"

    def test_validation_error_warning_severity(self) -> None:
        """Test ValidationError with warning severity."""
        warning = ValidationError(
            location="test.py",
            message="Warning message",
            severity="warning",
        )

        assert warning.severity == "warning"
