"""
English-first QC enforcement tests.

Verifies that _assert_can_approve blocks target-language approval until the
source language is APPROVED, and that PRODUCTION/ADMIN bypass the gate.
"""

import pytest
from fastapi import HTTPException

from app.models.job import LanguageQCStatus
from app.models.user import User, UserRole
from app.services.language_qc import _assert_can_approve

# ── Helpers ────────────────────────────────────────────────────────────────────

def _make_actor(role: UserRole, user_id: str = "reviewer-1") -> User:
    return User(
        **{
            "_id": user_id,
            "email": f"{user_id}@example.com",
            "full_name": "Test Actor",
            "role": role,
            "is_active": True,
        }
    )


def _make_job(
    source_lang: str = "en",
    *,
    source_status: str | None = None,
    target_lang: str = "es",
    target_status: str | None = None,
    reviewer_id: str = "reviewer-1",
) -> dict:
    """Build a minimal job_doc for _assert_can_approve tests."""
    lang_qc: dict = {}

    if source_status is not None:
        lang_qc[source_lang] = {
            "status": source_status,
            "assigned_reviewer_id": reviewer_id,
            "submitted_for_review_at": "2026-01-01T00:00:00",
        }

    if target_status is not None:
        lang_qc[target_lang] = {
            "status": target_status,
            "assigned_reviewer_id": reviewer_id,
            "submitted_for_review_at": "2026-01-01T00:00:00",
        }

    return {
        "_id": "job-1",
        "source": {"language": source_lang},
        "language_qc": lang_qc,
    }


# ── English-first gate ─────────────────────────────────────────────────────────

class TestEnglishFirstGate:
    def test_approving_target_while_source_pending_raises_409(self):
        actor = _make_actor(UserRole.REVIEWER)
        job = _make_job(
            source_status=LanguageQCStatus.PENDING.value,
            target_status=LanguageQCStatus.PENDING_REVIEW.value,
        )
        with pytest.raises(HTTPException) as exc:
            _assert_can_approve(job, "es", actor)
        assert exc.value.status_code == 409
        assert "en" in exc.value.detail

    def test_approving_target_while_source_in_review_raises_409(self):
        actor = _make_actor(UserRole.REVIEWER)
        job = _make_job(
            source_status=LanguageQCStatus.IN_REVIEW.value,
            target_status=LanguageQCStatus.PENDING_REVIEW.value,
        )
        with pytest.raises(HTTPException) as exc:
            _assert_can_approve(job, "es", actor)
        assert exc.value.status_code == 409

    def test_approving_target_after_source_approved_passes_gate(self):
        """English-first gate passes; only reviewer-assignment gate may block next."""
        actor = _make_actor(UserRole.REVIEWER)
        job = _make_job(
            source_status=LanguageQCStatus.APPROVED.value,
            target_status=LanguageQCStatus.PENDING_REVIEW.value,
        )
        # Should NOT raise 409 — assignment gate raises 403 (different error)
        with pytest.raises(HTTPException) as exc:
            _assert_can_approve(job, "es", actor)
        assert exc.value.status_code != 409

    def test_approving_source_language_itself_skips_english_first_gate(self):
        """Approving 'en' when source_lang='en' — english-first gate does not apply."""
        actor = _make_actor(UserRole.REVIEWER)
        job = _make_job(
            source_status=LanguageQCStatus.PENDING_REVIEW.value,
        )
        # English-first gate should NOT fire for source language itself
        with pytest.raises(HTTPException) as exc:
            _assert_can_approve(job, "en", actor)
        assert exc.value.status_code != 409

    def test_source_qc_state_missing_raises_409(self):
        """If source QC state is entirely absent, treat as unapproved."""
        actor = _make_actor(UserRole.REVIEWER)
        job = {"_id": "job-1", "source": {"language": "en"}, "language_qc": {}}
        with pytest.raises(HTTPException) as exc:
            _assert_can_approve(job, "fr", actor)
        assert exc.value.status_code == 409

    def test_non_default_source_language_respected(self):
        """If source_language is 'fr', gate checks fr approval before 'de'."""
        actor = _make_actor(UserRole.REVIEWER)
        job = _make_job(
            source_lang="fr",
            source_status=LanguageQCStatus.PENDING.value,
            target_lang="de",
            target_status=LanguageQCStatus.PENDING_REVIEW.value,
        )
        with pytest.raises(HTTPException) as exc:
            _assert_can_approve(job, "de", actor)
        assert exc.value.status_code == 409
        assert "fr" in exc.value.detail

    def test_non_default_source_approved_allows_target(self):
        actor = _make_actor(UserRole.REVIEWER)
        job = _make_job(
            source_lang="fr",
            source_status=LanguageQCStatus.APPROVED.value,
            target_lang="de",
            target_status=LanguageQCStatus.PENDING_REVIEW.value,
        )
        # English-first gate passes; reviewer-assignment gate fires (not 409)
        with pytest.raises(HTTPException) as exc:
            _assert_can_approve(job, "de", actor)
        assert exc.value.status_code != 409


# ── PRODUCTION / ADMIN bypass ──────────────────────────────────────────────────

class TestAdminProductionBypass:
    def test_production_bypasses_english_first_gate(self):
        actor = _make_actor(UserRole.PRODUCTION)
        job = _make_job(
            source_status=LanguageQCStatus.PENDING.value,
            target_status=LanguageQCStatus.PENDING_REVIEW.value,
        )
        # Production bypasses ALL gates — must not raise anything
        _assert_can_approve(job, "es", actor)  # no exception

    def test_admin_bypasses_english_first_gate(self):
        actor = _make_actor(UserRole.ADMIN)
        job = _make_job(
            source_status=LanguageQCStatus.PENDING.value,
            target_status=LanguageQCStatus.PENDING_REVIEW.value,
        )
        _assert_can_approve(job, "es", actor)  # no exception

    def test_project_manager_does_not_bypass_gate(self):
        """PM is not in the bypass set — must respect english-first."""
        actor = _make_actor(UserRole.PROJECT_MANAGER)
        job = _make_job(
            source_status=LanguageQCStatus.PENDING.value,
            target_status=LanguageQCStatus.PENDING_REVIEW.value,
        )
        with pytest.raises(HTTPException) as exc:
            _assert_can_approve(job, "es", actor)
        assert exc.value.status_code == 409


# ── Sequence: approve en, then approve es ─────────────────────────────────────

class TestApprovalSequence:
    def test_approve_source_then_target_succeeds(self):
        """Simulate the correct two-step flow at the gate level."""
        reviewer = _make_actor(UserRole.REVIEWER)

        # Step 1: approve source — gate does not apply
        job_before_source = _make_job(
            source_status=LanguageQCStatus.PENDING_REVIEW.value,
        )
        with pytest.raises(HTTPException) as exc:
            _assert_can_approve(job_before_source, "en", reviewer)
        # Only reviewer-assignment gate fires, not english-first
        assert exc.value.status_code != 409

        # Step 2: after source approved, target gate passes
        job_source_approved = _make_job(
            source_status=LanguageQCStatus.APPROVED.value,
            target_status=LanguageQCStatus.PENDING_REVIEW.value,
        )
        with pytest.raises(HTTPException) as exc:
            _assert_can_approve(job_source_approved, "es", reviewer)
        # English-first no longer blocks — only assignment gate
        assert exc.value.status_code != 409
