# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# Licensed under the MIT License

"""Tests for Sprint 2 — Startup Validation + Health Integration."""

import threading
import time
from unittest.mock import MagicMock, patch

import pytest

from familiar.core.credential_health import (
    CredentialStatus,
    ensure_credentials,
    get_credential_status,
)
from familiar.core.credential_health import (
    _reset_for_testing as reset_health,
)
from familiar.core.health import (
    DependencyHealth,
    DependencyType,
    HealthChecker,
    get_credential_health_summary,
    register_credential_checks,
    validate_credentials_background,
)


@pytest.fixture(autouse=True)
def clean_state(tmp_path, monkeypatch):
    """Reset credential health state and redirect status file."""
    reset_health()
    status_file = tmp_path / "credential_status.json"
    monkeypatch.setattr("familiar.core.credential_health._STATUS_FILE", status_file)
    yield
    reset_health()


# ── DependencyType.CREDENTIAL ───────────────────────────────────────────────


class TestDependencyTypeCredential:
    def test_credential_enum_exists(self):
        assert DependencyType.CREDENTIAL == "credential"

    def test_credential_enum_in_set(self):
        assert DependencyType.CREDENTIAL in DependencyType


# ── register_credential_checks ──────────────────────────────────────────────


class TestRegisterCredentialChecks:
    def test_registers_configured_services(self, monkeypatch):
        monkeypatch.setenv("GITHUB_TOKEN", "ghp_test")
        hc = HealthChecker()
        n = register_credential_checks(hc)
        assert n >= 1
        assert "cred_github" in hc._dependency_checks

    def test_skips_unconfigured_services(self, monkeypatch):
        monkeypatch.delenv("HUBSPOT_ACCESS_TOKEN", raising=False)
        hc = HealthChecker()
        register_credential_checks(hc)
        assert "cred_hubspot" not in hc._dependency_checks

    def test_none_type_services_registered(self):
        """NONE credential types (ollama, websearch) are always configured."""
        hc = HealthChecker()
        register_credential_checks(hc)
        assert "cred_websearch" in hc._dependency_checks

    def test_check_returns_dependency_health(self, monkeypatch):
        monkeypatch.setenv("GITHUB_TOKEN", "ghp_test")
        hc = HealthChecker()
        register_credential_checks(hc)
        # Run the check
        check_fn = hc._dependency_checks["cred_github"]
        result = check_fn()
        assert isinstance(result, DependencyHealth)
        assert result.details["service_key"] == "github"

    def test_check_shows_valid_after_ensure(self, monkeypatch):
        monkeypatch.setenv("GITHUB_TOKEN", "ghp_test")
        with patch(
            "familiar.core.credential_registry.validate_github",
            return_value=(True, "OK"),
        ):
            ensure_credentials("github")

        hc = HealthChecker()
        register_credential_checks(hc)
        check_fn = hc._dependency_checks["cred_github"]
        result = check_fn()
        assert result.healthy is True
        assert result.details["valid"] is True

    def test_check_shows_attention_after_failure(self, monkeypatch):
        monkeypatch.setenv("GITHUB_TOKEN", "ghp_bad")
        with patch(
            "familiar.core.credential_registry.validate_github",
            return_value=(False, "401 Unauthorized"),
        ):
            ensure_credentials("github")

        hc = HealthChecker()
        register_credential_checks(hc)
        check_fn = hc._dependency_checks["cred_github"]
        result = check_fn()
        # Untested/attention credentials are still "healthy" from the health
        # system's perspective (degraded, not broken) — valid=None counts as ok.
        # But valid=False means the credential specifically failed.
        assert result.details["attention_needed"] is True
        assert result.details["valid"] is False


# ── get_credential_health_summary ───────────────────────────────────────────


class TestGetCredentialHealthSummary:
    def test_summary_keys(self):
        summary = get_credential_health_summary()
        assert "total" in summary
        assert "configured" in summary
        assert "valid" in summary
        assert "attention" in summary
        assert "untested" in summary
        assert "errors" in summary

    def test_total_matches_registry(self):
        from familiar.core.credential_registry import CREDENTIAL_REGISTRY

        summary = get_credential_health_summary()
        assert summary["total"] == len(CREDENTIAL_REGISTRY)

    def test_valid_counted_after_ensure(self, monkeypatch):
        monkeypatch.setenv("GITHUB_TOKEN", "ghp_test")
        with patch(
            "familiar.core.credential_registry.validate_github",
            return_value=(True, "OK"),
        ):
            ensure_credentials("github")

        summary = get_credential_health_summary()
        assert summary["valid"] >= 1

    def test_attention_counted_after_failure(self, monkeypatch):
        monkeypatch.setenv("GITHUB_TOKEN", "ghp_bad")
        with patch(
            "familiar.core.credential_registry.validate_github",
            return_value=(False, "401"),
        ):
            ensure_credentials("github")

        summary = get_credential_health_summary()
        assert summary["attention"] >= 1
        assert any(e["service_key"] == "github" for e in summary["errors"])


# ── validate_credentials_background ─────────────────────────────────────────


class TestValidateCredentialsBackground:
    def test_thread_starts_and_completes(self, monkeypatch):
        # Clear keys so it just marks them unconfigured (fast, no network)
        for key in ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GITHUB_TOKEN"]:
            monkeypatch.delenv(key, raising=False)

        result_holder = {}

        def on_done(result):
            result_holder["result"] = result

        t = validate_credentials_background(category="security", callback=on_done)
        t.join(timeout=10)
        assert not t.is_alive()
        assert "result" in result_holder
        assert "total" in result_holder["result"]

    def test_thread_is_daemon(self, monkeypatch):
        for key in ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GITHUB_TOKEN"]:
            monkeypatch.delenv(key, raising=False)

        t = validate_credentials_background(category="security")
        assert t.daemon is True
        t.join(timeout=10)

    def test_category_filter(self, monkeypatch):
        for key in ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GITHUB_TOKEN"]:
            monkeypatch.delenv(key, raising=False)

        result_holder = {}
        t = validate_credentials_background(
            category="llm", callback=lambda r: result_holder.update(r)
        )
        t.join(timeout=10)
        assert result_holder["total"] >= 4

    def test_handles_exception_gracefully(self, monkeypatch):
        """Should not raise even if validate_all fails."""
        with patch(
            "familiar.core.credential_health.validate_all",
            side_effect=RuntimeError("test error"),
        ):
            t = validate_credentials_background()
            t.join(timeout=10)
            assert not t.is_alive()  # Completed without crashing


# ── Agent startup integration ───────────────────────────────────────────────


class TestAgentStartupIntegration:
    def test_validate_credentials_on_startup_exists(self):
        """Agent should have the startup method."""
        from familiar.core.agent import Agent

        assert hasattr(Agent, "_validate_credentials_on_startup")

    def test_health_checker_has_credential_type(self):
        """HealthChecker should support CREDENTIAL dependency type."""
        hc = HealthChecker()

        def dummy():
            return DependencyHealth(name="test", healthy=True)

        hc.register_dependency_check("cred_test", dummy, DependencyType.CREDENTIAL)
        assert "cred_test" in hc._dependency_checks
