# #region TranslateRunRoutesModule [C:3] [TYPE Module] [SEMANTICS fastapi, translate, api, search, execution, history]
# @BRIEF Translation Run execution, history, status, records and batches routes.
# @LAYER API

from datetime import UTC, datetime

from fastapi import Depends, HTTPException, Query, status
from sqlalchemy.orm import Session

from ....core.config_manager import ConfigManager
from ....core.database import SessionLocal, get_db
from ....core.logger import logger
from ....dependencies import get_config_manager, get_current_user, has_permission
from ....plugins.translate.orchestrator import TranslationOrchestrator
from ....schemas.auth import User
from ....schemas.translate import (
    BulkFindReplaceRequest,
    InlineEditRequest,
    OverrideLanguageRequest,
)
from ._helpers import _run_to_response
from ._router import router

# ============================================================
# Translation Run / Execute
# ============================================================

# #region run_translation [C:4] [TYPE Function]
# @BRIEF Execute a translation job (trigger a run).
# @PRE  User has translate.job.execute permission.
# @POST  Returns the created translation run.
@router.post("/jobs/{job_id}/run", status_code=status.HTTP_201_CREATED)
def run_translation(
    job_id: str,
    full_translation: bool = Query(False, description="Translate ALL rows (skip new-key-only filter)"),
    current_user: User = Depends(get_current_user),
    _ = Depends(has_permission("translate.job", "EXECUTE")),
    db: Session = Depends(get_db),
    config_manager: ConfigManager = Depends(get_config_manager),
):
    """Execute a translation job (trigger a run).

    - Normal run (default): translates only new/changed rows
    - Full translation (full_translation=true): re-translates ALL rows from the datasource
    """
    logger.reason(
        f"run_translation — Job: {job_id}, User: {current_user.username}, full: {full_translation}",
        extra={"src": "translate_routes"},
    )
    try:
        orch = TranslationOrchestrator(db, config_manager, current_user.username)
        run = orch.start_run(job_id=job_id, is_scheduled=False, full_translation=full_translation)
        # The request-scoped db session will be closed after this handler returns.
        # The background thread must use its OWN session to avoid operating on a
        # closed or expunged session.
        import threading

        def _background_execute():
            from ....models.translate import TranslationRun as TRModel
            bg_db = None
            try:
                bg_db = SessionLocal()
                bg_orch = TranslationOrchestrator(
                    bg_db, config_manager,
                    current_user.username if current_user else None,
                )
                # Re-fetch the run within the background session to get a fresh,
                # attached object
                bg_run = bg_db.query(TRModel).filter(TRModel.id == run.id).first()
                if bg_run is None:
                    logger.explore(
                        "Background execute: run not found",
                        extra={"src": "translate_routes", "run_id": run.id},
                    )
                    return

                # execute_run internally calls self.db.commit()
                bg_orch.execute_run(bg_run)

                # ----- VERIFICATION -----
                # After execute_run the run object from bg_db is detached after
                # commit.  Re-query to verify the status was persisted in the
                # database and is a terminal state.
                check_run = bg_db.query(TRModel).filter(TRModel.id == run.id).first()
                if check_run and check_run.status in ("COMPLETED", "FAILED", "CANCELLED"):
                    logger.reason(
                        "Background execute verified",
                        extra={
                            "src": "translate_routes",
                            "run_id": run.id,
                            "status": check_run.status,
                        },
                    )
                else:
                    # execute_run appeared to succeed but the run is still in a
                    # non-terminal state  — manually fail it so the frontend
                    # polling can detect the terminal state and stop spinning.
                    actual_status = check_run.status if check_run else "NOT_FOUND"
                    logger.explore(
                        "Background execute: run not in terminal state after commit",
                        extra={
                            "src": "translate_routes",
                            "run_id": run.id,
                            "status": actual_status,
                        },
                    )
                    if check_run:
                        check_run.status = "FAILED"
                        check_run.error_message = (
                            "Background execution did not reach terminal state "
                            f"(was: {actual_status})"
                        )
                        bg_db.commit()

            except Exception as bg_err:
                logger.explore(
                    "Background execute failed",
                    extra={
                        "src": "translate_routes",
                        "run_id": run.id,
                        "error": str(bg_err),
                    },
                )
                # Mark the run as FAILED so the frontend polling can detect a
                # terminal state and stop spinning.
                try:
                    fb_db = SessionLocal()
                    try:
                        fb_run = fb_db.query(TRModel).filter(TRModel.id == run.id).first()
                        if fb_run:
                            fb_run.status = "FAILED"
                            fb_run.error_message = f"Background execute error: {bg_err}"
                            fb_run.completed_at = datetime.now(UTC)
                            fb_db.commit()
                            logger.reason(
                                "Background execute: run marked FAILED",
                                extra={"src": "translate_routes", "run_id": run.id},
                            )
                    finally:
                        fb_db.close()
                except Exception as fb_err:
                    logger.explore(
                        "Background execute: unable to mark run FAILED",
                        extra={
                            "src": "translate_routes",
                            "run_id": run.id,
                            "error": str(fb_err),
                        },
                    )
            finally:
                if bg_db is not None:
                    bg_db.close()

        threading.Thread(
            target=_background_execute,
            daemon=True,
        ).start()
        return _run_to_response(run)
    except ValueError as e:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
    except Exception as e:
        logger.explore("run_translation failed", extra={"src": "translate_routes", "error": str(e)})
        raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Run failed: {e}")
# #endregion run_translation


# #region retry_run [C:4] [TYPE Function]
# @BRIEF Retry failed batches in a translation run.
# @PRE  User has translate.job.execute permission.
# @POST  Returns the updated translation run.
@router.post("/runs/{run_id}/retry")
def retry_run(
    run_id: str,
    current_user: User = Depends(get_current_user),
    _ = Depends(has_permission("translate.job", "EXECUTE")),
    db: Session = Depends(get_db),
    config_manager: ConfigManager = Depends(get_config_manager),
):
    """Retry failed batches in a translation run."""
    logger.reason(f"retry_run — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
    try:
        orch = TranslationOrchestrator(db, config_manager, current_user.username)
        run = orch.retry_failed_batches(run_id)
        return _run_to_response(run)
    except ValueError as e:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
    except Exception as e:
        logger.explore("retry_run failed", extra={"src": "translate_routes", "error": str(e)})
        raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Retry failed: {e}")
# #endregion retry_run


# #region retry_insert [C:4] [TYPE Function]
# @BRIEF Retry the SQL insert phase for a completed run.
# @PRE  User has translate.job.execute permission.
# @POST  Returns the updated run.
@router.post("/runs/{run_id}/retry-insert")
def retry_insert(
    run_id: str,
    current_user: User = Depends(get_current_user),
    _ = Depends(has_permission("translate.job", "EXECUTE")),
    db: Session = Depends(get_db),
    config_manager: ConfigManager = Depends(get_config_manager),
):
    """Retry the SQL insert phase for a completed run."""
    logger.reason(f"retry_insert — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
    try:
        orch = TranslationOrchestrator(db, config_manager, current_user.username)
        run = orch.retry_insert(run_id)
        return _run_to_response(run)
    except ValueError as e:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
    except Exception as e:
        logger.explore("retry_insert failed", extra={"src": "translate_routes", "error": str(e)})
        raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Retry insert failed: {e}")
# #endregion retry_insert


# #region cancel_run [C:4] [TYPE Function]
# @BRIEF Cancel a running translation.
# @PRE  User has translate.job.execute permission.
# @POST  Run is cancelled.
@router.post("/runs/{run_id}/cancel")
def cancel_run(
    run_id: str,
    current_user: User = Depends(get_current_user),
    _ = Depends(has_permission("translate.job", "EXECUTE")),
    db: Session = Depends(get_db),
    config_manager: ConfigManager = Depends(get_config_manager),
):
    """Cancel a running translation."""
    logger.reason(f"cancel_run — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
    try:
        orch = TranslationOrchestrator(db, config_manager, current_user.username)
        run = orch.cancel_run(run_id)
        return _run_to_response(run)
    except ValueError as e:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# #endregion cancel_run


# #region get_run_history [C:4] [TYPE Function]
# @BRIEF Get run history for a translation job.
# @PRE  User has translate.history.view permission.
# @POST  Returns list of runs.
@router.get("/jobs/{job_id}/runs")
def get_run_history(
    job_id: str,
    page: int = Query(1, ge=1),
    page_size: int = Query(20, ge=1, le=100),
    current_user: User = Depends(get_current_user),
    _ = Depends(has_permission("translate.history", "VIEW")),
    db: Session = Depends(get_db),
    config_manager: ConfigManager = Depends(get_config_manager),
):
    """Get run history for a translation job."""
    logger.reason(f"get_run_history — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
    try:
        orch = TranslationOrchestrator(db, config_manager, current_user.username)
        total, runs = orch.get_run_history(job_id, page=page, page_size=page_size)
        return {"items": runs, "total": total, "page": page, "page_size": page_size}
    except ValueError as e:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
# #endregion get_run_history


# #region get_run_status [C:4] [TYPE Function]
# @BRIEF Get status and statistics for a translation run.
# @PRE  User has translate.history.view permission.
# @POST  Returns run details with statistics.
@router.get("/runs/{run_id}")
def get_run_status(
    run_id: str,
    current_user: User = Depends(get_current_user),
    _ = Depends(has_permission("translate.history", "VIEW")),
    db: Session = Depends(get_db),
    config_manager: ConfigManager = Depends(get_config_manager),
):
    """Get status and statistics for a translation run."""
    logger.reason(f"get_run_status — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
    try:
        orch = TranslationOrchestrator(db, config_manager, current_user.username)
        return orch.get_run_status(run_id)
    except ValueError as e:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
# #endregion get_run_status


# #region get_run_records [C:4] [TYPE Function]
# @BRIEF Get paginated records for a translation run.
# @PRE  User has translate.history.view permission.
# @POST  Returns paginated records.
@router.get("/runs/{run_id}/records")
def get_run_records(
    run_id: str,
    page: int = Query(1, ge=1),
    page_size: int = Query(50, ge=1, le=500),
    status: str | None = Query(None),
    current_user: User = Depends(get_current_user),
    _ = Depends(has_permission("translate.history", "VIEW")),
    db: Session = Depends(get_db),
    config_manager: ConfigManager = Depends(get_config_manager),
):
    """Get paginated records for a translation run."""
    logger.reason(f"get_run_records — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
    try:
        orch = TranslationOrchestrator(db, config_manager, current_user.username)
        return orch.get_run_records(run_id, page=page, page_size=page_size, status_filter=status)
    except ValueError as e:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
# #endregion get_run_records


# ============================================================
# Batches
# ============================================================

# #region get_batches [C:4] [TYPE Function]
# @BRIEF Get batches for a translation run.
# @PRE  User has translate.job.view permission.
# @POST  Returns list of batches.
@router.get("/runs/{run_id}/batches")
def get_batches(
    run_id: str,
    current_user: User = Depends(get_current_user),
    _ = Depends(has_permission("translate.job", "VIEW")),
    db: Session = Depends(get_db),
):
    """Get batches for a translation run."""
    logger.reason(f"get_batches — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
    try:
        from ....models.translate import TranslationBatch
        batches = (
            db.query(TranslationBatch)
            .filter(TranslationBatch.run_id == run_id)
            .order_by(TranslationBatch.batch_index.asc())
            .all()
        )
        return [
            {
                "id": b.id,
                "run_id": b.run_id,
                "batch_index": b.batch_index,
                "status": b.status,
                "total_records": b.total_records or 0,
                "successful_records": b.successful_records or 0,
                "failed_records": b.failed_records or 0,
                "started_at": b.started_at.isoformat() if b.started_at else None,
                "completed_at": b.completed_at.isoformat() if b.completed_at else None,
                "created_at": b.created_at.isoformat() if b.created_at else None,
            }
            for b in batches
        ]
    except Exception as e:
        logger.explore("get_batches failed", extra={"src": "translate_routes", "error": str(e)})
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# #endregion get_batches

# ============================================================
# Manual Language Override
# ============================================================

# #region override_detected_language [C:4] [TYPE Function]
# @BRIEF Manually override the detected source language for a specific translation language entry.
# @PRE  User has translate.job.execute permission. Run, record, and language entry exist.
# @POST  TranslationLanguage.source_language_detected is updated; language_overridden is set to True.
# @SIDE_EFFECT  DB write.
@router.put("/runs/{run_id}/records/{record_id}/languages/{language_code}/override-language")
def override_detected_language(
    run_id: str,
    record_id: str,
    language_code: str,
    payload: OverrideLanguageRequest,
    current_user: User = Depends(get_current_user),
    _ = Depends(has_permission("translate.job", "EXECUTE")),
    db: Session = Depends(get_db),
):
    """Manually override the detected source language for a translation entry."""
    logger.reason(f"override_detected_language — Run: {run_id}, Record: {record_id}, Lang: {language_code}, Override: {payload.source_language}", extra={"src": "translate_routes"})
    try:
        from ....models.translate import TranslationLanguage, TranslationRecord

        # Verify the record exists and belongs to the run
        record = (
            db.query(TranslationRecord)
            .filter(
                TranslationRecord.id == record_id,
                TranslationRecord.run_id == run_id,
            )
            .first()
        )
        if not record:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Translation record '{record_id}' not found in run '{run_id}'",
            )

        # Find the language entry
        lang_entry = (
            db.query(TranslationLanguage)
            .filter(
                TranslationLanguage.record_id == record_id,
                TranslationLanguage.language_code == language_code,
            )
            .first()
        )
        if not lang_entry:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Language entry '{language_code}' not found for record '{record_id}'",
            )

        # Update the language entry
        lang_entry.source_language_detected = payload.source_language
        lang_entry.language_overridden = True
        lang_entry.needs_review = False  # Override clears the review flag
        db.commit()
        db.refresh(lang_entry)

        logger.reason("Language override applied", {
            "run_id": run_id,
            "record_id": record_id,
            "language_code": language_code,
            "new_source_language": payload.source_language,
            "overridden_by": current_user.username,
        })

        from ....schemas.translate import TranslationLanguageResponse
        return TranslationLanguageResponse.model_validate(lang_entry)
    except HTTPException:
        raise
    except Exception as e:
        logger.explore("override_detected_language failed", extra={"src": "translate_routes", "error": str(e)})
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# #endregion override_detected_language


# ============================================================
# Inline Correction
# ============================================================

# #region inline_edit_translation [C:4] [TYPE Function] [SEMANTICS api,translate,correction]
# @BRIEF Apply an inline correction to a translated value on a completed run result.
# @PRE  User has translate.job.execute permission. Run, record, and language entry exist.
# @POST  TranslationLanguage.final_value and user_edit are updated. Optional dictionary submission.
@router.put("/runs/{run_id}/records/{record_id}/languages/{language_code}")
def inline_edit_translation(
    run_id: str,
    record_id: str,
    language_code: str,
    payload: InlineEditRequest,
    current_user: User = Depends(get_current_user),
    _ = Depends(has_permission("translate.job", "EXECUTE")),
    db: Session = Depends(get_db),
):
    """Apply an inline correction to a translated value."""
    logger.reason(
        f"inline_edit_translation — Run: {run_id}, Record: {record_id}, "
        f"Lang: {language_code}, User: {current_user.username}",
        extra={"src": "translate_routes"},
    )
    try:
        from ....plugins.translate.service import InlineCorrectionService

        result = InlineCorrectionService.apply_inline_edit(
            db=db,
            run_id=run_id,
            record_id=record_id,
            language_code=language_code,
            final_value=payload.final_value,
            submit_to_dictionary=payload.submit_to_dictionary,
            dictionary_id=payload.dictionary_id,
            current_user=current_user.username,
            context_data_override=payload.context_data_override,
            usage_notes=payload.usage_notes,
            keep_context=payload.keep_context,
        )
        return result
    except ValueError as e:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
    except Exception as e:
        logger.explore("inline_edit_translation failed", extra={"src": "translate_routes", "error": str(e)})
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# #endregion inline_edit_translation


# ============================================================
# Bulk Find-and-Replace
# ============================================================

# #region bulk_find_replace [C:4] [TYPE Function] [SEMANTICS api,translate,bulk,replace]
# @BRIEF Perform bulk find-and-replace on translated values within a run.
# @PRE User has translate.job.execute permission. Run exists.
# @POST If preview=false, matching translations are updated. Optional dictionary submission.
# @COMPLEXITY 4

@router.post("/runs/{run_id}/bulk-replace")
def bulk_find_replace(
    run_id: str,
    payload: BulkFindReplaceRequest,
    current_user: User = Depends(get_current_user),
    _ = Depends(has_permission("translate.job", "EXECUTE")),
    db: Session = Depends(get_db),
):
    """Perform bulk find-and-replace on translated values within a run."""
    logger.reason(
        f"bulk_find_replace — Run: {run_id}, Pattern: '{payload.find_pattern}', "
        f"Regex: {payload.is_regex}, Lang: {payload.target_language}, "
        f"User: {current_user.username}",
        extra={"src": "translate_routes"},
    )
    try:
        from ....plugins.translate.service import BulkFindReplaceService

        if payload.preview:
            # Preview mode: return matches without applying
            preview = BulkFindReplaceService.preview(
                db=db,
                run_id=run_id,
                pattern=payload.find_pattern,
                is_regex=payload.is_regex,
                replacement_text=payload.replacement_text,
                target_language=payload.target_language,
            )
            return {
                "rows_affected": len(preview),
                "corrections_submitted": 0,
                "preview": preview,
            }

        # Apply mode: perform replacements
        result = BulkFindReplaceService.apply(
            db=db,
            run_id=run_id,
            pattern=payload.find_pattern,
            is_regex=payload.is_regex,
            replacement_text=payload.replacement_text,
            target_language=payload.target_language,
            submit_to_dictionary=payload.submit_to_dictionary,
            dictionary_id=payload.dictionary_id,
            usage_notes=payload.usage_notes,
            current_user=current_user.username,
            submit_to_dictionary_with_context=payload.submit_to_dictionary_with_context,
        )
        return result
    except ValueError as e:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
    except Exception as e:
        logger.explore("bulk_find_replace failed", extra={"src": "translate_routes", "error": str(e)})
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# #endregion bulk_find_replace


# #endregion TranslateRunRoutesModule
