# tests/test_query_api.py
"""Tests for the /query endpoint."""
import json
from io import BytesIO

from fastapi.testclient import TestClient


def test_get_courses_empty(client: TestClient) -> None:
    """Test that /courses returns empty list when no courses exist."""
    response = client.get("/courses")
    assert response.status_code == 200
    data = response.json()
    assert "courses" in data
    assert isinstance(data["courses"], list)


def test_get_courses_with_multiple_courses(client: TestClient, sample_text: str) -> None:
    """Test that /courses returns all courses after adding assets to multiple courses."""
    from unittest.mock import MagicMock
    from app.services.query_service import QueryService
    from app import main
    
    # Add a file to course "default" (already mocked)
    file_content = sample_text.encode("utf-8")
    files = {"file": ("file1.txt", BytesIO(file_content), "text/plain")}
    response1 = client.post("/courses/default/asset/add", files=files)
    assert response1.status_code == 200
    
    # Setup mock for course2
    mock_vectorstore_service = MagicMock()
    mock_vectorstore_service.create_or_append = MagicMock()
    mock_vectorstore = MagicMock()
    mock_vectorstore.add_texts = MagicMock()
    mock_vectorstore_service._vectorstore = mock_vectorstore
    main.vectorstore_services["course2"] = mock_vectorstore_service
    main.query_services["course2"] = QueryService(vectorstore=mock_vectorstore)
    
    # Add a file to course "course2"
    files = {"file": ("file2.txt", BytesIO(file_content), "text/plain")}
    response2 = client.post("/courses/course2/asset/add", files=files)
    assert response2.status_code == 200
    
    # Get courses
    response = client.get("/courses")
    assert response.status_code == 200
    data = response.json()
    course_ids = [c["id"] for c in data["courses"]]
    assert "default" in course_ids
    assert "course2" in course_ids


def test_query_endpoint_requires_initialization(client_uninitialized: TestClient) -> None:
    """Test that /query returns 503 if vector store is not initialized."""
    response = client_uninitialized.post(
        "/courses/default/query", json={"query": "test", "top_k": 3}
    )
    # Before ingestion, vector store should not be ready
    # The response may be 503 or 400 depending on initialization
    assert response.status_code in [400, 503]


def test_asset_add_endpoint(client: TestClient, sample_text: str) -> None:
    """Test uploading a file."""
    # Create a file-like object
    file_content = sample_text.encode("utf-8")
    files = {"file": ("test.txt", BytesIO(file_content), "text/plain")}

    response = client.post("/courses/default/asset/add", files=files, params={"overwrite": "false"})
    assert response.status_code == 200
    data = response.json()
    assert data["id"] == "test.txt"
    assert data["name"] == "test.txt"
    assert data["chunk_count"] > 0


def test_asset_list_endpoint(client: TestClient) -> None:
    """Test listing assets."""
    response = client.get("/courses/default/asset/get_all?page=1&per_page=20")
    assert response.status_code == 200
    data = response.json()
    assert "items" in data
    assert "total" in data
    assert "page" in data


def test_asset_get_endpoint(client: TestClient, sample_text: str) -> None:
    """Test getting details for a specific asset."""
    # First upload a file
    file_content = sample_text.encode("utf-8")
    files = {"file": ("test2.txt", BytesIO(file_content), "text/plain")}
    add_response = client.post("/courses/default/asset/add", files=files)
    assert add_response.status_code == 200

    # Now get the asset details
    response = client.get("/courses/default/asset/get/test2.txt")
    assert response.status_code == 200
    data = response.json()
    assert data["id"] == "test2.txt"
    assert data["path"]


def test_invalid_filename(client: TestClient, sample_text: str) -> None:
    """Test that invalid filenames are rejected."""
    file_content = sample_text.encode("utf-8")
    files = {"file": ("../malicious.txt", BytesIO(file_content), "text/plain")}

    response = client.post("/courses/default/asset/add", files=files)
    assert response.status_code == 400


def test_debug_index_info(client: TestClient) -> None:
    """Test the debug index info endpoint."""
    response = client.get("/courses/default/debug/index_info")
    assert response.status_code == 200
    data = response.json()
    assert "index_name" in data
    assert "redis_url" in data
    assert "vectorstore_connected" in data


def test_asset_add_markdown_file(client: TestClient) -> None:
    """Test uploading a markdown file."""
    content = b"# Hello\n\nThis is **markdown** content."
    files = {"file": ("readme.md", BytesIO(content), "text/markdown")}
    
    response = client.post("/courses/default/asset/add", files=files)
    assert response.status_code == 200
    data = response.json()
    assert data["id"] == "readme.md"
    assert data["chunk_count"] > 0


def test_asset_add_pdf_file_error(client: TestClient) -> None:
    """Test uploading an invalid PDF file returns error."""
    content = b"Not a real PDF"
    files = {"file": ("document.pdf", BytesIO(content), "application/pdf")}
    
    response = client.post("/courses/default/asset/add", files=files)
    # Should fail due to invalid PDF
    assert response.status_code in (400, 415)


def test_asset_add_unsupported_type(client: TestClient) -> None:
    """Test uploading an unsupported file type."""
    content = b"some binary content"
    files = {"file": ("document.docx", BytesIO(content), "application/vnd.openxmlformats-officedocument.wordprocessingml.document")}
    
    response = client.post("/courses/default/asset/add", files=files)
    assert response.status_code == 415
    assert "Unsupported file type" in response.json()["detail"]


def test_asset_add_youtube_requires_transcript(client: TestClient) -> None:
    """Test that YouTube import endpoint exists and validates URL."""
    response = client.post(
        "/courses/default/asset/add-youtube",
        json={"url": "invalid-url", "title": "Test Video"}
    )
    # Should fail due to invalid URL
    assert response.status_code in (400, 503)


def test_asset_add_youtube_missing_url(client: TestClient) -> None:
    """Test that YouTube import endpoint requires URL."""
    response = client.post(
        "/courses/default/asset/add-youtube",
        json={"title": "Test Video"}
    )
    assert response.status_code == 400


# ===========================
# YouTube Upload via /asset/add Tests
# ===========================


def test_asset_add_youtube_via_multipart(client: TestClient) -> None:
    """Test uploading YouTube video via /asset/add with .youtube file."""
    # Simulate a YouTube URL upload by creating a .youtube file
    youtube_url = b"https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    files = {"file": ("video.youtube", BytesIO(youtube_url), "video/youtube")}
    
    response = client.post("/courses/default/asset/add", files=files)
    # Should succeed (or fail gracefully if transcript unavailable)
    # For now, we expect 200 or 400 (no transcript) or 503 (service unavailable)
    assert response.status_code in (200, 400, 503)


def test_asset_add_youtube_url_file(client: TestClient) -> None:
    """Test uploading YouTube video via /asset/add with .youtube_url file."""
    youtube_url = b"https://youtu.be/dQw4w9WgXcQ"
    files = {"file": ("lecture.youtube_url", BytesIO(youtube_url), "video/youtube")}
    
    response = client.post("/courses/default/asset/add", files=files)
    assert response.status_code in (200, 400, 503)


def test_asset_add_youtube_with_title(client: TestClient) -> None:
    """Test uploading YouTube with custom title via multipart."""
    youtube_url = b"https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    files = {"file": ("my_lecture.youtube", BytesIO(youtube_url), "video/youtube")}
    
    response = client.post("/courses/default/asset/add", files=files)
    assert response.status_code in (200, 400, 503)
    if response.status_code == 200:
        data = response.json()
        assert "id" in data
        assert data["name"] == "my_lecture.youtube"


def test_asset_add_youtube_invalid_url(client: TestClient) -> None:
    """Test uploading YouTube with invalid URL."""
    invalid_url = b"https://example.com/not-youtube"
    files = {"file": ("invalid.youtube", BytesIO(invalid_url), "video/youtube")}
    
    response = client.post("/courses/default/asset/add", files=files)
    assert response.status_code == 400  # Invalid URL


def test_asset_add_youtube_empty_file(client: TestClient) -> None:
    """Test uploading empty YouTube file."""
    files = {"file": ("empty.youtube", BytesIO(b""), "video/youtube")}
    
    response = client.post("/courses/default/asset/add", files=files)
    assert response.status_code == 400


def test_asset_add_supported_extensions(client: TestClient, sample_text: str) -> None:
    """Test that /asset/add supports .txt, .md, .pdf, .youtube, .youtube_url."""
    supported = ["test.txt", "readme.md", "video.youtube", "lecture.youtube_url"]
    
    for filename in supported:
        if filename.endswith(".youtube") or filename.endswith(".youtube_url"):
            # YouTube files contain URLs
            content = b"https://www.youtube.com/watch?v=dQw4w9WgXcQ"
        else:
            # Regular files contain text
            content = sample_text.encode("utf-8")
        
        files = {"file": (filename, BytesIO(content))}
        response = client.post("/courses/default/asset/add", files=files)
        # Expect 200, 400 (invalid youtube), or 503 (service unavailable)
        assert response.status_code in (200, 400, 503), f"Failed for {filename}"


def test_asset_add_youtube_extension_supported(client: TestClient) -> None:
    """Test that .youtube extension is recognized as supported."""
    from app.services.file_extractor_service import FileExtractorService
    
    assert FileExtractorService.is_supported("video.youtube")
    assert FileExtractorService.is_supported("video.youtube_url")
    assert FileExtractorService.is_supported("lecture.YOUTUBE")


def test_asset_add_youtube_proper_error_on_empty(client: TestClient) -> None:
    """Test that empty YouTube URL file returns proper error."""
    files = {"file": ("empty.youtube", BytesIO(b""), "video/youtube")}
    
    response = client.post("/courses/default/asset/add", files=files)
    assert response.status_code == 400
    assert "empty" in response.json()["detail"].lower() or "url" in response.json()["detail"].lower()
