# #region SQLInsertService [C:4] [TYPE Class] [SEMANTICS sql, insert, superset, translate, direct-db]
# @defgroup Translate Module group.
# @BRIEF Generate INSERT SQL from translation records and submit to target via Superset SQL Lab OR direct DB.
# @PRE Job has target table configured. Run has successful records.
# @POST SQL generated and executed via Superset (sqllab) or DbExecutor (direct_db). insert_method recorded.
# @SIDE_EFFECT Superset API call OR native DB driver call; event log entries.
# @RELATION DEPENDS_ON -> [SQLGenerator]
# @RELATION DEPENDS_ON -> [SupersetSqlLabExecutor]
# @RELATION DEPENDS_ON -> [Core.DbExecutor]
# @RELATION DEPENDS_ON -> [Core.ConnectionService]
# @RELATION DEPENDS_ON -> [TranslationEventLog]
# @RELATION DEPENDS_ON -> [orchestrator_sql_rows]
# @RATIONALE Row/column building extracted to orchestrator_sql_rows module for INV_7 compliance.
#            Direct DB execution added as parallel path — does NOT replace Superset SQL Lab.

from typing import Any

from sqlalchemy.orm import Session, joinedload

from ...core.config_manager import ConfigManager
from ...core.connection_service import ConnectionService
from ...core.db_executor import DbExecutor
from ...core.logger import belief_scope, logger
from ...models.translate import TranslationJob, TranslationRecord, TranslationRun
from .events import TranslationEventLog
from .orchestrator_sql_rows import build_columns, build_context_keys, build_rows
from .sql_generator import SQLGenerator
from .superset_executor import SupersetSqlLabExecutor


class SQLInsertService:
    """Generate INSERT SQL from successful records and submit to target (Superset or direct DB)."""

    def __init__(self, db: Session, config_manager: ConfigManager, event_log: TranslationEventLog):
        self.db = db
        self.config_manager = config_manager
        self.event_log = event_log

    # region generate_and_insert_sql [TYPE Function]
    # @PURPOSE: Generate INSERT SQL and execute via configured insert method (sqllab or direct_db).
    async def generate_and_insert_sql(
        self,
        job: TranslationJob,
        run: TranslationRun,
    ) -> dict[str, Any]:
        with belief_scope("SQLInsertService.generate_and_insert_sql"):
            records = (
                self.db.query(TranslationRecord)
                .options(joinedload(TranslationRecord.languages))
                .filter(
                    TranslationRecord.run_id == run.id,
                    TranslationRecord.status == "SUCCESS",
                    TranslationRecord.target_sql.isnot(None),
                )
                .all()
            )

            if not records:
                logger.reason("No successful records to insert", {"run_id": run.id})
                return {"status": "skipped", "reason": "no_records", "query_id": None}

            effective_target = job.target_column or job.translation_column
            primary_language = (job.target_languages or ["en"])[0]

            columns = build_columns(job, effective_target)
            context_keys = build_context_keys(job, effective_target)
            rows_for_sql = build_rows(records, job, effective_target, primary_language, context_keys)

            if not columns:
                columns = [effective_target or "translated_text"]
                rows_for_sql = [{columns[0]: rec.target_sql or ""} for rec in records]

            dialect = await self._resolve_dialect(job)
            try:
                sql, row_count = SQLGenerator.generate(
                    dialect=dialect,
                    target_schema=job.target_schema,
                    target_table=job.target_table or "translated_data",
                    columns=columns,
                    rows=rows_for_sql,
                    key_columns=job.target_key_cols,
                    upsert_strategy=job.upsert_strategy or "MERGE",
                )
            except ValueError as e:
                logger.explore("SQL generation failed", {"error": str(e)})
                return {"status": "failed", "error_message": str(e), "query_id": None}

            self.event_log.log_event(
                job_id=job.id, run_id=run.id,
                event_type="INSERT_PHASE_STARTED",
                payload={
                    "sql_length": len(sql), "row_count": row_count,
                    "dialect": dialect, "insert_method": job.insert_method,
                },
            )

            # ── Dispatch to correct executor based on insert_method ──
            insert_method = getattr(job, "insert_method", "sqllab") or "sqllab"

            if insert_method == "direct_db" and job.connection_id:
                result = await self._execute_direct_db(job, run, sql)
            else:
                result = await self._execute_superset_sqllab(job, run, sql)

            self.event_log.log_event(
                job_id=job.id, run_id=run.id,
                event_type="INSERT_PHASE_COMPLETED",
                payload={**result, "insert_method": insert_method},
            )
            return result
    # endregion generate_and_insert_sql

    # region _execute_superset_sqllab [TYPE Function]
    # @BRIEF Execute INSERT via Superset SQL Lab API.
    async def _execute_superset_sqllab(
        self, job: TranslationJob, run: TranslationRun, sql: str,
    ) -> dict[str, Any]:
        try:
            env_id = job.environment_id or job.source_dialect or ""
            executor = SupersetSqlLabExecutor(self.config_manager, env_id)
            await executor.resolve_database_id(target_database_id=job.target_database_id)
            result = await executor.execute_and_poll(sql=sql, max_polls=30, poll_interval_seconds=2.0)
        except Exception as e:
            logger.explore("Superset SQL submission failed", {"run_id": run.id, "error": str(e)})
            result = {"status": "failed", "error_message": str(e), "query_id": None}
        return result
    # endregion _execute_superset_sqllab

    # region _execute_direct_db [TYPE Function]
    # @BRIEF Execute INSERT directly against the target database via DbExecutor.
    # @PRE job.connection_id references a valid DatabaseConnection.
    # @POST SQL executed directly. Connection snapshot stored on run for audit.
    # @SIDE_EFFECT Writes to target DB. Connection pool lifecycle managed by DbExecutor.
    async def _execute_direct_db(
        self, job: TranslationJob, run: TranslationRun, sql: str,
    ) -> dict[str, Any]:
        conn_service = ConnectionService(self.config_manager)
        # Snapshot connection metadata for audit (NEVER password)
        conn = conn_service.get_connection(job.connection_id)
        if conn is None:
            return {"status": "failed", "error_message": f"Connection '{job.connection_id}' not found", "query_id": None}

        # Store connection snapshot on run
        run.connection_snapshot = {
            "name": conn.name,
            "dialect": conn.dialect,
            "host": conn.host,
            "port": conn.port,
            "database": conn.database,
        }
        run.insert_method = "direct_db"
        self.db.commit()

        executor = DbExecutor(conn_service)
        result = await executor.execute_sql(job.connection_id, sql)

        if result.success:
            return {
                "status": "success",
                "rows_affected": result.rows_affected,
                "execution_time_ms": result.execution_time_ms,
                "query_id": None,  # Direct DB has no Superset query reference
                "connection_name": conn.name,
            }
        else:
            return {
                "status": "failed",
                "error_message": result.error or "Direct DB insert failed",
                "execution_time_ms": result.execution_time_ms,
                "query_id": None,
            }
    # endregion _execute_direct_db

    # region _resolve_dialect [TYPE Function]
    async def _resolve_dialect(self, job: TranslationJob) -> str:
        if getattr(job, "insert_method", None) == "direct_db" and job.connection_id:
            conn_service = ConnectionService(self.config_manager)
            conn = conn_service.get_connection(job.connection_id)
            if conn:
                return conn.dialect

        try:
            env_id = job.environment_id or job.source_dialect or ""
            executor = SupersetSqlLabExecutor(self.config_manager, env_id)
            await executor.resolve_database_id(target_database_id=job.target_database_id)
            real_backend = executor.get_database_backend()
        except Exception as e:
            logger.explore("Failed to resolve database backend, falling back to job dialect", {"error": str(e)})
            real_backend = None
        return real_backend or job.database_dialect or job.target_dialect or "postgresql"
    # endregion _resolve_dialect

# #endregion SQLInsertService
