"""Tests for the BlindfoldPII Guardrails validator."""

import os
import pytest
from unittest.mock import patch, MagicMock

from guardrails.classes.validation.validation_result import PassResult, FailResult

from guardrails_blindfold import BlindfoldPII
from tests.conftest import MockEntity, MockResponse


class TestBlindfoldPIIInit:
    """Test validator initialization."""

    def test_default_params(self):
        v = BlindfoldPII(api_key="test-key")
        assert v._policy == "basic"
        assert v._pii_method == "tokenize"
        assert v._region is None
        assert v._entities is None
        assert v._score_threshold is None

    def test_custom_params(self):
        v = BlindfoldPII(
            api_key="test-key",
            policy="gdpr_eu",
            pii_method="redact",
            region="eu",
            entities=["Person", "Email Address"],
            score_threshold=0.8,
        )
        assert v._policy == "gdpr_eu"
        assert v._pii_method == "redact"
        assert v._region == "eu"
        assert v._entities == ["Person", "Email Address"]
        assert v._score_threshold == 0.8

    def test_invalid_pii_method(self):
        with pytest.raises(ValueError, match="pii_method must be one of"):
            BlindfoldPII(api_key="test-key", pii_method="invalid")

    def test_api_key_from_env(self, monkeypatch):
        monkeypatch.setenv("BLINDFOLD_API_KEY", "env-key")
        v = BlindfoldPII()
        assert v._api_key == "env-key"

    def test_api_key_param_overrides_env(self, monkeypatch):
        monkeypatch.setenv("BLINDFOLD_API_KEY", "env-key")
        v = BlindfoldPII(api_key="param-key")
        assert v._api_key == "param-key"


class TestValidateNoPII:
    """Test validation when no PII is found."""

    def test_no_pii_returns_pass(self, mock_blindfold):
        mock_blindfold.detect.return_value = MockResponse(
            detected_entities=[],
        )

        v = BlindfoldPII(api_key="test-key")
        result = v._validate("Hello world", {})

        assert isinstance(result, PassResult)
        mock_blindfold.detect.assert_called_once()

    def test_detect_called_with_policy(self, mock_blindfold):
        mock_blindfold.detect.return_value = MockResponse(detected_entities=[])

        v = BlindfoldPII(api_key="test-key", policy="gdpr_eu")
        v._validate("Hello world", {})

        mock_blindfold.detect.assert_called_once_with(
            "Hello world", policy="gdpr_eu"
        )

    def test_detect_called_with_entities_and_threshold(self, mock_blindfold):
        mock_blindfold.detect.return_value = MockResponse(detected_entities=[])

        v = BlindfoldPII(
            api_key="test-key",
            entities=["Person"],
            score_threshold=0.9,
        )
        v._validate("Hello world", {})

        mock_blindfold.detect.assert_called_once_with(
            "Hello world",
            policy="basic",
            entities=["Person"],
            score_threshold=0.9,
        )


class TestValidateWithPII:
    """Test validation when PII is detected."""

    def test_pii_returns_fail(self, mock_blindfold, sample_entities):
        mock_blindfold.detect.return_value = MockResponse(
            detected_entities=sample_entities,
        )
        mock_blindfold.tokenize.return_value = MockResponse(
            text="Contact <Person_1> at <Email Address_1>",
        )

        v = BlindfoldPII(api_key="test-key")
        result = v._validate("Contact John Doe at john@example.com", {})

        assert isinstance(result, FailResult)
        assert "2 PII entities" in result.error_message
        assert "Person" in result.error_message
        assert "Email Address" in result.error_message

    def test_fix_value_is_tokenized(self, mock_blindfold, sample_entities):
        mock_blindfold.detect.return_value = MockResponse(
            detected_entities=sample_entities,
        )
        mock_blindfold.tokenize.return_value = MockResponse(
            text="Contact <Person_1> at <Email Address_1>",
        )

        v = BlindfoldPII(api_key="test-key")
        result = v._validate("Contact John Doe at john@example.com", {})

        assert result.fix_value == "Contact <Person_1> at <Email Address_1>"

    def test_error_spans_match_entities(self, mock_blindfold, sample_entities):
        mock_blindfold.detect.return_value = MockResponse(
            detected_entities=sample_entities,
        )
        mock_blindfold.tokenize.return_value = MockResponse(text="tokenized")

        v = BlindfoldPII(api_key="test-key")
        result = v._validate("Contact John Doe at john@example.com", {})

        assert len(result.error_spans) == 2
        assert result.error_spans[0].start == 8
        assert result.error_spans[0].end == 16
        assert result.error_spans[0].reason == "Person detected"
        assert result.error_spans[1].start == 20
        assert result.error_spans[1].end == 36
        assert result.error_spans[1].reason == "Email Address detected"

    def test_single_entity_message(self, mock_blindfold):
        entities = [
            MockEntity(type="Person", text="John Doe", start=0, end=8),
        ]
        mock_blindfold.detect.return_value = MockResponse(
            detected_entities=entities,
        )
        mock_blindfold.tokenize.return_value = MockResponse(text="<Person_1>")

        v = BlindfoldPII(api_key="test-key")
        result = v._validate("John Doe", {})

        assert "1 PII entity" in result.error_message


class TestPIIMethods:
    """Test different PII protection methods."""

    def test_redact_method(self, mock_blindfold, sample_entities):
        mock_blindfold.detect.return_value = MockResponse(
            detected_entities=sample_entities,
        )
        mock_blindfold.redact.return_value = MockResponse(
            text="Contact [REDACTED] at [REDACTED]",
        )

        v = BlindfoldPII(api_key="test-key", pii_method="redact")
        result = v._validate("Contact John Doe at john@example.com", {})

        assert result.fix_value == "Contact [REDACTED] at [REDACTED]"
        mock_blindfold.redact.assert_called_once()

    def test_mask_method(self, mock_blindfold, sample_entities):
        mock_blindfold.detect.return_value = MockResponse(
            detected_entities=sample_entities,
        )
        mock_blindfold.mask.return_value = MockResponse(
            text="Contact J****oe at j****om",
        )

        v = BlindfoldPII(api_key="test-key", pii_method="mask")
        result = v._validate("Contact John Doe at john@example.com", {})

        assert result.fix_value == "Contact J****oe at j****om"
        mock_blindfold.mask.assert_called_once()

    def test_hash_method(self, mock_blindfold, sample_entities):
        mock_blindfold.detect.return_value = MockResponse(
            detected_entities=sample_entities,
        )
        mock_blindfold.hash.return_value = MockResponse(
            text="Contact HASH_abc123 at HASH_def456",
        )

        v = BlindfoldPII(api_key="test-key", pii_method="hash")
        result = v._validate("Contact John Doe at john@example.com", {})

        assert result.fix_value == "Contact HASH_abc123 at HASH_def456"
        mock_blindfold.hash.assert_called_once()

    def test_synthesize_method(self, mock_blindfold, sample_entities):
        mock_blindfold.detect.return_value = MockResponse(
            detected_entities=sample_entities,
        )
        mock_blindfold.synthesize.return_value = MockResponse(
            text="Contact Jane Smith at jane@example.org",
        )

        v = BlindfoldPII(api_key="test-key", pii_method="synthesize")
        result = v._validate("Contact John Doe at john@example.com", {})

        assert result.fix_value == "Contact Jane Smith at jane@example.org"
        mock_blindfold.synthesize.assert_called_once()

    def test_encrypt_method(self, mock_blindfold, sample_entities):
        mock_blindfold.detect.return_value = MockResponse(
            detected_entities=sample_entities,
        )
        mock_blindfold.encrypt.return_value = MockResponse(
            text="Contact ENC_abc at ENC_def",
        )

        v = BlindfoldPII(api_key="test-key", pii_method="encrypt")
        result = v._validate("Contact John Doe at john@example.com", {})

        assert result.fix_value == "Contact ENC_abc at ENC_def"
        mock_blindfold.encrypt.assert_called_once()


class TestAPIKeyValidation:
    """Test API key handling."""

    def test_missing_api_key_raises(self, monkeypatch):
        monkeypatch.delenv("BLINDFOLD_API_KEY", raising=False)
        v = BlindfoldPII()
        with pytest.raises(ValueError, match="Blindfold API key is required"):
            v._validate("test", {})
