# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""Tests for OSINT authorization cache and tool-executor gating."""

import time
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

# ---------------------------------------------------------------------------
# Authorization gate tests
# ---------------------------------------------------------------------------


@pytest.fixture(autouse=True)
def isolated_auth_db(tmp_path):
    """Redirect the OSINT auth DB to a temp directory for each test."""
    db_path = tmp_path / "test_auth.db"
    with patch("familiar.skills.osint.auth._get_db_path", return_value=db_path):
        yield db_path


class TestIsAuthorized:
    def test_is_authorized_false_when_no_records(self):
        from familiar.skills.osint.auth import is_authorized

        assert is_authorized("example.com") is False

    def test_authorize_target_creates_record(self):
        from familiar.skills.osint.auth import authorize_target

        result = authorize_target("example.com", "research", "passive")
        assert isinstance(result, dict)
        assert "id" in result
        assert result["id"] is not None

    def test_is_authorized_true_after_authorize(self):
        from familiar.skills.osint.auth import authorize_target, is_authorized

        authorize_target("example.com", "research", "passive")
        assert is_authorized("example.com") is True

    def test_is_authorized_false_after_ttl_expires(self):
        from familiar.skills.osint.auth import authorize_target, is_authorized

        # Authorize now, then mock time to be 2 seconds past TTL
        authorize_target("expiring.com", "test", "passive", ttl=10)

        with patch("familiar.skills.osint.auth.time") as mock_time:
            mock_time.time.return_value = time.time() + 20  # 20s later, past 10s TTL
            result = is_authorized("expiring.com")
            assert result is False

    def test_is_authorized_case_insensitive(self):
        from familiar.skills.osint.auth import authorize_target, is_authorized

        authorize_target("EXAMPLE.COM", "research", "passive")
        assert is_authorized("example.com") is True

    def test_is_authorized_strips_whitespace(self):
        from familiar.skills.osint.auth import authorize_target, is_authorized

        authorize_target(" example.com ", "research", "passive")
        assert is_authorized("example.com") is True

    def test_authorize_returns_correct_fields(self):
        from familiar.skills.osint.auth import authorize_target

        result = authorize_target("target.com", "due diligence", "passive recon")
        assert "id" in result
        assert "target" in result
        assert "justification" in result
        assert "scope" in result
        assert "tool_scope" in result
        assert "expires_in" in result
        assert "expires_at_iso" in result

    def test_authorize_tool_scope_wildcard(self):
        from familiar.skills.osint.auth import authorize_target, is_authorized

        authorize_target("t.com", "research", "full", tool_scope="*")
        assert is_authorized("t.com", "any_tool") is True

    def test_authorize_tool_scope_specific(self):
        from familiar.skills.osint.auth import authorize_target, is_authorized

        authorize_target("t.com", "research", "email only", tool_scope="harvest_emails")
        assert is_authorized("t.com", "harvest_emails") is True
        assert is_authorized("t.com", "shodan_lookup") is False

    def test_list_authorizations_empty(self):
        from familiar.skills.osint.auth import list_authorizations

        result = list_authorizations()
        assert result == []

    def test_list_authorizations_shows_active(self):
        from familiar.skills.osint.auth import authorize_target, list_authorizations

        authorize_target("alpha.com", "research", "passive")
        authorize_target("beta.com", "due diligence", "passive")
        result = list_authorizations()
        assert len(result) == 2

    def test_list_authorizations_include_expired_true(self):
        from familiar.skills.osint.auth import authorize_target, list_authorizations

        # Add an authorization with very short TTL, then mock time forward
        authorize_target("expired.com", "old research", "passive", ttl=1)

        # Mock time so the record is expired when querying without include_expired
        with patch("familiar.skills.osint.auth.time") as mock_time:
            mock_time.time.return_value = time.time() + 100  # far in the future
            active_only = list_authorizations(include_expired=False)
            all_records = list_authorizations(include_expired=True)

        assert len(active_only) == 0
        assert len(all_records) >= 1


class TestRevokeAndPurge:
    def test_revoke_target_removes_records(self):
        from familiar.skills.osint.auth import authorize_target, is_authorized, revoke_target

        authorize_target("revokeme.com", "research", "passive")
        authorize_target("revokeme.com", "follow-up", "active")

        revoke_target("revokeme.com")
        assert is_authorized("revokeme.com") is False


# ---------------------------------------------------------------------------
# Tool gating tests
# ---------------------------------------------------------------------------


class TestOsintGatedTools:
    def test_osint_gated_tools_is_frozenset(self):
        from familiar.core.tool_executor import OSINT_GATED_TOOLS

        assert isinstance(OSINT_GATED_TOOLS, frozenset)

    def test_osint_gated_tools_contains_expected(self):
        from familiar.core.tool_executor import OSINT_GATED_TOOLS

        assert "harvest_emails" in OSINT_GATED_TOOLS
        assert "shodan_lookup" in OSINT_GATED_TOOLS
        assert "search_breach_data" in OSINT_GATED_TOOLS

    def test_is_private_target_rfc1918(self):
        from familiar.core.tool_executor import _is_private_target

        assert _is_private_target("192.168.1.1") is True

    def test_is_private_target_cidr(self):
        from familiar.core.tool_executor import _is_private_target

        assert _is_private_target("10.0.0.0/8") is True

    def test_is_private_target_public(self):
        from familiar.core.tool_executor import _is_private_target

        assert _is_private_target("8.8.8.8") is False

    def test_check_osint_auth_returns_empty_for_non_gated_tool(self):
        from familiar.core.tool_executor import _check_osint_authorization

        result = _check_osint_authorization("web_search", {}, None)
        # Non-gated tool: returns empty string (allowed)
        assert result == ""

    def test_check_osint_auth_returns_error_dict_when_not_authorized(self, tmp_path):
        from familiar.core.tool_executor import _check_osint_authorization

        db_path = tmp_path / "gate_test.db"
        with patch("familiar.skills.osint.auth._get_db_path", return_value=db_path):
            result = _check_osint_authorization("harvest_emails", {"domain": "evil.com"}, None)
        # Should return a non-empty string indicating authorization is required
        assert isinstance(result, str)
        assert len(result) > 0
        assert "evil.com" in result or "authorization" in result.lower()
