# #region PreviewExecutor [C:4] [TYPE Class] [SEMANTICS sqlalchemy, superset, llm, preview]
# @BRIEF Fetch sample data from Superset and call LLM provider for preview.
# @PRE Database session, config manager, and Superset client are available.
# @POST Sample rows fetched, LLM called.
# @SIDE_EFFECT Fetches data from Superset; makes HTTP calls to LLM provider.
# @RELATION DEPENDS_ON -> [SupersetClient]
# @RELATION DEPENDS_ON -> [LLMProviderService]
# @RELATION DEPENDS_ON -> [LLMClient]
# @RELATION DEPENDS_ON -> [preview_response_parser]
# @RATIONALE LLM response parsing and hash utilities extracted to preview_response_parser module for INV_7 compliance.

import json
from typing import Any

from sqlalchemy.orm import Session

from ...core.config_manager import ConfigManager
from ...core.logger import belief_scope, logger
from ...core.superset_client import SupersetClient
from ...models.translate import TranslationJob
from .preview_llm_client import LLMClient
from .preview_resolve_provider import resolve_provider_model as _resolve_provider_model
from .preview_response_parser import (
    compute_config_hash as _compute_config_hash,
    compute_dict_snapshot_hash as _compute_dict_snapshot_hash,
    extract_data_rows as _extract_data_rows,
    parse_llm_response as _parse_llm_response,
)


class PreviewExecutor:
    """Execute preview operations: fetch sample data from Superset, call LLM, parse responses."""

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

    # region fetch_sample_rows [TYPE Function]
    # @PURPOSE: Fetch sample rows from Superset dataset for preview.
    # @SIDE_EFFECT  Calls Superset chart data endpoint.
    def fetch_sample_rows(
        self,
        job: TranslationJob,
        sample_size: int = 10,
        env_id: str | None = None,
    ) -> list[dict[str, Any]]:
        with belief_scope("PreviewExecutor.fetch_sample_rows"):
            environments = self.config_manager.get_environments()
            target_env_id = env_id or job.environment_id or job.source_dialect or ""
            env_config = next((e for e in environments if e.id == target_env_id), None)
            if not env_config:
                if environments:
                    env_config = environments[0]
                else:
                    raise ValueError("No Superset environments configured")

            client = SupersetClient(env_config)
            dataset_detail = client.get_dataset_detail(int(job.source_datasource_id))
            query_context = client.build_dataset_preview_query_context(
                dataset_id=int(job.source_datasource_id), dataset_record=dataset_detail,
                template_params={}, effective_filters=[],
            )
            queries = query_context.get("queries", [])
            if queries:
                queries[0]["row_limit"] = sample_size
                queries[0].pop("result_type", None)
                queries[0].pop("columns", None)
                queries[0]["metrics"] = []
            query_context["result_type"] = "samples"
            form_data = query_context.get("form_data", {})
            form_data.pop("query_mode", None)

            try:
                response = client.network.request(
                    method="POST", endpoint="/api/v1/chart/data",
                    data=json.dumps(query_context),
                    headers={"Content-Type": "application/json"},
                )
            except Exception as e:
                raise ValueError(f"Failed to fetch sample data from Superset: {e}")

            return _extract_data_rows(response)
    # endregion fetch_sample_rows

    # region call_llm [TYPE Function]
    # @PURPOSE: Call the configured LLM provider with a prompt.
    # @SIDE_EFFECT  Makes HTTP call to LLM provider.
    def call_llm(self, job: TranslationJob, prompt: str, max_tokens: int = 8192) -> str:
        with belief_scope("PreviewExecutor.call_llm"):
            if not job.provider_id:
                raise ValueError("Job has no LLM provider configured")
            from ...services.llm_provider import LLMProviderService
            provider_svc = LLMProviderService(self.db)
            provider = provider_svc.get_provider(job.provider_id)
            if not provider:
                raise ValueError(f"LLM provider '{job.provider_id}' not found")
            api_key = provider_svc.get_decrypted_api_key(job.provider_id)
            if not api_key:
                raise ValueError(f"Could not decrypt API key for provider '{job.provider_id}'")

            model = provider.default_model or "gpt-4o-mini"
            provider_type = provider.provider_type.lower() if provider.provider_type else "openai"
            disable_reasoning = getattr(job, 'disable_reasoning', False)

            if provider_type not in ("openai", "openai_compatible", "openrouter", "kilo", "litellm"):
                raise ValueError(f"Unsupported provider type '{provider_type}' for preview")

            max_attempts = 2
            for attempt in range(max_attempts):
                try:
                    response_text = LLMClient.call_openai_compatible(
                        base_url=provider.base_url, api_key=api_key, model=model,
                        prompt=prompt, provider_type=provider_type,
                        max_tokens=max_tokens * (attempt + 1),
                        disable_reasoning=disable_reasoning or (attempt > 0),
                    )
                    break
                except ValueError as e:
                    if "empty content" in str(e) and attempt < max_attempts - 1:
                        logger.explore("Empty LLM response, retrying with doubled max_tokens")
                        continue
                    raise

            return response_text
    # endregion call_llm

    # region delegation_helpers [TYPE Function]
    # @PURPOSE: Backward-compatible delegation to preview_response_parser module functions.

    @staticmethod
    def parse_llm_response(
        response_text: str, expected_count: int,
        target_languages: list[str] | None = None,
        finish_reason: str | None = None,
    ) -> dict[str, dict[str, str]]:
        return _parse_llm_response(response_text, expected_count, target_languages, finish_reason)

    def resolve_provider_model(self, job: TranslationJob) -> str | None:
        return _resolve_provider_model(self.db, job)

    @staticmethod
    def compute_config_hash(job: TranslationJob) -> str:
        return _compute_config_hash(job)

    def compute_dict_snapshot_hash(self, job_id: str) -> str:
        return _compute_dict_snapshot_hash(self.db, job_id)
    # endregion delegation_helpers

# #endregion PreviewExecutor
