"""Tests for journal entry endpoints (/api/entries/*)."""
import pytest


# ---------------------------------------------------------------------------
# Shared helpers
# ---------------------------------------------------------------------------

VALID_ENCRYPTION = {
    "encrypted": True,
    "ciphertext": "dGVzdF9jaXBoZXJ0ZXh0",  # base64("test_ciphertext")
    "nonce": "dGVzdF9ub25jZQ==",             # base64("test_nonce")
    "algorithm": "XSalsa20-Poly1305",
}


@pytest.fixture
def user(client):
    """Register and return a test user."""
    c, _ = client
    response = c.post("/api/users/register", json={"email": "entry_test@example.com"})
    return response.json()


@pytest.fixture
def entry(client, user):
    """Create and return a test entry."""
    c, _ = client
    response = c.post(f"/api/entries/{user['id']}", json={"encryption": VALID_ENCRYPTION})
    assert response.status_code == 200
    return response.json()


# ---------------------------------------------------------------------------
# POST /api/entries/{user_id}
# ---------------------------------------------------------------------------

class TestCreateEntry:
    def test_create_encrypted_entry_returns_200(self, client, user):
        c, _ = client
        response = c.post(f"/api/entries/{user['id']}", json={"encryption": VALID_ENCRYPTION})
        assert response.status_code == 200

    def test_create_entry_returns_id_and_message(self, client, user):
        c, _ = client
        response = c.post(f"/api/entries/{user['id']}", json={"encryption": VALID_ENCRYPTION})
        data = response.json()
        assert "id" in data
        assert data["message"] == "Entry created successfully"

    def test_create_entry_with_mood(self, client, user):
        c, _ = client
        response = c.post(f"/api/entries/{user['id']}", json={
            "encryption": VALID_ENCRYPTION,
            "mood": "grateful",
        })
        assert response.status_code == 200

    def test_create_entry_with_invalid_mood_returns_422(self, client, user):
        c, _ = client
        response = c.post(f"/api/entries/{user['id']}", json={
            "encryption": VALID_ENCRYPTION,
            "mood": "ecstatic",  # Not in MoodEnum
        })
        assert response.status_code == 422

    def test_create_entry_with_tags(self, client, user):
        c, _ = client
        response = c.post(f"/api/entries/{user['id']}", json={
            "encryption": VALID_ENCRYPTION,
            "tags": ["family", "gratitude"],
        })
        assert response.status_code == 200

    def test_create_entry_missing_ciphertext_returns_400(self, client, user):
        """Encryption metadata without ciphertext must be rejected."""
        c, _ = client
        response = c.post(f"/api/entries/{user['id']}", json={
            "encryption": {
                "encrypted": True,
                "nonce": "bm9uY2U=",
                "algorithm": "XSalsa20-Poly1305",
                # ciphertext intentionally missing
            }
        })
        # Pydantic requires ciphertext field → 422
        assert response.status_code == 422

    def test_create_entry_encryption_missing_nonce_returns_400(self, client, user):
        c, _ = client
        response = c.post(f"/api/entries/{user['id']}", json={
            "encryption": {
                "encrypted": True,
                "ciphertext": "dGVzdA==",
                "algorithm": "XSalsa20-Poly1305",
                # nonce intentionally missing
            }
        })
        assert response.status_code == 422

    def test_create_entry_for_nonexistent_user_returns_404(self, client):
        c, _ = client
        response = c.post("/api/entries/507f1f77bcf86cd799439011", json={"encryption": VALID_ENCRYPTION})
        assert response.status_code == 404
        assert "User not found" in response.json()["detail"]

    def test_create_entry_with_invalid_user_id_returns_400(self, client):
        c, _ = client
        response = c.post("/api/entries/not-a-valid-id", json={"encryption": VALID_ENCRYPTION})
        assert response.status_code == 400

    def test_create_entry_with_specific_entry_date(self, client, user):
        c, _ = client
        response = c.post(f"/api/entries/{user['id']}", json={
            "encryption": VALID_ENCRYPTION,
            "entryDate": "2024-06-15T00:00:00",
        })
        assert response.status_code == 200


# ---------------------------------------------------------------------------
# GET /api/entries/{user_id}
# ---------------------------------------------------------------------------

class TestGetUserEntries:
    def test_returns_entries_and_pagination(self, client, user, entry):
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}")
        assert response.status_code == 200
        data = response.json()
        assert "entries" in data
        assert "pagination" in data

    def test_returns_entry_that_was_created(self, client, user, entry):
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}")
        entries = response.json()["entries"]
        assert len(entries) == 1
        assert entries[0]["id"] == entry["id"]

    def test_entry_includes_encryption_metadata(self, client, user, entry):
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}")
        fetched_entry = response.json()["entries"][0]
        assert fetched_entry["encryption"]["ciphertext"] == VALID_ENCRYPTION["ciphertext"]
        assert fetched_entry["encryption"]["nonce"] == VALID_ENCRYPTION["nonce"]

    def test_empty_list_when_no_entries(self, client, user):
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}")
        assert response.status_code == 200
        assert response.json()["entries"] == []
        assert response.json()["pagination"]["total"] == 0

    def test_pagination_limit(self, client, user):
        c, _ = client
        for _ in range(5):
            c.post(f"/api/entries/{user['id']}", json={"encryption": VALID_ENCRYPTION})
        response = c.get(f"/api/entries/{user['id']}?limit=2&skip=0")
        assert response.status_code == 200
        data = response.json()
        assert len(data["entries"]) == 2
        assert data["pagination"]["hasMore"] is True
        assert data["pagination"]["total"] == 5

    def test_pagination_skip(self, client, user):
        c, _ = client
        for _ in range(4):
            c.post(f"/api/entries/{user['id']}", json={"encryption": VALID_ENCRYPTION})
        response = c.get(f"/api/entries/{user['id']}?limit=10&skip=3")
        assert len(response.json()["entries"]) == 1

    def test_pagination_has_more_false_at_end(self, client, user):
        c, _ = client
        for _ in range(3):
            c.post(f"/api/entries/{user['id']}", json={"encryption": VALID_ENCRYPTION})
        response = c.get(f"/api/entries/{user['id']}?limit=10&skip=0")
        assert response.json()["pagination"]["hasMore"] is False

    def test_nonexistent_user_returns_404(self, client):
        c, _ = client
        response = c.get("/api/entries/507f1f77bcf86cd799439011")
        assert response.status_code == 404

    def test_invalid_user_id_returns_400(self, client):
        c, _ = client
        response = c.get("/api/entries/bad-id")
        assert response.status_code == 400


# ---------------------------------------------------------------------------
# GET /api/entries/{user_id}/{entry_id}
# ---------------------------------------------------------------------------

class TestGetSingleEntry:
    def test_returns_entry_by_id(self, client, user, entry):
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}/{entry['id']}")
        assert response.status_code == 200
        assert response.json()["id"] == entry["id"]

    def test_returned_entry_has_encryption_field(self, client, user, entry):
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}/{entry['id']}")
        data = response.json()
        assert "encryption" in data
        assert data["encryption"]["ciphertext"] == VALID_ENCRYPTION["ciphertext"]

    def test_entry_belongs_to_correct_user(self, client, user, entry):
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}/{entry['id']}")
        assert response.json()["userId"] == user["id"]

    def test_entry_from_different_user_returns_404(self, client, user, entry):
        """User isolation: another user cannot access this entry."""
        c, _ = client
        other = c.post("/api/users/register", json={"email": "other@example.com"}).json()
        response = c.get(f"/api/entries/{other['id']}/{entry['id']}")
        assert response.status_code == 404

    def test_nonexistent_entry_returns_404(self, client, user):
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}/507f1f77bcf86cd799439099")
        assert response.status_code == 404

    def test_invalid_entry_id_returns_400(self, client, user):
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}/not-valid-id")
        assert response.status_code == 400

    def test_invalid_user_id_returns_400(self, client, entry):
        c, _ = client
        response = c.get(f"/api/entries/bad-user-id/{entry['id']}")
        assert response.status_code == 400


# ---------------------------------------------------------------------------
# PUT /api/entries/{user_id}/{entry_id}
# ---------------------------------------------------------------------------

class TestUpdateEntry:
    def test_update_mood(self, client, user, entry):
        c, _ = client
        response = c.put(f"/api/entries/{user['id']}/{entry['id']}", json={"mood": "happy"})
        assert response.status_code == 200
        assert response.json()["mood"] == "happy"

    def test_update_encryption_ciphertext(self, client, user, entry):
        c, _ = client
        new_enc = {**VALID_ENCRYPTION, "ciphertext": "bmV3Y2lwaGVydGV4dA=="}
        response = c.put(f"/api/entries/{user['id']}/{entry['id']}", json={"encryption": new_enc})
        assert response.status_code == 200
        assert response.json()["encryption"]["ciphertext"] == "bmV3Y2lwaGVydGV4dA=="

    def test_update_persists(self, client, user, entry):
        c, _ = client
        c.put(f"/api/entries/{user['id']}/{entry['id']}", json={"mood": "sad"})
        response = c.get(f"/api/entries/{user['id']}/{entry['id']}")
        assert response.json()["mood"] == "sad"

    def test_update_invalid_mood_returns_422(self, client, user, entry):
        c, _ = client
        response = c.put(f"/api/entries/{user['id']}/{entry['id']}", json={"mood": "furious"})
        assert response.status_code == 422

    def test_update_nonexistent_entry_returns_404(self, client, user):
        c, _ = client
        response = c.put(f"/api/entries/{user['id']}/507f1f77bcf86cd799439099", json={"mood": "happy"})
        assert response.status_code == 404

    def test_update_invalid_entry_id_returns_400(self, client, user):
        c, _ = client
        response = c.put(f"/api/entries/{user['id']}/bad-id", json={"mood": "happy"})
        assert response.status_code == 400


# ---------------------------------------------------------------------------
# DELETE /api/entries/{user_id}/{entry_id}
# ---------------------------------------------------------------------------

class TestDeleteEntry:
    def test_delete_entry_returns_200(self, client, user, entry):
        c, _ = client
        response = c.delete(f"/api/entries/{user['id']}/{entry['id']}")
        assert response.status_code == 200
        assert "deleted" in response.json()["message"].lower()

    def test_deleted_entry_is_not_retrievable(self, client, user, entry):
        c, _ = client
        c.delete(f"/api/entries/{user['id']}/{entry['id']}")
        response = c.get(f"/api/entries/{user['id']}/{entry['id']}")
        assert response.status_code == 404

    def test_deleted_entry_not_in_list(self, client, user, entry):
        c, _ = client
        c.delete(f"/api/entries/{user['id']}/{entry['id']}")
        response = c.get(f"/api/entries/{user['id']}")
        assert response.json()["entries"] == []

    def test_delete_entry_wrong_user_returns_404(self, client, user, entry):
        """User isolation: another user cannot delete this entry."""
        c, _ = client
        other = c.post("/api/users/register", json={"email": "other_del@example.com"}).json()
        response = c.delete(f"/api/entries/{other['id']}/{entry['id']}")
        assert response.status_code == 404

    def test_delete_nonexistent_entry_returns_404(self, client, user):
        c, _ = client
        response = c.delete(f"/api/entries/{user['id']}/507f1f77bcf86cd799439099")
        assert response.status_code == 404

    def test_delete_invalid_entry_id_returns_400(self, client, user):
        c, _ = client
        response = c.delete(f"/api/entries/{user['id']}/bad-id")
        assert response.status_code == 400


# ---------------------------------------------------------------------------
# GET /api/entries/{user_id}/by-date/{date_str}
# ---------------------------------------------------------------------------

class TestGetEntriesByDate:
    def test_returns_entry_for_matching_date(self, client, user):
        c, _ = client
        c.post(f"/api/entries/{user['id']}", json={
            "encryption": VALID_ENCRYPTION,
            "entryDate": "2024-06-15T00:00:00",
        })
        response = c.get(f"/api/entries/{user['id']}/by-date/2024-06-15")
        assert response.status_code == 200
        data = response.json()
        assert data["count"] == 1
        assert data["date"] == "2024-06-15"

    def test_returns_empty_for_date_with_no_entries(self, client, user):
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}/by-date/2020-01-01")
        assert response.status_code == 200
        assert response.json()["count"] == 0

    def test_does_not_return_entries_from_other_dates(self, client, user):
        c, _ = client
        c.post(f"/api/entries/{user['id']}", json={
            "encryption": VALID_ENCRYPTION,
            "entryDate": "2024-06-15T00:00:00",
        })
        response = c.get(f"/api/entries/{user['id']}/by-date/2024-06-16")  # Next day
        assert response.json()["count"] == 0

    def test_invalid_date_format_returns_400(self, client, user):
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}/by-date/not-a-date")
        assert response.status_code == 400

    def test_invalid_date_13th_month_returns_400(self, client, user):
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}/by-date/2024-13-01")
        assert response.status_code == 400

    def test_invalid_user_id_returns_400(self, client):
        c, _ = client
        response = c.get("/api/entries/bad-id/by-date/2024-06-15")
        assert response.status_code == 400


# ---------------------------------------------------------------------------
# GET /api/entries/{user_id}/by-month/{year}/{month}
# ---------------------------------------------------------------------------

class TestGetEntriesByMonth:
    def test_returns_entries_for_matching_month(self, client, user):
        c, _ = client
        c.post(f"/api/entries/{user['id']}", json={
            "encryption": VALID_ENCRYPTION,
            "entryDate": "2024-06-15T00:00:00",
        })
        response = c.get(f"/api/entries/{user['id']}/by-month/2024/6")
        assert response.status_code == 200
        data = response.json()
        assert data["count"] == 1
        assert data["year"] == 2024
        assert data["month"] == 6

    def test_does_not_return_entries_from_other_months(self, client, user):
        c, _ = client
        c.post(f"/api/entries/{user['id']}", json={
            "encryption": VALID_ENCRYPTION,
            "entryDate": "2024-05-10T00:00:00",  # May, not June
        })
        response = c.get(f"/api/entries/{user['id']}/by-month/2024/6")
        assert response.json()["count"] == 0

    def test_december_january_rollover_works(self, client, user):
        """Month 12 boundary (year+1 rollover) must not crash."""
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}/by-month/2024/12")
        assert response.status_code == 200

    def test_invalid_month_0_returns_400(self, client, user):
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}/by-month/2024/0")
        assert response.status_code == 400

    def test_invalid_month_13_returns_400(self, client, user):
        c, _ = client
        response = c.get(f"/api/entries/{user['id']}/by-month/2024/13")
        assert response.status_code == 400

    def test_invalid_user_id_returns_400(self, client):
        c, _ = client
        response = c.get("/api/entries/bad-id/by-month/2024/6")
        assert response.status_code == 400


# ---------------------------------------------------------------------------
# POST /api/entries/convert-timestamp/utc-to-ist
# ---------------------------------------------------------------------------

class TestConvertTimestamp:
    def test_converts_utc_z_suffix_to_ist(self, client):
        c, _ = client
        response = c.post("/api/entries/convert-timestamp/utc-to-ist", json={
            "timestamp": "2024-01-01T00:00:00Z"
        })
        assert response.status_code == 200
        data = response.json()
        assert "utc" in data
        assert "ist" in data
        assert "+05:30" in data["ist"]

    def test_ist_is_5h30m_ahead_of_utc(self, client):
        c, _ = client
        response = c.post("/api/entries/convert-timestamp/utc-to-ist", json={
            "timestamp": "2024-01-01T00:00:00Z"
        })
        assert "05:30:00+05:30" in response.json()["ist"]

    def test_missing_timestamp_field_returns_400(self, client):
        c, _ = client
        response = c.post("/api/entries/convert-timestamp/utc-to-ist", json={})
        assert response.status_code == 400

    def test_invalid_timestamp_string_returns_400(self, client):
        c, _ = client
        response = c.post("/api/entries/convert-timestamp/utc-to-ist", json={
            "timestamp": "not-a-date"
        })
        assert response.status_code == 400

    def test_returns_original_utc_in_response(self, client):
        c, _ = client
        utc = "2024-06-15T12:00:00Z"
        response = c.post("/api/entries/convert-timestamp/utc-to-ist", json={"timestamp": utc})
        assert response.json()["utc"] == utc
