"""
Tests for search API endpoint

Tests cover:
- Search API parameter validation
- Search result formatting
- Pagination with limit and offset
- Authentication-based filtering (published/unpublished)
- FTS5 availability handling
- Error cases and edge cases
"""

import pytest
from pathlib import Path

from starpunk import create_app
from starpunk.notes import create_note


@pytest.fixture
def app(tmp_path):
    """Create test application with FTS5 enabled"""
    test_data_dir = tmp_path / "data"
    test_data_dir.mkdir(parents=True, exist_ok=True)

    test_config = {
        "TESTING": True,
        "DATABASE_PATH": test_data_dir / "starpunk.db",
        "DATA_PATH": test_data_dir,
        "NOTES_PATH": test_data_dir / "notes",
        "SESSION_SECRET": "test-secret-key",
        "ADMIN_ME": "https://test.example.com",
        "SITE_URL": "https://example.com",
        "SITE_NAME": "Test Blog",
        "DEV_MODE": False,
    }
    app = create_app(config=test_config)
    return app


@pytest.fixture
def client(app):
    """Create test client"""
    return app.test_client()


@pytest.fixture
def test_notes(app):
    """Create test notes for searching"""
    with app.app_context():
        notes = []

        # Published notes
        note1 = create_note(
            content="# Python Tutorial\n\nLearn Python programming with examples.",
            published=True
        )
        notes.append(note1)

        note2 = create_note(
            content="# JavaScript Guide\n\nModern JavaScript best practices.",
            published=True
        )
        notes.append(note2)

        note3 = create_note(
            content="# Python Testing\n\nHow to write tests in Python using pytest.",
            published=True
        )
        notes.append(note3)

        # Unpublished note
        note4 = create_note(
            content="# Draft Python Article\n\nThis is unpublished.",
            published=False
        )
        notes.append(note4)

        return notes


def test_search_api_requires_query(client):
    """Test that search API requires a query parameter"""
    response = client.get("/api/search")
    assert response.status_code == 400
    data = response.get_json()
    assert "error" in data
    assert "Missing required parameter" in data["error"]


def test_search_api_rejects_empty_query(client):
    """Test that search API rejects empty query"""
    response = client.get("/api/search?q=")
    assert response.status_code == 400
    data = response.get_json()
    assert "error" in data


def test_search_api_returns_results(client, test_notes):
    """Test that search API returns matching results"""
    response = client.get("/api/search?q=python")
    assert response.status_code == 200
    data = response.get_json()

    assert data["query"] == "python"
    assert data["count"] >= 2  # Should match at least 2 Python notes
    assert len(data["results"]) >= 2

    # Check result structure
    result = data["results"][0]
    assert "slug" in result
    assert "title" in result
    assert "excerpt" in result
    assert "published_at" in result
    assert "url" in result


def test_search_api_returns_no_results_for_nonexistent(client, test_notes):
    """Test that search API returns empty results for non-matching query"""
    response = client.get("/api/search?q=nonexistent")
    assert response.status_code == 200
    data = response.get_json()

    assert data["query"] == "nonexistent"
    assert data["count"] == 0
    assert len(data["results"]) == 0


def test_search_api_validates_limit(client, test_notes):
    """Test that search API validates and applies limit parameter"""
    # Test valid limit
    response = client.get("/api/search?q=python&limit=1")
    assert response.status_code == 200
    data = response.get_json()
    assert data["limit"] == 1
    assert len(data["results"]) <= 1

    # Test max limit (100)
    response = client.get("/api/search?q=python&limit=1000")
    assert response.status_code == 200
    data = response.get_json()
    assert data["limit"] == 100  # Should be capped at 100

    # Test invalid limit (defaults to 20)
    response = client.get("/api/search?q=python&limit=invalid")
    assert response.status_code == 200
    data = response.get_json()
    assert data["limit"] == 20


def test_search_api_validates_offset(client, test_notes):
    """Test that search API validates offset parameter"""
    response = client.get("/api/search?q=python&offset=1")
    assert response.status_code == 200
    data = response.get_json()
    assert data["offset"] == 1

    # Test invalid offset (defaults to 0)
    response = client.get("/api/search?q=python&offset=-5")
    assert response.status_code == 200
    data = response.get_json()
    assert data["offset"] == 0


def test_search_api_pagination(client, test_notes):
    """Test that search API pagination works correctly"""
    # Get first page
    response1 = client.get("/api/search?q=python&limit=1&offset=0")
    data1 = response1.get_json()

    # Get second page
    response2 = client.get("/api/search?q=python&limit=1&offset=1")
    data2 = response2.get_json()

    # Results should be different (if there are at least 2 matches)
    if data1["count"] > 0 and len(data2["results"]) > 0:
        assert data1["results"][0]["slug"] != data2["results"][0]["slug"]


def test_search_api_respects_published_status(client, test_notes):
    """Test that anonymous users only see published notes"""
    response = client.get("/api/search?q=draft")
    assert response.status_code == 200
    data = response.get_json()

    # Anonymous user should not see unpublished "Draft Python Article"
    assert data["count"] == 0


def test_search_api_highlights_matches(client, test_notes):
    """Test that search API includes highlighted excerpts"""
    response = client.get("/api/search?q=python")
    assert response.status_code == 200
    data = response.get_json()

    if data["count"] > 0:
        # Check that excerpts contain <mark> tags for highlighting
        excerpt = data["results"][0]["excerpt"]
        assert "<mark>" in excerpt or "python" in excerpt.lower()


def test_search_api_handles_special_characters(client, test_notes):
    """Test that search API handles special characters in query"""
    # Test quotes
    response = client.get('/api/search?q="python"')
    assert response.status_code == 200

    # Test with URL encoding
    response = client.get("/api/search?q=python%20testing")
    assert response.status_code == 200
    data = response.get_json()
    assert data["query"] == "python testing"


def test_search_api_generates_correct_urls(client, test_notes):
    """Test that search API generates correct note URLs"""
    response = client.get("/api/search?q=python")
    assert response.status_code == 200
    data = response.get_json()

    if data["count"] > 0:
        result = data["results"][0]
        assert result["url"].startswith("/notes/")
        assert result["url"] == f"/notes/{result['slug']}"


def test_search_api_provides_fallback_title(client, app):
    """Test that search API provides fallback title for notes without title"""
    with app.app_context():
        # Create note without clear title
        note = create_note(
            content="Just some content without a heading.",
            published=True
        )

        response = client.get("/api/search?q=content")
        assert response.status_code == 200
        data = response.get_json()

        if data["count"] > 0:
            # Should have some title (either extracted or fallback)
            assert data["results"][0]["title"] is not None
            assert len(data["results"][0]["title"]) > 0
