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

"""Tests for Sprint 3 — Dashboard Settings Tab Credential Cards."""

import json
from unittest.mock import MagicMock, patch

import pytest

from familiar.core.credential_health import (
    _reset_for_testing as reset_health,
)
from familiar.core.credential_health import (
    ensure_credentials,
)


@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()


@pytest.fixture
def client(monkeypatch):
    """Create a Flask test client with auth bypassed."""
    monkeypatch.setenv("FAMILIAR_DASHBOARD_KEY", "test-key-for-auth")
    from familiar.dashboard.app import app

    app.config["TESTING"] = True
    with app.test_client() as c:
        yield c


def _auth_headers(post=False):
    h = {"X-API-Key": "test-key-for-auth"}
    if post:
        h["Origin"] = "http://localhost:5000"
    return h


# ── GET /api/credentials/status ─────────────────────────────────────────────


class TestCredentialsStatusEndpoint:
    def test_returns_all_credentials(self, client):
        resp = client.get("/api/credentials/status", headers=_auth_headers())
        assert resp.status_code == 200
        data = resp.get_json()
        assert "credentials" in data
        assert len(data["credentials"]) > 0
        # Check structure of first credential
        cred = data["credentials"][0]
        assert "service_key" in cred
        assert "display_name" in cred
        assert "category" in cred
        assert "configured" in cred
        assert "valid" in cred
        assert "attention_needed" in cred

    def test_filter_by_category(self, client):
        resp = client.get("/api/credentials/status?category=llm", headers=_auth_headers())
        data = resp.get_json()
        creds = data["credentials"]
        assert len(creds) >= 4
        assert all(c["category"] == "llm" for c in creds)

    def test_no_credentials_for_unknown_category(self, client):
        resp = client.get("/api/credentials/status?category=nonexistent", headers=_auth_headers())
        data = resp.get_json()
        assert data["credentials"] == []

    def test_includes_spec_metadata(self, client):
        resp = client.get("/api/credentials/status", headers=_auth_headers())
        data = resp.get_json()
        github = [c for c in data["credentials"] if c["service_key"] == "github"]
        assert len(github) == 1
        assert github[0]["credential_type"] == "api_key"
        assert github[0]["reauth_type"] == "simple_token"
        assert "doc_url" in github[0]


# ── GET /api/credentials/status/<service_key> ───────────────────────────────


class TestCredentialStatusSingleEndpoint:
    def test_known_service(self, client):
        resp = client.get("/api/credentials/status/github", headers=_auth_headers())
        assert resp.status_code == 200
        data = resp.get_json()
        assert data["service_key"] == "github"
        assert "GitHub" in data["display_name"]

    def test_unknown_service(self, client):
        resp = client.get("/api/credentials/status/nonexistent", headers=_auth_headers())
        assert resp.status_code == 404
        data = resp.get_json()
        assert "error" in data


# ── POST /api/credentials/validate/<service_key> ────────────────────────────


class TestCredentialValidateEndpoint:
    def test_validate_trigger(self, client, monkeypatch):
        monkeypatch.setenv("GITHUB_TOKEN", "ghp_test")
        with patch(
            "familiar.core.credential_registry.validate_github",
            return_value=(True, "OK (HTTP 200)"),
        ):
            resp = client.post("/api/credentials/validate/github", headers=_auth_headers(post=True))
        assert resp.status_code == 200
        data = resp.get_json()
        assert data["valid"] is True
        assert data["service_key"] == "github"
        assert data["last_validated"] is not None

    def test_validate_unknown_service(self, client):
        resp = client.post(
            "/api/credentials/validate/nonexistent", headers=_auth_headers(post=True)
        )
        assert resp.status_code == 404

    def test_validate_forces_recheck(self, client, monkeypatch):
        """validate endpoint should invalidate cache and re-check."""
        monkeypatch.setenv("GITHUB_TOKEN", "ghp_test")
        call_count = [0]

        def mock_validate():
            call_count[0] += 1
            return (True, "OK")

        with patch(
            "familiar.core.credential_registry.validate_github",
            side_effect=mock_validate,
        ):
            # First call
            client.post("/api/credentials/validate/github", headers=_auth_headers(post=True))
            # Second call — should still validate (not cached)
            client.post("/api/credentials/validate/github", headers=_auth_headers(post=True))
        assert call_count[0] == 2

    def test_validate_returns_failure(self, client, monkeypatch):
        monkeypatch.setenv("GITHUB_TOKEN", "ghp_expired")
        with patch(
            "familiar.core.credential_registry.validate_github",
            return_value=(False, "HTTP 401: Bad credentials"),
        ):
            resp = client.post("/api/credentials/validate/github", headers=_auth_headers(post=True))
        data = resp.get_json()
        assert data["valid"] is False
        assert data["attention_needed"] is True
        assert "401" in data["last_error"]


# ── GET /api/credentials/health ─────────────────────────────────────────────


class TestCredentialsHealthEndpoint:
    def test_health_summary(self, client):
        resp = client.get("/api/credentials/health", headers=_auth_headers())
        assert resp.status_code == 200
        data = resp.get_json()
        assert "total" in data
        assert "configured" in data
        assert "valid" in data
        assert "attention" in data
