"""Tests for the saved searches API (app/api/saved_searches.py)."""

import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool

from app.database import Base, get_db
from app.models import SavedSearch

# ---------------------------------------------------------------------------
# Test data constants
# ---------------------------------------------------------------------------

_OWNER = "test_user@example.com"
_OTHER_OWNER = "other_user@example.com"


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


@pytest.fixture()
def int_engine():
    """In-memory SQLite engine for integration tests."""
    engine = create_engine(
        "sqlite:///:memory:",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    Base.metadata.create_all(bind=engine)
    yield engine
    Base.metadata.drop_all(bind=engine)


@pytest.fixture()
def int_session(int_engine):
    """DB session scoped to one test."""
    Session = sessionmaker(bind=int_engine)
    session = Session()
    yield session
    session.close()


def _make_client(int_engine, owner_id: str = _OWNER):
    """Return a TestClient with *owner_id* injected as the authenticated user."""
    from unittest.mock import patch

    from app.main import app

    def override_db():
        Session = sessionmaker(bind=int_engine)
        session = Session()
        try:
            yield session
        finally:
            session.close()

    app.dependency_overrides[get_db] = override_db
    with patch("app.api.saved_searches._get_user_id", return_value=owner_id):
        with TestClient(app, base_url="http://localhost", raise_server_exceptions=False) as client:
            yield client
    app.dependency_overrides.clear()


@pytest.fixture()
def int_client(int_engine):
    """TestClient authenticated as _OWNER."""
    yield from _make_client(int_engine, _OWNER)


# ---------------------------------------------------------------------------
# CRUD tests
# ---------------------------------------------------------------------------


@pytest.mark.integration
class TestSavedSearchesAPI:
    """Tests for Saved Searches endpoints."""

    def test_list_saved_searches_empty(self, int_client):
        """No saved searches returns empty list."""
        resp = int_client.get("/api/saved-searches")
        assert resp.status_code == 200
        assert resp.json() == []

    def test_create_saved_search(self, int_client):
        """Create a saved search and verify the response."""
        payload = {"name": "My Invoices", "filters": {"tags": "invoice", "document_type": "Invoice"}}
        resp = int_client.post("/api/saved-searches", json=payload)
        assert resp.status_code == 201
        data = resp.json()
        assert data["name"] == "My Invoices"
        assert data["filters"] == {"tags": "invoice", "document_type": "Invoice"}
        assert "id" in data

    def test_create_saved_search_invalid_filters(self, int_client):
        """Creating with invalid filters returns 422."""
        # Missing filters parameter (or empty after sanitization)
        payload = {"name": "My Invoices", "filters": {}}
        resp = int_client.post("/api/saved-searches", json=payload)
        assert resp.status_code == 422

        # Invalid filters format
        payload2 = {"name": "My Invoices", "filters": "not_a_dict"}
        resp2 = int_client.post("/api/saved-searches", json=payload2)
        assert resp2.status_code == 422

    def test_create_saved_search_duplicate(self, int_client):
        """Creating a duplicate named search returns 409."""
        payload = {"name": "Duplicate", "filters": {"q": "test"}}
        int_client.post("/api/saved-searches", json=payload)
        resp = int_client.post("/api/saved-searches", json=payload)
        assert resp.status_code == 409

    def test_create_saved_search_limit(self, int_client, int_session):
        """Exceeding MAX_SAVED_SEARCHES_PER_USER returns 409."""
        # Create 50 searches using the API to ensure they are visible
        for i in range(50):
            resp = int_client.post("/api/saved-searches", json={"name": f"Search LIMIT {i}", "filters": {"q": "test"}})
            assert resp.status_code == 201

        payload = {"name": "One too many", "filters": {"q": "test"}}
        resp = int_client.post("/api/saved-searches", json=payload)
        assert resp.status_code == 409

    def test_update_saved_search(self, int_client):
        """Update an existing saved search."""
        payload = {"name": "Original Name", "filters": {"q": "test"}}
        created = int_client.post("/api/saved-searches", json=payload).json()
        search_id = created["id"]

        update_payload = {"name": "Updated Name", "filters": {"tags": "new"}}
        resp = int_client.put(f"/api/saved-searches/{search_id}", json=update_payload)
        assert resp.status_code == 200
        data = resp.json()
        assert data["name"] == "Updated Name"
        assert data["filters"] == {"tags": "new"}

    def test_update_saved_search_not_found(self, int_client):
        """Updating a non-existent search returns 404."""
        update_payload = {"name": "Updated Name"}
        resp = int_client.put("/api/saved-searches/999", json=update_payload)
        assert resp.status_code == 404

    def test_update_saved_search_duplicate_name(self, int_client):
        """Updating name to an existing search name returns 409."""
        payload1 = {"name": "Search 1", "filters": {"q": "a"}}
        payload2 = {"name": "Search 2", "filters": {"q": "b"}}
        int_client.post("/api/saved-searches", json=payload1)
        created2 = int_client.post("/api/saved-searches", json=payload2).json()
        search2_id = created2["id"]

        update_payload = {"name": "Search 1"}
        resp = int_client.put(f"/api/saved-searches/{search2_id}", json=update_payload)
        assert resp.status_code == 409

    def test_delete_saved_search(self, int_client, int_session):
        """Delete an existing search."""
        payload = {"name": "To be deleted", "filters": {"q": "test"}}
        created = int_client.post("/api/saved-searches", json=payload).json()
        search_id = created["id"]

        resp = int_client.delete(f"/api/saved-searches/{search_id}")
        assert resp.status_code == 204

        assert int_session.query(SavedSearch).filter(SavedSearch.id == search_id).first() is None

    def test_delete_saved_search_not_found(self, int_client):
        """Deleting a non-existent search returns 404."""
        resp = int_client.delete("/api/saved-searches/999")
        assert resp.status_code == 404

    def test_other_users_searches_isolated(self, int_engine, int_session):
        """Users only see and can only modify their own saved searches."""
        int_session.add(SavedSearch(user_id=_OTHER_OWNER, name="Other Search", filters='{"q": "test"}'))
        int_session.commit()

        client = next(_make_client(int_engine, _OWNER))
        resp = client.get("/api/saved-searches")
        assert resp.status_code == 200
        assert len(resp.json()) == 0

        other_search = int_session.query(SavedSearch).first()
        resp = client.put(f"/api/saved-searches/{other_search.id}", json={"name": "Hacked"})
        assert resp.status_code == 404

        resp = client.delete(f"/api/saved-searches/{other_search.id}")
        assert resp.status_code == 404
