"""Tests for the PrivacyPal Python SDK client."""

import json
import pytest
import requests
import responses

from privacypal_sdk import (
    PrivacyPalClient,
    AuthenticationError,
    TrialExpiredError,
    NetworkError,
)

API_URL = "http://localhost:42026"
TOKEN = "test-jwt-token"


@pytest.fixture
def client():
    return PrivacyPalClient(api_url=API_URL, api_key=TOKEN)


@pytest.fixture
def unauthenticated_client():
    return PrivacyPalClient(api_url=API_URL, api_key="")


# --------------------------------------------------------------------------
# Encode
# --------------------------------------------------------------------------

class TestEncode:
    @responses.activate
    def test_encode_success(self, client):
        responses.post(
            f"{API_URL}/api/scanner/encode",
            json={
                "encodedData": "Hello Maria Garcia",
                "continuationId": "cid-001",
                "transformations": [
                    {"original": "John Doe", "twin": "Maria Garcia", "entityType": "PERSON"}
                ],
                "statistics": {"piiEntitiesDetected": 1},
            },
            status=200,
        )

        result = client.encode("Hello John Doe", source_container="test")

        assert result["encodedData"] == "Hello Maria Garcia"
        assert result["continuationId"] == "cid-001"
        assert len(result["transformations"]) == 1

        body = json.loads(responses.calls[0].request.body)
        assert body["data"] == "Hello John Doe"
        assert body["sourceContainer"] == "test"
        assert body["scoreThreshold"] == 0.35

    @responses.activate
    def test_encode_with_continuation_id(self, client):
        responses.post(f"{API_URL}/api/scanner/encode", json={"encodedData": "x"}, status=200)

        client.encode("data", continuation_id="existing-cid")

        body = json.loads(responses.calls[0].request.body)
        assert body["continuationId"] == "existing-cid"

    @responses.activate
    def test_encode_401_raises_auth_error(self, client):
        responses.post(
            f"{API_URL}/api/scanner/encode",
            json={"error": "Unauthorized", "message": "Token expired"},
            status=401,
        )

        with pytest.raises(AuthenticationError, match="401: Token expired"):
            client.encode("sensitive data")

    @responses.activate
    def test_encode_403_raises_trial_expired(self, client):
        responses.post(
            f"{API_URL}/api/scanner/encode",
            json={"message": "Trial expired", "trialExpired": True},
            status=403,
        )

        with pytest.raises(TrialExpiredError, match=r"403:.*\[Trial expired\]"):
            client.encode("sensitive data")


class TestEncodeBatch:
    @responses.activate
    def test_encode_batch_success(self, client):
        responses.post(
            f"{API_URL}/api/scanner/encode/batch",
            json={
                "continuationId": "batch-cid",
                "results": [
                    {"success": True, "encodedData": "encoded-1"},
                    {"success": True, "encodedData": "encoded-2"},
                ],
            },
            status=200,
        )

        result = client.encode_batch([
            {"data": "Alice Smith"},
            {"data": "Bob Jones"},
        ])

        assert result["continuationId"] == "batch-cid"
        assert len(result["results"]) == 2


# --------------------------------------------------------------------------
# Decode
# --------------------------------------------------------------------------

class TestDecode:
    @responses.activate
    def test_decode_success(self, client):
        responses.post(
            f"{API_URL}/api/scanner/decode",
            json={
                "decodedData": "Hello John Doe",
                "continuationId": "cid-001",
                "transformations": [],
            },
            status=200,
        )

        result = client.decode(
            continuation_id="cid-001",
            data="Hello Maria Garcia",
            sensitive_hashes=["hash-1"],
        )

        assert result["decodedData"] == "Hello John Doe"
        body = json.loads(responses.calls[0].request.body)
        assert body["continuationId"] == "cid-001"
        assert body["sensitiveHashes"] == ["hash-1"]
        assert body["authorization"]["type"] == "jwt"


class TestGetDatasetTwins:
    @responses.activate
    def test_get_twins(self, client):
        responses.get(
            f"{API_URL}/api/scanner/twins/cid-001",
            json={"twins": [{"entityType": "PERSON"}], "count": 1},
            status=200,
        )

        result = client.get_dataset_twins("cid-001")
        assert result["count"] == 1


# --------------------------------------------------------------------------
# Authentication
# --------------------------------------------------------------------------

class TestLogin:
    @responses.activate
    def test_login_success(self, unauthenticated_client):
        responses.post(
            f"{API_URL}/api/user/login",
            json={
                "code": 200,
                "data": {
                    "id": "user-1",
                    "email": "test@example.com",
                    "token": "new-jwt-token",
                },
            },
            status=200,
        )

        result = unauthenticated_client.login("test@example.com", "password123")

        assert result["data"]["token"] == "new-jwt-token"
        body = json.loads(responses.calls[0].request.body)
        assert body["email"] == "test@example.com"
        assert "x-access-token" not in responses.calls[0].request.headers

    @responses.activate
    def test_login_401(self, unauthenticated_client):
        responses.post(
            f"{API_URL}/api/user/login",
            json={"message": "Invalid credentials"},
            status=401,
        )

        with pytest.raises(AuthenticationError, match="401:"):
            unauthenticated_client.login("bad@example.com", "wrong")


class TestRegister:
    @responses.activate
    def test_register_success(self, unauthenticated_client):
        responses.post(
            f"{API_URL}/api/user/register",
            json={"token": "new-jwt", "newUserId": "user-2", "email": "new@example.com"},
            status=200,
        )

        result = unauthenticated_client.register("Jane", "Smith", "new@example.com", "Pass123!")

        assert result["token"] == "new-jwt"
        body = json.loads(responses.calls[0].request.body)
        assert body["firstName"] == "Jane"
        assert "x-access-token" not in responses.calls[0].request.headers


class TestRefreshToken:
    @responses.activate
    def test_refresh_success(self, client):
        responses.post(
            f"{API_URL}/api/user/refresh-token",
            json={"data": {"token": "refreshed-token"}},
            status=200,
        )

        result = client.refresh_user_token("old-token")
        assert result["data"]["token"] == "refreshed-token"


# --------------------------------------------------------------------------
# Account / User
# --------------------------------------------------------------------------

class TestGetAccount:
    @responses.activate
    def test_get_account(self, client):
        responses.get(
            f"{API_URL}/api/user/account",
            json={"id": "user-1", "email": "test@example.com", "tier": "pro"},
            status=200,
        )

        result = client.get_account()
        assert result["email"] == "test@example.com"
        assert result["tier"] == "pro"


class TestGetUserStats:
    @responses.activate
    def test_with_days(self, client):
        responses.get(f"{API_URL}/api/user/stats?days=7", json={"totalPrompts": 42}, status=200)

        result = client.get_user_stats(days=7)
        assert result["totalPrompts"] == 42

    @responses.activate
    def test_all_time(self, client):
        responses.get(f"{API_URL}/api/user/stats", json={"totalPrompts": 999}, status=200)

        result = client.get_user_stats()
        assert result["totalPrompts"] == 999


# --------------------------------------------------------------------------
# Company
# --------------------------------------------------------------------------

class TestGetCompany:
    @responses.activate
    def test_get_company(self, client):
        responses.get(
            f"{API_URL}/api/company",
            json={"company": {"name": "Acme", "memberCount": 5}},
            status=200,
        )

        result = client.get_company()
        assert result["name"] == "Acme"


class TestGenerateInviteLink:
    @responses.activate
    def test_generate_invite(self, client):
        responses.post(
            f"{API_URL}/api/company/invite",
            json={"inviteLink": "https://app.privacypal.io/invite/abc123"},
            status=200,
        )

        result = client.generate_invite_link()
        assert "inviteLink" in result


# --------------------------------------------------------------------------
# Health Check
# --------------------------------------------------------------------------

class TestHealthCheck:
    @responses.activate
    def test_healthy(self, client):
        responses.get(f"{API_URL}/health", json="success", status=200)

        result = client.health_check()
        assert result["success"] is True
        assert result["status"] == 200

    @responses.activate
    def test_unhealthy(self, client):
        responses.get(f"{API_URL}/health", body=ConnectionError("refused"))

        result = client.health_check()
        assert result["success"] is False
        assert "error" in result


# --------------------------------------------------------------------------
# Update Audit Tokens
# --------------------------------------------------------------------------

class TestUpdateAuditTokens:
    @responses.activate
    def test_update_tokens(self, client):
        responses.post(
            f"{API_URL}/api/company/audit-logs/update-tokens",
            json={"updated": True},
            status=200,
        )

        result = client.update_audit_tokens("cid-001", tokens_in=245, tokens_out=512, model="gpt-4")
        assert result["success"] is True


# --------------------------------------------------------------------------
# Configuration
# --------------------------------------------------------------------------

class TestConfiguration:
    def test_update_api_key(self, client):
        client.update_api_key("new-token")
        config = client.get_config()
        assert config["apiKey"] == "new-token"
        assert client._session.headers["x-access-token"] == "new-token"

    def test_update_api_url(self, client):
        client.update_api_url("https://new-api.example.com/")
        config = client.get_config()
        assert config["apiUrl"] == "https://new-api.example.com"

    def test_on_token_refresh_callback(self):
        captured = []
        c = PrivacyPalClient(api_url=API_URL, api_key="old", on_token_refresh=captured.append)
        c.update_api_key("new")
        assert captured == ["new"]


# --------------------------------------------------------------------------
# Network Error
# --------------------------------------------------------------------------

class TestNetworkError:
    @responses.activate
    def test_connection_error_raises_network_error(self, client):
        responses.get(
            f"{API_URL}/api/user/account",
            body=requests.ConnectionError("refused"),
        )

        with pytest.raises(NetworkError, match="Network Error"):
            client.get_account()


# --------------------------------------------------------------------------
# Auth header presence
# --------------------------------------------------------------------------

class TestAuthHeaders:
    @responses.activate
    def test_authenticated_requests_include_token(self, client):
        responses.get(f"{API_URL}/api/user/account", json={}, status=200)
        client.get_account()
        assert responses.calls[0].request.headers["x-access-token"] == TOKEN

    @responses.activate
    def test_login_does_not_include_token(self, unauthenticated_client):
        responses.post(f"{API_URL}/api/user/login", json={"data": {"token": "t"}}, status=200)
        unauthenticated_client.login("a@b.com", "pass")
        assert "x-access-token" not in responses.calls[0].request.headers
