"""Tests for Architecture Linter v2.

Validates that the linter correctly detects violations and passes clean repos.
Refs: #437 — test-backed linter.
"""

import json
import sys
import tempfile
from pathlib import Path

# Add scripts/ to path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))

from architecture_linter_v2 import Linter, LinterResult


# ── helpers ───────────────────────────────────────────────────────────

def _make_repo(tmpdir: str, files: dict[str, str], name: str = "test-repo") -> Path:
    """Create a fake repo with given files and return its path."""
    repo = Path(tmpdir) / name
    repo.mkdir()
    for relpath, content in files.items():
        p = repo / relpath
        p.parent.mkdir(parents=True, exist_ok=True)
        p.write_text(content)
    return repo


def _run(tmpdir, files, name="test-repo"):
    repo = _make_repo(tmpdir, files, name)
    return Linter(str(repo)).run()


# ── clean repo passes ─────────────────────────────────────────────────

def test_clean_repo_passes():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {
            "README.md": "# Test Repo\n\nThis is a clean test repo with sufficient content to pass.",
            "main.py": "print('hello world')\n",
        })
        assert result.passed, f"Expected pass but got: {result.errors}"
        assert result.violation_count == 0


# ── missing README ────────────────────────────────────────────────────

def test_missing_readme_fails():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {"main.py": "x = 1\n"})
        assert not result.passed
        assert any("README" in e for e in result.errors)


def test_short_readme_warns():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {"README.md": "hi\n"})
        # Warnings don't fail the build
        assert result.passed
        assert any("short" in w.lower() for w in result.warnings)


# ── hardcoded IPs ─────────────────────────────────────────────────────

def test_hardcoded_public_ip_detected():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {
            "README.md": "# R\n\nGood repo.",
            "server.py": "HOST = '203.0.113.42'\n",
        })
        assert not result.passed
        assert any("203.0.113.42" in e for e in result.errors)


def test_localhost_ip_ignored():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {
            "README.md": "# R\n\nGood repo.",
            "server.py": "HOST = '127.0.0.1'\n",
        })
        ip_errors = [e for e in result.errors if "IP" in e]
        assert len(ip_errors) == 0


# ── API keys ──────────────────────────────────────────────────────────

def test_openai_key_detected():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {
            "README.md": "# R\n\nGood repo.",
            "config.py": 'key = "sk-abcdefghijklmnopqrstuvwx"\n',
        })
        assert not result.passed
        assert any("secret" in e.lower() or "key" in e.lower() for e in result.errors)


def test_aws_key_detected():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {
            "README.md": "# R\n\nGood repo.",
            "deploy.yaml": 'aws_key: AKIAIOSFODNN7EXAMPLE\n',
        })
        assert not result.passed
        assert any("secret" in e.lower() for e in result.errors)


def test_env_example_skipped():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {
            "README.md": "# R\n\nGood repo.",
            ".env.example": 'OPENAI_KEY=sk-placeholder\n',
        })
        secret_errors = [e for e in result.errors if "secret" in e.lower()]
        assert len(secret_errors) == 0


# ── sovereignty rules (v1 cloud API checks) ───────────────────────────

def test_openai_url_detected():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {
            "README.md": "# R\n\nGood repo.",
            "app.py": 'url = "https://api.openai.com/v1/chat"\n',
        })
        assert not result.passed
        assert any("openai" in e.lower() for e in result.errors)


def test_cloud_provider_detected():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {
            "README.md": "# R\n\nGood repo.",
            "config.yaml": "provider: openai\n",
        })
        assert not result.passed
        assert any("provider" in e.lower() for e in result.errors)


# ── sidecar boundary ──────────────────────────────────────────────────

def test_sovereign_keyword_in_hermes_agent_fails():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {
            "README.md": "# R\n\nGood repo.",
            "index.py": "import mempalace\n",
        }, name="hermes-agent")
        assert not result.passed
        assert any("sidecar" in e.lower() or "mempalace" in e.lower() for e in result.errors)


def test_sovereign_keyword_in_other_repo_ok():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {
            "README.md": "# R\n\nGood repo.",
            "index.py": "import mempalace\n",
        }, name="some-other-repo")
        sidecar_errors = [e for e in result.errors if "sidecar" in e.lower()]
        assert len(sidecar_errors) == 0


# ── SOUL.md canonical location ────────────────────────────────────────

def test_soul_md_required_in_timmy_config():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {
            "README.md": "# timmy-config\n\nConfig repo.",
        }, name="timmy-config")
        assert not result.passed
        assert any("SOUL.md" in e for e in result.errors)


def test_soul_md_present_in_timmy_config_ok():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {
            "README.md": "# timmy-config\n\nConfig repo.",
            "SOUL.md": "# Soul\n\nCanonical identity document.",
        }, name="timmy-config")
        soul_errors = [e for e in result.errors if "SOUL" in e]
        assert len(soul_errors) == 0


def test_soul_md_in_wrong_repo_fails():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {
            "README.md": "# R\n\nGood repo.",
            "SOUL.md": "# Soul\n\nShould not be here.",
        }, name="other-repo")
        assert any("canonical" in e.lower() for e in result.errors)


# ── LinterResult structure ────────────────────────────────────────────

def test_result_summary_is_string():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {"README.md": "# OK repo with enough text here\n"})
        assert isinstance(result.summary(), str)
        assert "PASSED" in result.summary() or "FAILED" in result.summary()


def test_result_repo_name():
    with tempfile.TemporaryDirectory() as tmp:
        result = _run(tmp, {"README.md": "# OK\n"}, name="my-repo")
        assert result.repo_name == "my-repo"


# ── invalid path ──────────────────────────────────────────────────────

def test_invalid_path_raises():
    try:
        Linter("/nonexistent/path/xyz")
        assert False, "Should have raised FileNotFoundError"
    except FileNotFoundError:
        pass


# ── skip dirs ──────────────────────────────────────────────────────────

def test_git_dir_skipped():
    with tempfile.TemporaryDirectory() as tmp:
        repo = _make_repo(tmp, {
            "README.md": "# R\n\nGood repo.",
            "main.py": "x = 1\n",
        })
        # Create a .git/ dir with a bad file
        git_dir = repo / ".git"
        git_dir.mkdir()
        (git_dir / "bad.py").write_text("HOST = '203.0.113.1'\n")

        result = Linter(str(repo)).run()
        git_errors = [e for e in result.errors if ".git" in e]
        assert len(git_errors) == 0
