"""Error handling tests."""

from io import BytesIO
from unittest.mock import patch

from fastapi.testclient import TestClient


def test_invalid_file_format_returns_422(
    client: TestClient,
    invalid_video_bytes: bytes,
) -> None:
    """Test that invalid file format returns 422 Unprocessable Entity."""
    files = {"file": ("document.txt", BytesIO(invalid_video_bytes), "text/plain")}
    response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})

    assert response.status_code == 422


def test_invalid_jump_type_returns_422(
    client: TestClient,
    sample_video_bytes: bytes,
) -> None:
    """Test that invalid jump_type returns 422."""
    files = {"file": ("test.mp4", BytesIO(sample_video_bytes), "video/mp4")}
    response = client.post("/api/analyze", files=files, data={"jump_type": "invalid"})

    assert response.status_code == 422


def test_file_too_large_returns_422(
    client: TestClient,
    large_video_bytes: bytes,
) -> None:
    """Test that file >500MB returns 422."""
    files = {"file": ("large.mp4", BytesIO(large_video_bytes), "video/mp4")}
    response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})

    assert response.status_code == 422


def test_validation_error_response_format(
    client: TestClient,
    invalid_video_bytes: bytes,
) -> None:
    """Test that validation error response has correct format."""
    files = {"file": ("document.txt", BytesIO(invalid_video_bytes), "text/plain")}
    response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})
    data = response.json()

    # Required error fields
    assert data["status"] == 422
    assert "message" in data
    assert "error" in data
    assert "processing_time_s" in data


def test_validation_error_message_descriptive(
    client: TestClient,
    invalid_video_bytes: bytes,
) -> None:
    """Test that validation error messages are descriptive."""
    files = {"file": ("document.txt", BytesIO(invalid_video_bytes), "text/plain")}
    response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})
    data = response.json()

    # Error should explain what's wrong
    assert len(data["error"]) > 0


def test_kinemotion_processing_error_returns_500(
    client: TestClient,
    sample_video_bytes: bytes,
) -> None:
    """Test that processing errors return 500 Internal Server Error."""
    files = {"file": ("test.mp4", BytesIO(sample_video_bytes), "video/mp4")}

    # Mock kinemotion to raise an error
    with patch("kinemotion_backend.app.process_cmj_video") as mock_cmj:
        mock_cmj.side_effect = RuntimeError("Video processing failed")
        response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})

    assert response.status_code == 500


def test_processing_error_response_format(
    client: TestClient,
    sample_video_bytes: bytes,
) -> None:
    """Test that processing error response has correct format."""
    files = {"file": ("test.mp4", BytesIO(sample_video_bytes), "video/mp4")}

    with patch("kinemotion_backend.app.process_cmj_video") as mock_cmj:
        mock_cmj.side_effect = RuntimeError("Video processing failed")
        response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})

    data = response.json()

    # Required error fields
    assert data["status"] == 500
    assert "message" in data
    assert "error" in data
    assert "processing_time_s" in data


def test_processing_error_contains_error_type(
    client: TestClient,
    sample_video_bytes: bytes,
) -> None:
    """Test that processing error includes error type."""
    files = {"file": ("test.mp4", BytesIO(sample_video_bytes), "video/mp4")}

    with patch("kinemotion_backend.app.process_cmj_video") as mock_cmj:
        mock_cmj.side_effect = RuntimeError("Video processing failed")
        response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})

    data = response.json()
    # Error should include RuntimeError type
    assert "RuntimeError" in data["error"]


def test_file_cleanup_on_processing_error(
    client: TestClient,
    sample_video_bytes: bytes,
    tmp_path,
) -> None:
    """Test that temporary files are cleaned up after processing error."""
    files = {"file": ("test.mp4", BytesIO(sample_video_bytes), "video/mp4")}

    with patch("kinemotion_backend.app.process_cmj_video") as mock_cmj:
        mock_cmj.side_effect = RuntimeError("Processing error")
        response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})

    # Response should be error
    assert response.status_code == 500
    # Temp files should be cleaned up (can't verify directly, but endpoint
    # should not crash)


def test_file_cleanup_on_validation_error(
    client: TestClient,
    invalid_video_bytes: bytes,
) -> None:
    """Test that validation errors don't leave temp files."""
    files = {"file": ("document.txt", BytesIO(invalid_video_bytes), "text/plain")}
    response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})

    # Should return validation error
    assert response.status_code == 422
    # Endpoint should handle cleanup gracefully


def test_multiple_errors_sequential(
    client: TestClient,
    sample_video_bytes: bytes,
) -> None:
    """Test that endpoint handles multiple sequential errors."""
    files_invalid = {"file": ("document.txt", BytesIO(b"text"), "text/plain")}
    files_valid = {"file": ("test.mp4", BytesIO(sample_video_bytes), "video/mp4")}

    # First request with invalid file
    response1 = client.post(
        "/api/analyze", files=files_invalid, data={"jump_type": "cmj"}
    )
    assert response1.status_code == 422

    # Second request with valid file
    response2 = client.post(
        "/api/analyze", files=files_valid, data={"jump_type": "cmj"}
    )
    assert response2.status_code == 200


def test_value_error_during_processing_returns_422(
    client: TestClient,
    sample_video_bytes: bytes,
) -> None:
    """Test that ValueError during processing returns 422."""
    files = {"file": ("test.mp4", BytesIO(sample_video_bytes), "video/mp4")}

    with patch("kinemotion_backend.app.process_cmj_video") as mock_cmj:
        mock_cmj.side_effect = ValueError("Invalid video content")
        response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})

    # ValueError should be treated as validation error
    assert response.status_code == 422


def test_generic_exception_returns_500(
    client: TestClient,
    sample_video_bytes: bytes,
) -> None:
    """Test that unexpected exceptions return 500."""
    files = {"file": ("test.mp4", BytesIO(sample_video_bytes), "video/mp4")}

    with patch("kinemotion_backend.app.process_cmj_video") as mock_cmj:
        mock_cmj.side_effect = Exception("Unexpected error")
        response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})

    assert response.status_code == 500


def test_keyboard_interrupt_returns_500(
    client: TestClient,
    sample_video_bytes: bytes,
) -> None:
    """Test that KeyboardInterrupt returns 500."""
    files = {"file": ("test.mp4", BytesIO(sample_video_bytes), "video/mp4")}

    with patch("kinemotion_backend.app.process_cmj_video") as mock_cmj:
        mock_cmj.side_effect = KeyboardInterrupt()
        response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})

    assert response.status_code == 500


def test_processing_time_recorded_on_error(
    client: TestClient,
    sample_video_bytes: bytes,
) -> None:
    """Test that processing_time_s is recorded even on error."""
    files = {"file": ("test.mp4", BytesIO(sample_video_bytes), "video/mp4")}

    with patch("kinemotion_backend.app.process_cmj_video") as mock_cmj:
        mock_cmj.side_effect = RuntimeError("Error")
        response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})

    data = response.json()
    assert "processing_time_s" in data
    assert isinstance(data["processing_time_s"], (int, float))


def test_error_messages_not_empty(
    client: TestClient,
    sample_video_bytes: bytes,
) -> None:
    """Test that error messages are not empty."""
    files = {"file": ("test.mp4", BytesIO(sample_video_bytes), "video/mp4")}

    with patch("kinemotion_backend.app.process_cmj_video") as mock_cmj:
        mock_cmj.side_effect = RuntimeError("Processing failed")
        response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})

    data = response.json()
    assert len(data["message"]) > 0
    assert len(data["error"]) > 0


def test_no_metrics_in_error_response(
    client: TestClient,
    sample_video_bytes: bytes,
) -> None:
    """Test that error responses don't include metrics."""
    files = {"file": ("test.mp4", BytesIO(sample_video_bytes), "video/mp4")}

    with patch("kinemotion_backend.app.process_cmj_video") as mock_cmj:
        mock_cmj.side_effect = RuntimeError("Error")
        response = client.post("/api/analyze", files=files, data={"jump_type": "cmj"})

    data = response.json()
    assert "metrics" not in data
