# #region TranslateJobService [C:4] [TYPE Module] [SEMANTICS sqlalchemy, translate, job, datasource, dialect]
# @BRIEF Service layer for translation job CRUD with datasource column validation and database dialect detection.
# @LAYER Domain
# @RELATION DEPENDS_ON -> [TranslationJob]
# @RELATION DEPENDS_ON -> [SupersetClient]
# @RELATION DEPENDS_ON -> [ConfigManager]
# @PRE Database session and config manager are available.
# @POST Translation jobs are created/updated/deleted with column validation and dialect caching.
# @SIDE_EFFECT Queries Superset for column metadata and database dialect at save time.
# @RATIONALE Snapshot isolation — in-progress runs use config snapshot; config edits affect future runs only.
# @REJECTED Invalidating in-progress runs on config edit would break scheduled run continuity.
# @REJECTED Monolithic service.py — violated INV_7. Decomposed into service_datasource, service_inline_correction, service_bulk_replace, service_utils.

from datetime import UTC, datetime
import uuid

from sqlalchemy.orm import Session

from ...core.config_manager import ConfigManager
from ...core.logger import logger
from ...models.translate import TranslationJob, TranslationJobDictionary
from ...schemas.translate import TranslateJobCreate, TranslateJobUpdate
from .dictionary_validation import _validate_bcp47
from .service_datasource import fetch_datasource_metadata
from .service_utils import _extract_dialect


# #region TranslateJobService [TYPE Class]
# @BRIEF Service for translation job CRUD with validation and Superset integration.
class TranslateJobService:
    """CRUD service for translation jobs with Superset datasource integration."""

    def __init__(self, db: Session, config_manager: ConfigManager, current_user: str | None = None):
        self.db = db
        self.config_manager = config_manager
        self.current_user = current_user

    # region list_jobs [TYPE Function]
    # @PURPOSE: List translation jobs with optional status filter and pagination.
    def list_jobs(
        self,
        page: int = 1,
        page_size: int = 20,
        status_filter: str | None = None,
    ) -> tuple[int, list[TranslationJob]]:
        query = self.db.query(TranslationJob)
        if status_filter:
            query = query.filter(TranslationJob.status == status_filter)
        total = query.count()
        offset = (page - 1) * page_size
        jobs = query.order_by(TranslationJob.created_at.desc()).offset(offset).limit(page_size).all()
        return total, jobs
    # endregion list_jobs

    # region get_job [TYPE Function]
    # @PURPOSE: Get a single translation job by ID.
    def get_job(self, job_id: str) -> TranslationJob:
        job = self.db.query(TranslationJob).filter(TranslationJob.id == job_id).first()
        if not job:
            raise ValueError(f"Translation job '{job_id}' not found")
        return job
    # endregion get_job

    # region create_job [TYPE Function]
    # @PURPOSE: Create a new translation job with column validation.
    # @SIDE_EFFECT  Validates columns via SupersetClient; caches database_dialect.
    def create_job(self, payload: TranslateJobCreate) -> TranslationJob:
        logger.info(f"[TranslateJobService] Creating job '{payload.name}'")
        if payload.source_datasource_id and not payload.translation_column:
            raise ValueError("A translation column is required when a datasource is selected")
        valid_strategies = {"MERGE", "INSERT", "UPDATE"}
        if payload.upsert_strategy not in valid_strategies:
            raise ValueError(
                f"Invalid upsert_strategy '{payload.upsert_strategy}'. "
                f"Must be one of: {', '.join(sorted(valid_strategies))}"
            )

        dialect = payload.database_dialect
        if payload.source_datasource_id and (payload.environment_id or payload.source_dialect):
            if not dialect:
                try:
                    env_id = payload.environment_id or payload.source_dialect
                    _, detected_dialect = fetch_datasource_metadata(
                        int(payload.source_datasource_id), env_id, self.config_manager,
                    )
                    dialect = detected_dialect
                except Exception as e:
                    logger.warning(f"[TranslateJobService] Dialect detection failed: {e}")
                    dialect = payload.source_dialect

        target_languages = payload.target_languages
        if not target_languages and payload.target_language:
            target_languages = [payload.target_language]
        if target_languages:
            if not isinstance(target_languages, list):
                target_languages = [str(target_languages)]
            for lang in target_languages:
                _validate_bcp47(lang, "target_languages")

        job = TranslationJob(
            id=str(uuid.uuid4()), name=payload.name, description=payload.description,
            source_dialect=payload.source_dialect, target_dialect=payload.target_dialect,
            database_dialect=dialect, source_datasource_id=payload.source_datasource_id,
            source_table=payload.source_table, target_schema=payload.target_schema,
            target_table=payload.target_table, source_key_cols=payload.source_key_cols or [],
            target_key_cols=payload.target_key_cols or [],
            translation_column=payload.translation_column, target_column=payload.target_column,
            target_language_column=payload.target_language_column,
            target_source_column=payload.target_source_column,
            target_source_language_column=payload.target_source_language_column,
            context_columns=payload.context_columns or [], target_languages=target_languages,
            provider_id=payload.provider_id, batch_size=payload.batch_size,
            upsert_strategy=payload.upsert_strategy, environment_id=payload.environment_id,
            target_database_id=payload.target_database_id,
            status="DRAFT", created_by=self.current_user,
        )
        self.db.add(job)
        self.db.flush()

        if payload.dictionary_ids:
            for dict_id in payload.dictionary_ids:
                self.db.add(TranslationJobDictionary(
                    id=str(uuid.uuid4()), job_id=job.id, dictionary_id=dict_id,
                ))

        self.db.commit()
        self.db.refresh(job)
        logger.info(f"[TranslateJobService] Created job '{job.id}'")
        return job
    # endregion create_job

    # region update_job [TYPE Function]
    # @PURPOSE: Update an existing translation job.
    # @SIDE_EFFECT  Re-detects database_dialect if source_datasource_id changed.
    def update_job(self, job_id: str, payload: TranslateJobUpdate) -> TranslationJob:
        logger.info(f"[TranslateJobService] Updating job '{job_id}'")
        job = self.get_job(job_id)
        update_data = payload.model_dump(exclude_unset=True)
        dict_ids = update_data.pop("dictionary_ids", None)

        if "target_language" in update_data:
            tl = update_data.pop("target_language")
            if tl and "target_languages" not in update_data:
                update_data["target_languages"] = [tl]

        if update_data.get("target_languages"):
            if not isinstance(update_data["target_languages"], list):
                update_data["target_languages"] = [str(update_data["target_languages"])]
            for lang in update_data["target_languages"]:
                _validate_bcp47(lang, "target_languages")

        for field, value in update_data.items():
            if hasattr(job, field):
                setattr(job, field, value)

        if payload.source_datasource_id and not payload.database_dialect:
            try:
                env_id = (payload.environment_id or payload.source_dialect or job.environment_id or job.source_dialect)
                _, detected_dialect = fetch_datasource_metadata(
                    int(payload.source_datasource_id), env_id, self.config_manager,
                )
                job.database_dialect = detected_dialect
            except Exception as e:
                logger.warning(f"[TranslateJobService] Dialect re-detection failed: {e}")

        job.updated_at = datetime.now(UTC)

        if dict_ids is not None:
            self.db.query(TranslationJobDictionary).filter(TranslationJobDictionary.job_id == job_id).delete()
            for dict_id in dict_ids:
                self.db.add(TranslationJobDictionary(
                    id=str(uuid.uuid4()), job_id=job_id, dictionary_id=dict_id,
                ))

        self.db.commit()
        self.db.refresh(job)
        logger.info(f"[TranslateJobService] Updated job '{job_id}'")
        return job
    # endregion update_job

    # region delete_job [TYPE Function]
    # @PURPOSE: Delete a translation job and its associations.
    def delete_job(self, job_id: str) -> None:
        logger.info(f"[TranslateJobService] Deleting job '{job_id}'")
        job = self.get_job(job_id)
        self.db.query(TranslationJobDictionary).filter(TranslationJobDictionary.job_id == job_id).delete()
        self.db.delete(job)
        self.db.commit()
        logger.info(f"[TranslateJobService] Deleted job '{job_id}'")
    # endregion delete_job

    # region duplicate_job [TYPE Function]
    # @PURPOSE: Duplicate a translation job with a new name.
    def duplicate_job(self, job_id: str, new_name: str | None = None) -> TranslationJob:
        logger.info(f"[TranslateJobService] Duplicating job '{job_id}'")
        source = self.get_job(job_id)
        new_job = TranslationJob(
            id=str(uuid.uuid4()), name=new_name or f"{source.name} (Copy)",
            description=source.description, source_dialect=source.source_dialect,
            target_dialect=source.target_dialect, database_dialect=source.database_dialect,
            source_datasource_id=source.source_datasource_id, source_table=source.source_table,
            target_schema=source.target_schema, target_table=source.target_table,
            source_key_cols=source.source_key_cols, target_key_cols=source.target_key_cols,
            translation_column=source.translation_column, target_column=source.target_column,
            target_language_column=source.target_language_column,
            target_source_column=source.target_source_column,
            target_source_language_column=source.target_source_language_column,
            context_columns=source.context_columns, target_languages=source.target_languages,
            provider_id=source.provider_id, batch_size=source.batch_size,
            upsert_strategy=source.upsert_strategy, status="DRAFT",
            created_by=self.current_user,
        )
        self.db.add(new_job)
        self.db.flush()

        old_dicts = self.db.query(TranslationJobDictionary).filter(TranslationJobDictionary.job_id == job_id).all()
        for assoc in old_dicts:
            self.db.add(TranslationJobDictionary(
                id=str(uuid.uuid4()), job_id=new_job.id, dictionary_id=assoc.dictionary_id,
            ))

        self.db.commit()
        self.db.refresh(new_job)
        logger.info(f"[TranslateJobService] Duplicated job '{job_id}' -> '{new_job.id}'")
        return new_job
    # endregion duplicate_job

    # region get_job_dictionary_ids [TYPE Function]
    # @PURPOSE: Get dictionary IDs attached to a job.
    def get_job_dictionary_ids(self, job_id: str) -> list[str]:
        assocs = self.db.query(TranslationJobDictionary).filter(TranslationJobDictionary.job_id == job_id).all()
        return [a.dictionary_id for a in assocs]
    # endregion get_job_dictionary_ids

    # region fetch_available_datasources [TYPE Function]
    # @PURPOSE: List available Superset datasets for translation job creation.
    def fetch_available_datasources(self, env_id: str, search: str | None = None) -> list:
        from ...core.superset_client import SupersetClient
        env = self.config_manager.get_environment(env_id)
        if not env:
            raise ValueError(f"Environment '{env_id}' not found")
        client = SupersetClient(env)
        _, datasets = client.get_datasets()
        result = []
        for ds in datasets:
            name = ds.get("table_name", "")
            if search and search.lower() not in name.lower():
                continue
            db_info = ds.get("database", {})
            backend = db_info.get("backend", "")
            dialect = _extract_dialect(backend)
            result.append({
                "id": ds.get("id"),
                "table_name": name,
                "schema": ds.get("schema"),
                "database_name": db_info.get("database_name", "Unknown"),
                "database_id": db_info.get("id"),  # <-- добавлено: ID БД для target_database_id
                "database_dialect": dialect,
                "description": ds.get("description", ""),
            })
        return result
    # endregion fetch_available_datasources
# #endregion TranslateJobService


# Re-exports for backward compatibility
from .service_bulk_replace import BulkFindReplaceService  # noqa: E402, F401
from .service_datasource import (  # noqa: E402, F401
    detect_virtual_columns,
    get_datasource_columns,
    get_dialect_from_database,
)
from .service_inline_correction import InlineCorrectionService  # noqa: E402, F401
# #endregion TranslateJobService
