"""
Tests pour GET /api/v1/download/{job_id}
Couvre les AC 1-6 de la story 2.12 : Telechargement Fichier Traduit
"""

import io
from pathlib import Path
from unittest.mock import patch, MagicMock
from zipfile import ZipFile

import pytest
from fastapi.testclient import TestClient

TRANSLATE_URL = "/api/v1/translate"
STATUS_URL = "/api/v1/translations"
DOWNLOAD_URL = "/api/v1/download"
REGISTER_URL = "/api/v1/auth/register"
LOGIN_URL = "/api/v1/auth/login"

AUTH_USER_ID = None  # set by the authenticated_client fixture

VALID_USER = {
    "email": "download@example.com",
    "password": "Password123!",
    "name": "Download User",
}


def create_valid_excel() -> bytes:
    """Create a minimal valid .xlsx file (ZIP with office content)."""
    buf = io.BytesIO()
    with ZipFile(buf, "w") as zf:
        zf.writestr(
            "[Content_Types].xml",
            '<?xml version="1.0"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"></Types>',
        )
        zf.writestr(
            "_rels/.rels",
            '<?xml version="1.0"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>',
        )
        zf.writestr(
            "xl/workbook.xml",
            '<?xml version="1.0"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"></workbook>',
        )
    buf.seek(0)
    return buf.read()


def create_valid_docx() -> bytes:
    """Create a minimal valid .docx file."""
    buf = io.BytesIO()
    with ZipFile(buf, "w") as zf:
        zf.writestr(
            "[Content_Types].xml",
            '<?xml version="1.0"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"></Types>',
        )
        zf.writestr(
            "_rels/.rels",
            '<?xml version="1.0"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>',
        )
        zf.writestr(
            "word/document.xml",
            '<?xml version="1.0"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"></w:document>',
        )
    buf.seek(0)
    return buf.read()


def create_valid_pptx() -> bytes:
    """Create a minimal valid .pptx file."""
    buf = io.BytesIO()
    with ZipFile(buf, "w") as zf:
        zf.writestr(
            "[Content_Types].xml",
            '<?xml version="1.0"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"></Types>',
        )
        zf.writestr(
            "_rels/.rels",
            '<?xml version="1.0"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>',
        )
        zf.writestr(
            "ppt/presentation.xml",
            '<?xml version="1.0"?><p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"></p:presentation>',
        )
    buf.seek(0)
    return buf.read()


@pytest.fixture()
def users_file(tmp_path: Path) -> Path:
    """Fichier de stockage JSON isole pour les tests."""
    return tmp_path / "users.json"


@pytest.fixture()
def client(users_file: Path, monkeypatch):
    """TestClient avec stockage JSON isole et rate limiting desactive."""
    import services.auth_service as auth_svc

    monkeypatch.setattr(auth_svc, "USERS_FILE", users_file)
    monkeypatch.setattr(auth_svc, "USE_DATABASE", False)
    monkeypatch.setattr(auth_svc, "DATABASE_AVAILABLE", False)

    from middleware.rate_limiting import RateLimitManager

    async def _check_request_allow(self, request):
        return True, "ok", "test"

    async def _check_translation_allow(self, request, file_size_mb=0):
        return True, "ok"

    monkeypatch.setattr(RateLimitManager, "check_request", _check_request_allow)
    monkeypatch.setattr(RateLimitManager, "check_translation", _check_translation_allow)

    def _check_usage_limits_allow(user):
        return {
            "can_translate": True,
            "docs_used": 0,
            "docs_limit": 5,
            "docs_remaining": 5,
            "pages_used": 0,
            "extra_credits": 0,
            "max_pages_per_doc": 50,
            "max_file_size_mb": 10,
            "allowed_providers": ["google"],
        }

    monkeypatch.setattr(
        "routes.translate_routes.check_usage_limits", _check_usage_limits_allow
    )

    from main import app

    return TestClient(app, raise_server_exceptions=True)


@pytest.fixture()
def authenticated_client(client):
    """Client avec un utilisateur enregistre et authentifie."""
    global AUTH_USER_ID
    reg = client.post(REGISTER_URL, json=VALID_USER)
    AUTH_USER_ID = reg.json()["data"]["id"]
    response = client.post(
        LOGIN_URL,
        json={
            "email": VALID_USER["email"],
            "password": VALID_USER["password"],
        },
    )
    token = response.json()["data"]["access_token"]
    client.headers["Authorization"] = f"Bearer {token}"
    return client


# ---------------------------------------------------------------------------
# AC1: Download Endpoint
# ---------------------------------------------------------------------------


class TestDownloadEndpoint:
    """AC1: GET /api/v1/download/{id} returns translated file as binary download"""

    def test_returns_400_for_invalid_job_id_format(self, authenticated_client):
        """Invalid job_id format returns 400 with INVALID_JOB_ID"""
        response = authenticated_client.get(f"{DOWNLOAD_URL}/invalid_format")
        assert response.status_code == 400
        body = response.json()
        assert body["error"] == "INVALID_JOB_ID"

    def test_returns_400_for_job_id_with_special_chars(self, authenticated_client):
        """Job ID with special chars returns 400"""
        response = authenticated_client.get(f"{DOWNLOAD_URL}/tr_invalid@#$%")
        assert response.status_code == 400
        body = response.json()
        assert body["error"] == "INVALID_JOB_ID"

    def test_returns_404_for_non_existent_job(self, authenticated_client):
        """AC4: Non-existent job returns 404 with FILE_EXPIRED"""
        response = authenticated_client.get(f"{DOWNLOAD_URL}/tr_nonexistent123")
        assert response.status_code == 404
        body = response.json()
        assert body["error"] == "FILE_EXPIRED"

    def test_returns_404_for_job_without_output_path(self, authenticated_client):
        """AC4: Job without output_path returns 404 with FILE_EXPIRED"""
        from routes import translate_routes

        job_id = "tr_test_no_output"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "completed",
            "file_name": "test.xlsx",
            "output_path": None,
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 404
        body = response.json()
        assert body["error"] == "FILE_EXPIRED"

    def test_returns_404_for_file_deleted_from_disk(
        self, authenticated_client, tmp_path
    ):
        """AC4: Job with output_path but file missing from disk returns 404"""
        from routes import translate_routes

        nonexistent_file = tmp_path / "deleted_file.xlsx"

        job_id = "tr_deleted_disk"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "completed",
            "file_name": "deleted.xlsx",
            "file_extension": ".xlsx",
            "output_path": str(nonexistent_file),
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 404
        body = response.json()
        assert body["error"] == "FILE_EXPIRED"
        assert body["details"]["status"] == "file_deleted"

    def test_returns_404_for_non_completed_job(self, authenticated_client):
        """AC5: Non-completed jobs return 404 with NOT_READY"""
        from routes import translate_routes

        job_id = "tr_test_processing"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "processing",
            "progress_percent": 50,
            "file_name": "test.xlsx",
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 404
        body = response.json()
        assert body["error"] == "NOT_READY"

    def test_returns_404_for_queued_job(self, authenticated_client):
        """AC5: Queued jobs return 404 with NOT_READY"""
        from routes import translate_routes

        job_id = "tr_test_queued"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "queued",
            "file_name": "test.xlsx",
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 404
        body = response.json()
        assert body["error"] == "NOT_READY"

    def test_returns_404_for_failed_job(self, authenticated_client):
        """AC5: Failed jobs return 404 with NOT_READY"""
        from routes import translate_routes

        job_id = "tr_test_failed"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "failed",
            "error_message": "Something went wrong",
            "file_name": "test.xlsx",
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 404
        body = response.json()
        assert body["error"] == "NOT_READY"


# ---------------------------------------------------------------------------
# AC2: Content-Disposition Header
# ---------------------------------------------------------------------------


class TestContentDisposition:
    """AC2: Header includes original filename with "_translated" suffix"""

    def test_content_disposition_has_translated_suffix(
        self, authenticated_client, tmp_path
    ):
        """Content-Disposition includes _translated suffix"""
        from routes import translate_routes

        output_file = tmp_path / "test_translated.xlsx"
        output_file.write_bytes(create_valid_excel())

        job_id = "tr_test_disposition"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "completed",
            "file_name": "report.xlsx",
            "file_extension": ".xlsx",
            "output_path": str(output_file),
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 200
        content_disp = response.headers.get("content-disposition", "")
        assert "attachment" in content_disp
        assert "report_translated.xlsx" in content_disp

    def test_content_disposition_for_docx(self, authenticated_client, tmp_path):
        """Content-Disposition works for .docx files"""
        from routes import translate_routes

        output_file = tmp_path / "doc_translated.docx"
        output_file.write_bytes(create_valid_docx())

        job_id = "tr_test_docx"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "completed",
            "file_name": "document.docx",
            "file_extension": ".docx",
            "output_path": str(output_file),
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 200
        content_disp = response.headers.get("content-disposition", "")
        assert "document_translated.docx" in content_disp

    def test_content_disposition_for_pptx(self, authenticated_client, tmp_path):
        """Content-Disposition works for .pptx files"""
        from routes import translate_routes

        output_file = tmp_path / "ppt_translated.pptx"
        output_file.write_bytes(create_valid_pptx())

        job_id = "tr_test_pptx"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "completed",
            "file_name": "presentation.pptx",
            "file_extension": ".pptx",
            "output_path": str(output_file),
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 200
        content_disp = response.headers.get("content-disposition", "")
        assert "presentation_translated.pptx" in content_disp


# ---------------------------------------------------------------------------
# AC3: Immediate File Deletion (tested via BackgroundTask)
# ---------------------------------------------------------------------------


class TestFileDeletion:
    """AC3: File is deleted immediately after successful download"""

    def test_file_deleted_after_download(self, authenticated_client, tmp_path):
        """File should be deleted after download completes"""
        from routes import translate_routes

        output_file = tmp_path / "to_delete.xlsx"
        output_file.write_bytes(create_valid_excel())

        input_file = tmp_path / "input_file.xlsx"
        input_file.write_bytes(create_valid_excel())

        job_id = "tr_test_delete"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "completed",
            "file_name": "to_delete.xlsx",
            "file_extension": ".xlsx",
            "output_path": str(output_file),
            "input_path": str(input_file),
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 200

        import time

        time.sleep(0.5)

        assert not output_file.exists(), "Output file should be deleted after download"
        assert not input_file.exists(), "Input file should be deleted after download"


# ---------------------------------------------------------------------------
# AC6: Correct MIME Types
# ---------------------------------------------------------------------------


class TestMIMETypes:
    """AC6: Content-Type set correctly for each format"""

    def test_mime_type_xlsx(self, authenticated_client, tmp_path):
        """xlsx returns correct MIME type"""
        from routes import translate_routes

        output_file = tmp_path / "test.xlsx"
        output_file.write_bytes(create_valid_excel())

        job_id = "tr_test_mime_xlsx"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "completed",
            "file_name": "test.xlsx",
            "file_extension": ".xlsx",
            "output_path": str(output_file),
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 200
        content_type = response.headers.get("content-type", "")
        assert (
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
            in content_type
        )

    def test_mime_type_docx(self, authenticated_client, tmp_path):
        """docx returns correct MIME type"""
        from routes import translate_routes

        output_file = tmp_path / "test.docx"
        output_file.write_bytes(create_valid_docx())

        job_id = "tr_test_mime_docx"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "completed",
            "file_name": "test.docx",
            "file_extension": ".docx",
            "output_path": str(output_file),
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 200
        content_type = response.headers.get("content-type", "")
        assert (
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
            in content_type
        )

    def test_mime_type_pptx(self, authenticated_client, tmp_path):
        """pptx returns correct MIME type"""
        from routes import translate_routes

        output_file = tmp_path / "test.pptx"
        output_file.write_bytes(create_valid_pptx())

        job_id = "tr_test_mime_pptx"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "completed",
            "file_name": "test.pptx",
            "file_extension": ".pptx",
            "output_path": str(output_file),
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 200
        content_type = response.headers.get("content-type", "")
        assert (
            "application/vnd.openxmlformats-officedocument.presentationml.presentation"
            in content_type
        )


# ---------------------------------------------------------------------------
# AC4: File Expired/Not Found
# ---------------------------------------------------------------------------


class TestFileExpired:
    """AC4: If translation not found, expired, or output_path missing, returns 404"""

    def test_file_expired_message_in_french(self, authenticated_client):
        """Error message should be in French"""
        response = authenticated_client.get(f"{DOWNLOAD_URL}/tr_nonexistent")
        assert response.status_code == 404
        body = response.json()
        assert body["error"] == "FILE_EXPIRED"
        assert (
            "non disponible" in body["message"].lower()
            or "expire" in body["message"].lower()
        )

    def test_not_ready_message_in_french(self, authenticated_client):
        """NOT_READY error message should be in French"""
        from routes import translate_routes

        job_id = "tr_test_not_ready_msg"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "processing",
            "progress_percent": 30,
            "file_name": "test.xlsx",
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 404
        body = response.json()
        assert body["error"] == "NOT_READY"
        assert "cours" in body["message"].lower() or "encore" in body["message"].lower()


# ---------------------------------------------------------------------------
# Integration: Full flow
# ---------------------------------------------------------------------------


class TestDownloadIntegration:
    """Integration tests for download flow"""

    def test_download_returns_binary_content(self, authenticated_client, tmp_path):
        """Download returns actual binary content"""
        from routes import translate_routes

        content = create_valid_excel()
        output_file = tmp_path / "binary_test.xlsx"
        output_file.write_bytes(content)

        job_id = "tr_test_binary"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "completed",
            "file_name": "binary_test.xlsx",
            "file_extension": ".xlsx",
            "output_path": str(output_file),
            "user_id": AUTH_USER_ID,
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 200
        assert len(response.content) > 0
        assert response.content[:2] == b"PK"


# ---------------------------------------------------------------------------
# Error Details
# ---------------------------------------------------------------------------


class TestErrorDetails:
    """Test that error responses include proper details"""

    def test_file_expired_includes_job_id_in_details(self, authenticated_client):
        """FILE_EXPIRED includes job_id in details"""
        response = authenticated_client.get(f"{DOWNLOAD_URL}/tr_nonexistent999")
        assert response.status_code == 404
        body = response.json()
        assert body["error"] == "FILE_EXPIRED"
        assert "details" in body
        assert body["details"]["job_id"] == "tr_nonexistent999"

    def test_not_ready_includes_status_in_details(self, authenticated_client):
        """NOT_READY includes status in details"""
        from routes import translate_routes

        job_id = "tr_test_details"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "processing",
            "progress_percent": 45,
            "file_name": "test.xlsx",
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 404
        body = response.json()
        assert body["error"] == "NOT_READY"
        assert "details" in body
        assert body["details"]["status"] == "processing"
        assert body["details"]["progress_percent"] == 45


# ---------------------------------------------------------------------------
# Authorization Tests
# ---------------------------------------------------------------------------


class TestDownloadAuthorization:
    """Test authorization for download endpoint"""

    def test_user_cannot_download_other_users_file(self, client, tmp_path):
        """User cannot download file belonging to another user"""
        from routes import translate_routes

        output_file = tmp_path / "other_user_file.xlsx"
        output_file.write_bytes(create_valid_excel())

        job_id = "tr_other_user123"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "completed",
            "file_name": "other.xlsx",
            "file_extension": ".xlsx",
            "output_path": str(output_file),
            "user_id": "different_user_id_456",
        }

        client.post(REGISTER_URL, json=VALID_USER)
        response = client.post(
            LOGIN_URL,
            json={
                "email": VALID_USER["email"],
                "password": VALID_USER["password"],
            },
        )
        token = response.json()["data"]["access_token"]
        client.headers["Authorization"] = f"Bearer {token}"

        response = client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 403
        body = response.json()
        assert body["error"] == "ACCESS_DENIED"

    def test_user_can_download_own_file(self, authenticated_client, tmp_path):
        """User can download their own file"""
        from routes import translate_routes

        output_file = tmp_path / "own_file.xlsx"
        output_file.write_bytes(create_valid_excel())

        job_id = "tr_own_file123"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": AUTH_USER_ID,
            "status": "completed",
            "file_name": "own.xlsx",
            "file_extension": ".xlsx",
            "output_path": str(output_file),
            "user_id": AUTH_USER_ID,
        }

        response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 200

    def test_anonymous_job_requires_token(self, client, tmp_path):
        """Jobs without an owner require the secret per-job token (fix 2026-08-26)"""
        from routes import translate_routes

        output_file = tmp_path / "public_job.xlsx"
        output_file.write_bytes(create_valid_excel())

        job_id = "tr_public_job99"
        translate_routes._translation_jobs[job_id] = {
            "id": job_id,
            "user_id": None,
            "access_token": "secret_job_token",
            "status": "completed",
            "file_name": "public.xlsx",
            "file_extension": ".xlsx",
            "output_path": str(output_file),
        }

        # Without the token: denied
        response = client.get(f"{DOWNLOAD_URL}/{job_id}")
        assert response.status_code == 403
        assert response.json()["error"] == "ACCESS_DENIED"

        # With the correct token: allowed
        response = client.get(f"{DOWNLOAD_URL}/{job_id}?token=secret_job_token")
        assert response.status_code == 200
