"""Unit tests for VIN validator."""
import pytest

from app.validators.vin_validator import VinValidator, vin_validator


class TestVinValidator:
    """Tests for VIN validation logic."""

    def test_correct_ocr_errors_basic(self) -> None:
        """Test basic OCR error correction."""
        validator = VinValidator()

        # I -> 1
        assert validator.correct_ocr_errors("IHGBH41JXMN109186") == "1HGBH41JXMN109186"

        # O -> 0
        assert validator.correct_ocr_errors("1HGBH41JXMN1O9186") == "1HGBH41JXMN109186"

        # Q -> 0
        assert validator.correct_ocr_errors("1HGBH41JXMN1Q9186") == "1HGBH41JXMN109186"

    def test_correct_ocr_errors_lowercase(self) -> None:
        """Test OCR error correction handles lowercase."""
        validator = VinValidator()

        result = validator.correct_ocr_errors("1hgbh41jxmn109186")
        assert result == "1HGBH41JXMN109186"

    def test_correct_ocr_errors_strips_spaces(self) -> None:
        """Test OCR error correction removes spaces and dashes."""
        validator = VinValidator()

        assert validator.correct_ocr_errors("1HG BH41 JXMN 109186") == "1HGBH41JXMN109186"
        assert validator.correct_ocr_errors("1HG-BH41-JXMN-109186") == "1HGBH41JXMN109186"

    def test_calculate_check_digit(self) -> None:
        """Test check digit calculation."""
        validator = VinValidator()

        # Test with known valid VINs
        # 1HGBH41JXMN109186 has check digit X at position 9
        result = validator.calculate_check_digit("1HGBH41JXMN109186")
        assert result == "X"

        # 5YJSA1E28HF123456 has check digit 2 at position 9
        result = validator.calculate_check_digit("5YJSA1E28HF123456")
        assert result == "8"  # Verify this is correct for this VIN

    def test_validate_check_digit_valid(self) -> None:
        """Test check digit validation with valid VIN."""
        validator = VinValidator()

        # This VIN has a valid check digit
        assert validator.validate_check_digit("1HGBH41JXMN109186") is True

    def test_validate_check_digit_invalid(self) -> None:
        """Test check digit validation with invalid VIN."""
        validator = VinValidator()

        # Modify check digit to make it invalid
        assert validator.validate_check_digit("1HGBH41J1MN109186") is False

    def test_validate_modern_vin_valid(self) -> None:
        """Test validation of valid modern VIN."""
        validator = VinValidator()

        result = validator.validate("1HGBH41JXMN109186")
        assert result.is_valid is True
        assert result.vin == "1HGBH41JXMN109186"
        assert result.confidence_adjustment > 0  # Check digit valid = boost

    def test_validate_modern_vin_with_ocr_errors(self) -> None:
        """Test validation corrects OCR errors."""
        validator = VinValidator()

        # I at start should be corrected to 1
        result = validator.validate("IHGBH41JXMN109186")
        assert result.is_valid is True
        assert result.vin == "1HGBH41JXMN109186"

    def test_validate_short_vin(self) -> None:
        """Test validation rejects short VIN."""
        validator = VinValidator()

        result = validator.validate("1HGBH41JX")
        assert result.is_valid is False
        assert "length" in result.error.lower()

    def test_validate_long_vin(self) -> None:
        """Test validation rejects long VIN."""
        validator = VinValidator()

        result = validator.validate("1HGBH41JXMN109186XX")
        assert result.is_valid is False
        assert "length" in result.error.lower()

    def test_validate_empty_vin(self) -> None:
        """Test validation handles empty VIN."""
        validator = VinValidator()

        result = validator.validate("")
        assert result.is_valid is False
        assert "empty" in result.error.lower()

    def test_validate_invalid_characters(self) -> None:
        """Test validation rejects invalid characters after correction."""
        validator = VinValidator()

        # Contains characters not in VIN alphabet
        result = validator.validate("1HGBH41JXMN!@#186", correct_errors=False)
        assert result.is_valid is False
        assert "character" in result.error.lower()

    def test_validate_legacy_vin_allowed(self) -> None:
        """Test validation allows legacy VINs when enabled."""
        validator = VinValidator()

        # 13-character VIN (pre-1981)
        result = validator.validate("ABCD123456789", allow_legacy=True)
        assert result.is_valid is True
        assert result.confidence_adjustment < 0  # Reduced confidence for legacy

    def test_validate_legacy_vin_rejected(self) -> None:
        """Test validation rejects legacy VINs by default."""
        validator = VinValidator()

        result = validator.validate("ABCD123456789", allow_legacy=False)
        assert result.is_valid is False

    def test_extract_candidates_finds_vin(self) -> None:
        """Test candidate extraction from text."""
        validator = VinValidator()

        text = "VIN: 1HGBH41JXMN109186 is shown here"
        candidates = validator.extract_candidates(text)

        assert len(candidates) >= 1
        assert candidates[0][0] == "1HGBH41JXMN109186"

    def test_extract_candidates_multiple_vins(self) -> None:
        """Test candidate extraction with multiple VINs."""
        validator = VinValidator()

        text = "First VIN: 1HGBH41JXMN109186 Second VIN: 5YJSA1E28HF123456"
        candidates = validator.extract_candidates(text)

        assert len(candidates) >= 2
        vins = [c[0] for c in candidates]
        assert "1HGBH41JXMN109186" in vins
        assert "5YJSA1E28HF123456" in vins

    def test_extract_candidates_with_ocr_errors(self) -> None:
        """Test candidate extraction corrects OCR errors."""
        validator = VinValidator()

        # Contains O instead of 0
        text = "VIN: 1HGBH41JXMN1O9186"
        candidates = validator.extract_candidates(text)

        assert len(candidates) >= 1
        assert candidates[0][0] == "1HGBH41JXMN109186"

    def test_extract_candidates_no_vin(self) -> None:
        """Test candidate extraction with no VIN."""
        validator = VinValidator()

        text = "This text contains no VIN numbers"
        candidates = validator.extract_candidates(text)

        assert len(candidates) == 0

    def test_singleton_instance(self) -> None:
        """Test singleton instance is available."""
        assert vin_validator is not None
        assert isinstance(vin_validator, VinValidator)


class TestVinValidatorEdgeCases:
    """Edge case tests for VIN validator."""

    def test_all_zeros_vin(self) -> None:
        """Test VIN with all zeros (unlikely but valid format)."""
        validator = VinValidator()

        result = validator.validate("00000000000000000")
        assert result.is_valid is True
        assert len(result.vin) == 17

    def test_mixed_case_vin(self) -> None:
        """Test VIN with mixed case."""
        validator = VinValidator()

        result = validator.validate("1hGbH41jXmN109186")
        assert result.is_valid is True
        assert result.vin == "1HGBH41JXMN109186"

    def test_vin_with_leading_trailing_whitespace(self) -> None:
        """Test VIN with whitespace."""
        validator = VinValidator()

        result = validator.validate("  1HGBH41JXMN109186  ")
        assert result.is_valid is True
        assert result.vin == "1HGBH41JXMN109186"

    def test_check_digit_x(self) -> None:
        """Test VIN with X as check digit."""
        validator = VinValidator()

        # 1HGBH41JXMN109186 has X as check digit
        assert validator.validate_check_digit("1HGBH41JXMN109186") is True
