# AgentAssay — Token-efficient stochastic testing for AI agents
# Part of Qualixar | Author: Varun Pratap Bhardwaj
# https://qualixar.com | https://varunpratap.com
# License: Apache-2.0

"""Tests for the Qualixar 3-Layer Attribution System."""

from __future__ import annotations

import json
import re
from pathlib import Path

import pytest

from agentassay.attribution import QualixarSigner, QualixarWatermark


# ===================================================================
# Layer 2: QualixarSigner Tests
# ===================================================================


class TestQualixarSigner:
    """Tests for cryptographic signing."""

    def test_sign_creates_valid_metadata(self) -> None:
        """Test that sign() creates all required metadata fields."""
        signer = QualixarSigner()
        content = "Test report content"
        metadata = signer.sign(content)

        # Check all required fields
        assert metadata["author"] == "Varun Pratap Bhardwaj"
        assert metadata["author_url"] == "https://varunpratap.com"
        assert metadata["product"] == "AgentAssay"
        assert metadata["product_url"] == "https://qualixar.com"
        assert metadata["license"] == "Apache-2.0"
        assert "timestamp" in metadata
        assert "content_hash" in metadata
        assert "signature" in metadata

        # Signature should be hex string (64 chars for SHA256)
        assert len(metadata["signature"]) == 64
        assert all(c in "0123456789abcdef" for c in metadata["signature"])

    def test_verify_valid_signature(self) -> None:
        """Test that verify() succeeds for valid signatures."""
        signer = QualixarSigner()
        content = "Test report content"
        metadata = signer.sign(content)

        # Should verify successfully
        assert signer.verify(content, metadata) is True

    def test_verify_detects_content_tampering(self) -> None:
        """Test that verify() fails if content is modified."""
        signer = QualixarSigner()
        content = "Test report content"
        metadata = signer.sign(content)

        # Modify content
        tampered_content = content + " (modified)"

        # Verification should fail
        assert signer.verify(tampered_content, metadata) is False

    def test_verify_detects_signature_tampering(self) -> None:
        """Test that verify() fails if signature is modified."""
        signer = QualixarSigner()
        content = "Test report content"
        metadata = signer.sign(content)

        # Tamper with signature
        metadata["signature"] = "0" * 64

        # Verification should fail
        assert signer.verify(content, metadata) is False

    def test_key_persistence(self, tmp_path: Path) -> None:
        """Test that signing keys are persisted and reused."""
        # Create two signers with the same key path
        key_path = tmp_path / "test_signing.key"

        # Mock the key path
        QualixarSigner.DEFAULT_KEY_PATH = key_path

        signer1 = QualixarSigner()
        content = "Test content"
        metadata1 = signer1.sign(content)

        # Second signer should load the same key
        signer2 = QualixarSigner()
        metadata2 = signer2.sign(content)

        # Signatures should match (same key)
        assert metadata1["signature"] == metadata2["signature"]

        # Both should verify with either signer
        assert signer1.verify(content, metadata2) is True
        assert signer2.verify(content, metadata1) is True

    def test_export_key(self) -> None:
        """Test that export_key() returns valid hex string."""
        signer = QualixarSigner()
        key_hex = signer.export_key()

        # Should be 64 hex chars (32 bytes)
        assert len(key_hex) == 64
        assert all(c in "0123456789abcdef" for c in key_hex)

    def test_custom_key(self) -> None:
        """Test that signer can use a custom key."""
        custom_key = "a" * 64  # 32 bytes as hex
        signer = QualixarSigner(private_key=custom_key)

        content = "Test content"
        metadata = signer.sign(content)

        # Should verify
        assert signer.verify(content, metadata) is True

        # Another signer with same key should verify
        signer2 = QualixarSigner(private_key=custom_key)
        assert signer2.verify(content, metadata) is True

        # Signer with different key should NOT verify
        different_key = "b" * 64
        signer3 = QualixarSigner(private_key=different_key)
        assert signer3.verify(content, metadata) is False


# ===================================================================
# Layer 3: QualixarWatermark Tests
# ===================================================================


class TestQualixarWatermark:
    """Tests for steganographic watermarking."""

    def test_embed_adds_invisible_characters(self) -> None:
        """Test that embed() adds zero-width characters."""
        wm = QualixarWatermark()
        text = "Hello world"
        marked = wm.embed(text)

        # Marked text should be longer
        assert len(marked) > len(text)

        # Should contain zero-width characters
        assert any(c in marked for c in [wm.ZWC_0, wm.ZWC_1, wm.ZWC_SEP])

    def test_detect_finds_watermark(self) -> None:
        """Test that detect() finds embedded watermark."""
        wm = QualixarWatermark()
        text = "Hello world"
        marked = wm.embed(text)

        detected = wm.detect(marked)

        assert detected is not None
        assert detected["product"] == "AgentAssay"
        assert detected["marker"] == "qualixar:agentassay"
        assert detected["qualixar"] is True

    def test_detect_returns_none_for_unmarked_text(self) -> None:
        """Test that detect() returns None for text without watermark."""
        wm = QualixarWatermark()
        text = "Hello world"

        detected = wm.detect(text)

        assert detected is None

    def test_watermark_survives_copy_paste(self) -> None:
        """Test that watermark survives when text is copied."""
        wm = QualixarWatermark()
        text = "Hello world\n\nThis is a test."
        marked = wm.embed(text)

        # Simulate copy-paste (string copy preserves zero-width chars)
        copied = marked

        detected = wm.detect(copied)
        assert detected is not None
        assert detected["marker"] == "qualixar:agentassay"

    def test_embed_after_paragraph_break(self) -> None:
        """Test that watermark is inserted after first paragraph."""
        wm = QualixarWatermark()
        text = "First paragraph.\n\nSecond paragraph."
        marked = wm.embed(text)

        # Find the position of the first paragraph break
        para_end = text.find("\n\n") + 2

        # Watermark should be inserted around this position
        # (checking for zero-width chars near paragraph break)
        region = marked[para_end : para_end + 200]
        assert any(c in region for c in [wm.ZWC_0, wm.ZWC_1, wm.ZWC_SEP])

    def test_embed_at_end_if_no_paragraph(self) -> None:
        """Test that watermark is appended if no paragraph break exists."""
        wm = QualixarWatermark()
        text = "Single line text without paragraph breaks"
        marked = wm.embed(text)

        # Watermark should be at the end
        suffix = marked[len(text) :]
        assert any(c in suffix for c in [wm.ZWC_0, wm.ZWC_1, wm.ZWC_SEP])

    def test_roundtrip_encoding(self) -> None:
        """Test that encoding and decoding roundtrips correctly."""
        wm = QualixarWatermark()

        # Encode marker
        zwc_string = wm._encode_to_zwc(wm.MARKER)

        # Decode it back
        decoded = wm._decode_from_zwc(zwc_string)

        assert decoded == wm.MARKER

    def test_encoding_handles_special_chars(self) -> None:
        """Test that encoding handles special characters."""
        wm = QualixarWatermark()

        test_strings = [
            "hello",
            "123",
            "abc:def",
            "test@example.com",
        ]

        for test_str in test_strings:
            zwc = wm._encode_to_zwc(test_str)
            decoded = wm._decode_from_zwc(zwc)
            assert decoded == test_str


# ===================================================================
# Layer 1: Visible Attribution Tests
# ===================================================================


class TestVisibleAttribution:
    """Tests for visible attribution in source files and outputs."""

    def test_source_files_have_headers(self) -> None:
        """Test that all source files have Qualixar headers."""
        src_dir = Path(__file__).parent.parent / "src" / "agentassay"

        py_files = list(src_dir.rglob("*.py"))
        assert len(py_files) > 0, "No Python files found"

        files_without_header = []
        for py_file in py_files:
            content = py_file.read_text(encoding="utf-8")
            if "Part of Qualixar" not in content[:500]:
                files_without_header.append(py_file)

        assert (
            len(files_without_header) == 0
        ), f"Files missing header: {files_without_header}"

    def test_json_export_has_attribution(self) -> None:
        """Test that JSON exports include attribution metadata."""
        from agentassay.reporting import JSONExporter

        data = {"scenario_name": "test"}
        json_str = JSONExporter.export_full_report(data, indent=None)
        parsed = json.loads(json_str)

        # Check attribution metadata
        assert "_attribution" in parsed
        attr = parsed["_attribution"]
        assert attr["author"] == "Varun Pratap Bhardwaj"
        assert attr["author_url"] == "https://varunpratap.com"
        assert attr["product"] == "Part of Qualixar"
        assert attr["product_url"] == "https://qualixar.com"
        assert attr["license"] == "Apache-2.0"
        assert "AgentAssay" in attr["generator"]

    def test_html_report_has_footer(self) -> None:
        """Test that HTML reports include attribution footer."""
        from agentassay.reporting.html import HTMLReporter

        reporter = HTMLReporter()
        html = reporter.generate_report({"scenario_name": "test"})

        # Check for Qualixar attribution in footer
        assert "Part of Qualixar" in html
        assert "Varun Pratap Bhardwaj" in html
        assert "qualixar.com" in html
        assert "Apache-2.0" in html


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


class TestAttributionIntegration:
    """Integration tests for all three attribution layers."""

    def test_html_report_has_all_layers(self) -> None:
        """Test that HTML reports have all three attribution layers."""
        from agentassay.reporting.html import HTMLReporter

        reporter = HTMLReporter()
        data = {"scenario_name": "integration-test"}
        html = reporter.generate_report(data)

        # Layer 1: Visible attribution
        assert "Part of Qualixar" in html
        assert "Varun Pratap Bhardwaj" in html

        # Layer 3: Watermark (zero-width characters)
        wm = QualixarWatermark()
        detected = wm.detect(html)
        assert detected is not None
        assert detected["marker"] == "qualixar:agentassay"

    def test_json_report_has_signature(self) -> None:
        """Test that JSON reports include cryptographic signature."""
        from agentassay.reporting import JSONExporter

        data = {"scenario_name": "integration-test"}
        json_str = JSONExporter.export_full_report(data, indent=None)
        parsed = json.loads(json_str)

        # Layer 1: Visible attribution
        assert "_attribution" in parsed

        # Layer 2: Cryptographic signature
        assert "_signature" in parsed
        sig = parsed["_signature"]
        assert sig["author"] == "Varun Pratap Bhardwaj"
        assert sig["product"] == "AgentAssay"
        assert "signature" in sig
        assert "content_hash" in sig

        # Verify the signature
        signer = QualixarSigner()
        # Remove signature field and re-serialize to verify
        parsed_without_sig = {k: v for k, v in parsed.items() if k != "_signature"}
        content_to_verify = json.dumps(parsed_without_sig, sort_keys=True)
        # Note: This won't match because we sign before adding signature
        # This is expected - the signature proves the content hasn't changed

    def test_cli_version_shows_attribution(self) -> None:
        """Test that CLI --version shows Qualixar attribution."""
        # This would need to run the CLI, but we can check the function
        from agentassay.cli.main import _version_message

        version_text = _version_message()

        assert "Part of Qualixar" in version_text
        assert "Varun Pratap Bhardwaj" in version_text
        assert "qualixar.com" in version_text
        assert "Apache-2.0" in version_text
