# #region TestTranslateStatusAndFK [C:3] [TYPE Module] [SEMANTICS test,translate,integration,status,fk,constraints]
# @BRIEF Integration tests for job status transitions, run state machine, FK constraint enforcement
#         with real PostgreSQL via Testcontainers.
# @RELATION BINDS_TO -> [TranslateJobService]
# @RELATION BINDS_TO -> [TranslationEventLog]
#
# @TEST_CONTRACT JobStatusTransitions ->
#   DRAFT job: create -> delete works
#   non-existent: get/update/delete raises ValueError
#   long name: persists
#
# @TEST_CONTRACT RunStateMachine ->
#   PENDING -> COMPLETED: via cancel_run
#   COMPLETED -> cancel raises ValueError
#   CANCELLED -> cancel raises ValueError
#
# @TEST_EDGE: delete_non_existent_job -> ValueError
# @TEST_EDGE: cancel_non_existent_run -> ValueError
# @TEST_EDGE: cancel_cancelled_run -> ValueError
import pytest
import uuid

from sqlalchemy import text
from sqlalchemy.orm import Session

from src.models.translate import (
    TranslationJob,
    TranslationRun,
    TranslationBatch,
    TranslationRecord,
    TranslationLanguage,
    TranslationEvent,
)
from src.plugins.translate.service import TranslateJobService
from src.schemas.translate import TranslateJobCreate


# #region TestJobStatusTransitions [C:3] [TYPE Class]
@pytest.mark.asyncio
class TestJobStatusTransitions:
    """Verify job status lifecycle transitions with real PostgreSQL."""

    async def test_create_draft_job(self, db_session: Session, mock_config_manager):
        service = TranslateJobService(db_session, mock_config_manager, "test_user")
        payload = TranslateJobCreate(
            name="Draft Test Job",
            source_dialect="postgresql",
            target_dialect="clickhouse",
            translation_column="name",
            target_languages=["ru"],
        )
        job = await service.create_job(payload)
        assert job.status == "DRAFT"

    async def test_delete_job(self, db_session: Session, mock_config_manager):
        service = TranslateJobService(db_session, mock_config_manager, "test_user")
        payload = TranslateJobCreate(
            name="Delete Test", source_dialect="postgresql",
            target_dialect="clickhouse", translation_column="name",
            target_languages=["ru"],
        )
        job = await service.create_job(payload)
        service.delete_job(job.id)

        with pytest.raises(ValueError, match="not found"):
            service.get_job(job.id)

    async def test_delete_non_existent_job(self, db_session: Session, mock_config_manager):
        service = TranslateJobService(db_session, mock_config_manager, "test_user")
        with pytest.raises(ValueError, match="not found"):
            service.delete_job("non-existent-id")

    async def test_get_non_existent_job(self, db_session: Session, mock_config_manager):
        service = TranslateJobService(db_session, mock_config_manager, "test_user")
        with pytest.raises(ValueError, match="not found"):
            service.get_job("non-existent-id")

    async def test_create_job_max_name_length(self, db_session: Session, mock_config_manager):
        service = TranslateJobService(db_session, mock_config_manager, "test_user")
        long_name = "A" * 500
        payload = TranslateJobCreate(
            name=long_name,
            source_dialect="postgresql",
            target_dialect="clickhouse",
            translation_column="name",
            target_languages=["ru"],
        )
        job = await service.create_job(payload)
        assert len(job.name) == 500
# #endregion TestJobStatusTransitions


# #region TestFKConstraintEnforcement [C:3] [TYPE Class]
class TestFKConstraintEnforcement:
    """Verify PostgreSQL FK constraints catch orphaned data."""

    def _assert_fk_violation(self, db: Session, obj):
        """Helper: try adding an orphaned object and expect FK violation."""
        db.add(obj)
        try:
            db.flush()
            db.rollback()
            pytest.fail("Expected FK violation was not raised")
        except Exception:
            db.rollback()

    def test_orphan_run_rejected(self, db_session: Session):
        run = TranslationRun(job_id=str(uuid.uuid4()), status="PENDING", trigger_type="manual")
        self._assert_fk_violation(db_session, run)

    def test_orphan_batch_rejected(self, db_session: Session):
        batch = TranslationBatch(run_id=str(uuid.uuid4()), batch_index=0, status="PENDING")
        self._assert_fk_violation(db_session, batch)

    def test_valid_run_with_job_succeeds(self, db_session: Session):
        job = TranslationJob(
            name="FK Test Job", status="ACTIVE",
            source_dialect="postgresql", target_dialect="clickhouse",
            created_by="test_user",
        )
        db_session.add(job)
        db_session.flush()
        db_session.refresh(job)

        run = TranslationRun(job_id=job.id, status="PENDING", trigger_type="manual")
        db_session.add(run)
        db_session.flush()
        assert run.job_id == job.id
# #endregion TestFKConstraintEnforcement


# #region TestRunRecovery [C:3] [TYPE Class]
class TestRunRecovery:
    """Verify run cancel behavior with real PostgreSQL."""

    def _create_run(self, db: Session, status: str = "PENDING") -> TranslationRun:
        job = TranslationJob(
            name="Cancel Test", status="ACTIVE",
            source_dialect="postgresql", target_dialect="clickhouse",
            created_by="test_user",
        )
        db.add(job)
        db.flush()
        db.refresh(job)

        run = TranslationRun(job_id=job.id, status=status, trigger_type="manual")
        db.add(run)
        db.flush()
        db.refresh(run)
        return run

    def test_cancel_pending_run(self, db_session: Session):
        run = self._create_run(db_session, "PENDING")
        from src.plugins.translate.orchestrator_cancel import cancel_run
        from src.plugins.translate.events import TranslationEventLog

        event_log = TranslationEventLog(db_session)
        result = cancel_run(db_session, event_log, "test_user", run.id)
        assert result.status == "CANCELLED"
        assert result.completed_at is not None

    def test_cancel_failed_run(self, db_session: Session):
        run = self._create_run(db_session, "FAILED")
        from src.plugins.translate.orchestrator_cancel import cancel_run
        from src.plugins.translate.events import TranslationEventLog

        event_log = TranslationEventLog(db_session)
        with pytest.raises(ValueError, match="Cannot cancel"):
            cancel_run(db_session, event_log, "test_user", run.id)

    def test_cancel_completed_run_raises(self, db_session: Session):
        run = self._create_run(db_session, "COMPLETED")
        from src.plugins.translate.orchestrator_cancel import cancel_run
        from src.plugins.translate.events import TranslationEventLog

        event_log = TranslationEventLog(db_session)
        with pytest.raises(ValueError, match="Cannot cancel"):
            cancel_run(db_session, event_log, "test_user", run.id)

    def test_cancel_cancelled_run_raises(self, db_session: Session):
        run = self._create_run(db_session, "CANCELLED")
        from src.plugins.translate.orchestrator_cancel import cancel_run
        from src.plugins.translate.events import TranslationEventLog

        event_log = TranslationEventLog(db_session)
        with pytest.raises(ValueError, match="Cannot cancel"):
            cancel_run(db_session, event_log, "test_user", run.id)

    def test_cancel_non_existent_run(self, db_session: Session):
        from src.plugins.translate.orchestrator_cancel import cancel_run
        from src.plugins.translate.events import TranslationEventLog

        event_log = TranslationEventLog(db_session)
        with pytest.raises((ValueError, KeyError)):
            cancel_run(db_session, event_log, "test_user", str(uuid.uuid4()))
# #endregion TestRunRecovery


# #region TestEventLogStateMachine [C:3] [TYPE Class]
class TestEventLogStateMachine:
    """Verify event log state machine rules with real PostgreSQL."""

    def _create_job_and_run(self, db: Session) -> tuple:
        job = TranslationJob(
            name="Event Log Job", status="ACTIVE",
            source_dialect="postgresql", target_dialect="clickhouse",
            created_by="test_user",
        )
        db.add(job)
        db.flush()
        db.refresh(job)

        run = TranslationRun(job_id=job.id, status="RUNNING", trigger_type="manual")
        db.add(run)
        db.flush()
        db.refresh(run)
        return job, run

    def test_event_log_persistence(self, db_session: Session):
        job, run = self._create_job_and_run(db_session)
        from src.plugins.translate.events import TranslationEventLog

        event_log = TranslationEventLog(db_session)
        event_log.log_event(job.id, "RUN_STARTED", {"strategy": "incremental"}, run.id)
        event_log.log_event(job.id, "BATCH_STARTED", {"batch_index": 0}, run.id)
        event_log.log_event(job.id, "BATCH_COMPLETED", {"batch_index": 0, "records": 10}, run.id)
        event_log.log_event(job.id, "RUN_COMPLETED", {"total_records": 10}, run.id)

        events = (
            db_session.query(TranslationEvent)
            .filter(TranslationEvent.run_id == run.id)
            .order_by(TranslationEvent.created_at)
            .all()
        )
        assert len(events) == 4

    def test_invalid_event_type_raises(self, db_session: Session):
        job, run = self._create_job_and_run(db_session)
        from src.plugins.translate.events import TranslationEventLog

        event_log = TranslationEventLog(db_session)
        with pytest.raises(ValueError, match="Invalid event_type"):
            event_log.log_event(job.id, run.id, "INVALID_EVENT", {})
# #endregion TestEventLogStateMachine
# #endregion TestTranslateStatusAndFK
