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

"""Tests for Sprint 4 — Re-Auth from Dashboard."""

import json
import os
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,
    get_credential_status,
)


@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 configured."""
    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 _get_headers():
    return {"X-API-Key": "test-key-for-auth"}


def _post_headers():
    return {"X-API-Key": "test-key-for-auth", "Origin": "http://localhost:5000"}


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


class TestCredentialFieldsEndpoint:
    def test_returns_fields_for_known_service(self, client):
        resp = client.get("/api/credentials/fields/github", headers=_get_headers())
        assert resp.status_code == 200
        data = resp.get_json()
        assert data["service_key"] == "github"
        assert "fields" in data
        assert len(data["fields"]) >= 1
        field = data["fields"][0]
        assert "env_var" in field
        assert "label" in field
        assert "is_secret" in field

    def test_unknown_service_404(self, client):
        resp = client.get("/api/credentials/fields/nonexistent", headers=_get_headers())
        assert resp.status_code == 404

    def test_fields_include_masked_value(self, client, monkeypatch):
        monkeypatch.setenv("GITHUB_TOKEN", "ghp_1234567890abcdef")
        resp = client.get("/api/credentials/fields/github", headers=_get_headers())
        data = resp.get_json()
        field = [f for f in data["fields"] if f["env_var"] == "GITHUB_TOKEN"][0]
        assert field["has_value"] is True
        assert "•" in field["masked_value"]
        # Should NOT contain the full token
        assert "1234567890" not in field["masked_value"]

    def test_fields_empty_when_not_set(self, client, monkeypatch):
        monkeypatch.delenv("GITHUB_TOKEN", raising=False)
        resp = client.get("/api/credentials/fields/github", headers=_get_headers())
        data = resp.get_json()
        field = [f for f in data["fields"] if f["env_var"] == "GITHUB_TOKEN"][0]
        assert field["has_value"] is False
        assert field["masked_value"] == ""

    def test_returns_reauth_type(self, client):
        resp = client.get("/api/credentials/fields/github", headers=_get_headers())
        data = resp.get_json()
        assert data["reauth_type"] == "simple_token"

    def test_multi_field_service(self, client):
        resp = client.get("/api/credentials/fields/email", headers=_get_headers())
        data = resp.get_json()
        assert data["reauth_type"] == "multi_field"
        assert len(data["fields"]) >= 4  # EMAIL_ADDRESS, PASSWORD, IMAP_SERVER, IMAP_PORT


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


class TestCredentialUpdateEndpoint:
    def test_update_with_valid_credentials(self, client, monkeypatch):
        with (
            patch(
                "familiar.core.credential_registry.validate_github",
                return_value=(True, "OK (HTTP 200)"),
            ),
            patch(
                "familiar.channels.connect_wizard._helpers._update_env_file",
            ) as mock_env,
        ):
            resp = client.post(
                "/api/credentials/update/github",
                headers={**_post_headers(), "Content-Type": "application/json"},
                data=json.dumps({"values": {"GITHUB_TOKEN": "ghp_new_valid_token"}}),
            )
        assert resp.status_code == 200
        data = resp.get_json()
        assert data["valid"] is True
        assert data["saved"] is True
        # Env var should be set in process
        assert os.environ.get("GITHUB_TOKEN") == "ghp_new_valid_token"

    def test_update_with_invalid_credentials_rejected(self, client, monkeypatch):
        old_token = "ghp_original"
        monkeypatch.setenv("GITHUB_TOKEN", old_token)
        with patch(
            "familiar.core.credential_registry.validate_github",
            return_value=(False, "HTTP 401: Bad credentials"),
        ):
            resp = client.post(
                "/api/credentials/update/github",
                headers={**_post_headers(), "Content-Type": "application/json"},
                data=json.dumps({"values": {"GITHUB_TOKEN": "ghp_bad_token"}}),
            )
        assert resp.status_code == 422
        data = resp.get_json()
        assert data["valid"] is False
        assert "Validation failed" in data["error"]
        # Should restore old value
        assert os.environ.get("GITHUB_TOKEN") == old_token

    def test_update_unknown_service(self, client):
        resp = client.post(
            "/api/credentials/update/nonexistent",
            headers={**_post_headers(), "Content-Type": "application/json"},
            data=json.dumps({"values": {"FOO": "bar"}}),
        )
        assert resp.status_code == 404

    def test_update_no_values(self, client):
        resp = client.post(
            "/api/credentials/update/github",
            headers={**_post_headers(), "Content-Type": "application/json"},
            data=json.dumps({}),
        )
        assert resp.status_code == 400

    def test_update_invalid_env_keys_rejected(self, client):
        resp = client.post(
            "/api/credentials/update/github",
            headers={**_post_headers(), "Content-Type": "application/json"},
            data=json.dumps({"values": {"WRONG_KEY": "value"}}),
        )
        assert resp.status_code == 400
        data = resp.get_json()
        assert "Invalid keys" in data["error"]

    def test_update_clears_attention_on_success(self, client, monkeypatch):
        # First make it fail so attention_needed is set
        monkeypatch.setenv("GITHUB_TOKEN", "ghp_bad")
        with patch(
            "familiar.core.credential_registry.validate_github",
            return_value=(False, "401"),
        ):
            ensure_credentials("github")

        status = get_credential_status("github")
        assert status.attention_needed is True

        # Now update with valid credentials
        with (
            patch(
                "familiar.core.credential_registry.validate_github",
                return_value=(True, "OK"),
            ),
            patch(
                "familiar.channels.connect_wizard._helpers._update_env_file",
            ),
        ):
            resp = client.post(
                "/api/credentials/update/github",
                headers={**_post_headers(), "Content-Type": "application/json"},
                data=json.dumps({"values": {"GITHUB_TOKEN": "ghp_new_valid"}}),
            )
        assert resp.status_code == 200
        status = get_credential_status("github")
        assert status.attention_needed is False

    def test_update_multi_field(self, client, monkeypatch):
        """Multi-field services like email should accept multiple values."""
        values = {
            "EMAIL_ADDRESS": "test@example.com",
            "EMAIL_PASSWORD": "secret",
            "EMAIL_IMAP_SERVER": "imap.example.com",
            "EMAIL_IMAP_PORT": "993",
        }
        with (
            patch(
                "familiar.core.credential_registry.validate_email",
                return_value=(True, "Connected"),
            ),
            patch(
                "familiar.channels.connect_wizard._helpers._update_env_file",
            ),
        ):
            resp = client.post(
                "/api/credentials/update/email",
                headers={**_post_headers(), "Content-Type": "application/json"},
                data=json.dumps({"values": values}),
            )
        assert resp.status_code == 200
        data = resp.get_json()
        assert data["valid"] is True

    def test_restore_on_validation_failure_no_previous(self, client, monkeypatch):
        """If env var wasn't set before and validation fails, it should be removed."""
        monkeypatch.delenv("GITHUB_TOKEN", raising=False)
        with patch(
            "familiar.core.credential_registry.validate_github",
            return_value=(False, "Invalid"),
        ):
            client.post(
                "/api/credentials/update/github",
                headers={**_post_headers(), "Content-Type": "application/json"},
                data=json.dumps({"values": {"GITHUB_TOKEN": "ghp_bad"}}),
            )
        # Should not leave the bad value in env
        assert os.environ.get("GITHUB_TOKEN") is None
