import json
import asyncio
import time
from typing import Dict, Any, List, Tuple, Sequence, Union
import structlog

from ...shared.clients.openai_client import openai_client
from ...shared.utils.text_processing import detect_case_style, apply_case_style
from ...shared.utils.metrics import BatchMetrics, OperationMetrics
from ...shared.exceptions.base import TranslationException
from .config import TranslationServiceConfig
from .dependencies import AllergenService
from .models.requests import TranslationRequest, BatchTranslationRequest
from .models.responses import (
    TranslationResponse,
    AllergensOnlyResponse,
    MenuOnlyResponse,
    BatchTranslationResponse,
    BatchAllergensOnlyResponse,
    BatchMenuOnlyResponse,
    BatchItemResponse,
    BatchAllergensOnlyItemResponse,
    BatchMenuOnlyItemResponse
)

logger = structlog.get_logger("translation.service")

# VERSION constant
VERSION = "2.0.0"


class TranslationService:
    def __init__(self, config: TranslationServiceConfig):
        self.config = config
        self.semaphore = asyncio.Semaphore(config.max_concurrent_requests)
        self.allergen_service = AllergenService([a.dict() for a in config.allergens])

        self.base_language_codes = tuple(config.supported_languages)
        self.english_code = "en"
        self.language_configs = {
            False: self._build_language_config(self._exclude_english(self.base_language_codes)),
            True: self._build_language_config(self._attach_english(self.base_language_codes))
        }

    @staticmethod
    def _analytics_context(
        app_service: str,
        city: str = "City not set",
        zavedenia_id: int = -1,
        **data_input: Any
    ) -> Dict[str, Any]:
        return {
            "city": city,
            "zavedenia_id": zavedenia_id,
            "request_body": data_input.pop("request_body", None),
            "data_input": {
                "app_service": app_service,
                **{key: value for key, value in data_input.items() if value is not None}
            }
        }

    @staticmethod
    def _merge_metrics(metrics_list: Sequence[Dict[str, Any]]) -> Dict[str, Any]:
        if not metrics_list:
            return {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0, "model": "missing content"}
        models = [m.get("model") for m in metrics_list if m.get("model")]
        model = models[0] if len(set(models)) == 1 else "multiple_models"
        return {
            "input_tokens": sum(int(m.get("input_tokens", 0) or 0) for m in metrics_list),
            "output_tokens": sum(int(m.get("output_tokens", 0) or 0) for m in metrics_list),
            "total_tokens": sum(int(m.get("total_tokens", 0) or 0) for m in metrics_list),
            "model": model,
        }

    def _exclude_english(self, language_codes: Sequence[str]) -> Tuple[str, ...]:
        return tuple(code for code in language_codes if code != self.english_code)

    def _attach_english(self, language_codes: Sequence[str]) -> Tuple[str, ...]:
        if self.english_code in language_codes:
            return tuple(language_codes)
        return tuple(language_codes) + (self.english_code,)

    def _build_language_config(self, language_codes: Sequence[str]) -> Dict[str, Any]:
        codes = tuple(language_codes)
        mid_point = len(codes) // 2
        first_half_codes = codes[:mid_point]
        second_half_codes = codes[mid_point:]

        return {
            "codes": codes,
            "first_half_codes": first_half_codes,
            "second_half_codes": second_half_codes,
        }

    def _get_language_config(self, include_english: bool) -> Dict[str, Any]:
        return self.language_configs[include_english]

    @staticmethod
    def _inject_language_translation(translations: str, language_code: str, value: str) -> str:
        start_tag = f"<!-- start {language_code} -->"
        end_tag = f"<!-- end {language_code} -->"

        if start_tag not in translations or end_tag not in translations:
            placeholder = f"{start_tag}{end_tag}"
            translations = f"{translations} {placeholder}".strip()

        start_idx = translations.find(start_tag) + len(start_tag)
        end_idx = translations.find(end_tag, start_idx)
        if end_idx == -1:
            return translations

        return f"{translations[:start_idx]}{value}{translations[end_idx:]}"

    @staticmethod
    def _normalize_language_map(
        translations: Any,
        languages: Sequence[str]
    ) -> Dict[str, str]:
        if isinstance(translations, dict):
            base_map = translations
        elif isinstance(translations, list):
            base_map = {}
            for entry in translations:
                if not isinstance(entry, dict):
                    continue
                code = entry.get("language") or entry.get("lang") or entry.get("code")
                if not code:
                    continue
                base_map[str(code)] = entry.get("text", "")
        else:
            base_map = {}

        normalized: Dict[str, str] = {}
        for code in languages:
            value = base_map.get(code, "")
            if value is None:
                value = ""
            elif not isinstance(value, str):
                value = str(value)
            normalized[code] = value
        return normalized

    @staticmethod
    def _combine_translation_maps(
        first_half: Dict[str, str],
        second_half: Dict[str, str]
    ) -> Dict[str, str]:
        combined = dict(first_half)
        combined.update(second_half)
        return combined

    @staticmethod
    def _build_language_blocks(
        translations: Dict[str, str],
        languages: Sequence[str]
    ) -> str:
        segments = [
            f"<!-- start {code} -->{translations.get(code, '')}<!-- end {code} -->"
            for code in languages
        ]
        return " ".join(segments).strip()

    @staticmethod
    def _apply_case_style_to_map(
        translations: Dict[str, str],
        style: str
    ) -> Dict[str, str]:
        if style not in ("upper", "lower"):
            return translations
        return {code: apply_case_style(value, style) for code, value in translations.items()}

    async def _translate_half(
        self,
        menu_item: str,
        description: str,
        language_codes: Sequence[str],
        half_name: str,
        city: str = "City not set",
        zavedenia_id: int = -1
    ) -> Tuple[Dict[str, str], Dict[str, str], Dict[str, Any]]:
        """Translate to half of the languages"""
        languages_json = json.dumps(list(language_codes))
        prompt = f"""
You are a professional culinary translator specializing in restaurant menus.

Given:
- Menu item: "{menu_item}"
- Description: "{description}"

Task:
Translate the provided menu item and/or description into ALL of these language codes:
{languages_json}

CRITICAL RULES:
- Many menu items contain transliterations of international culinary terms (e.g., French techniques, Italian dishes, cooking styles). Identify these and use the correct culinary term in the target language, NOT a phonetic guess.
- Example: Bulgarian "Биск" is the culinary term "bisque" (a French cream soup) — in French it must be "bisque", NOT "biscuit".
- Preserve proper names, brand names, and place names (e.g., "Черноморски" → "de la mer Noire" in French, "Black Sea" in English).
- Use professional culinary vocabulary appropriate for fine-dining menus.

Return ONLY a JSON object in this exact structure:
{{
  "menuItemTranslations": {{"ru": "string"}},
  "descriptionTranslations": {{"ru": "string"}}
}}
Keys must be the language codes listed above. Include all codes.
"""
        result, metrics = await openai_client.json_completion(
            api_key=self.config.openai_api_key,
            model=self.config.translation_model,
            prompt=prompt,
            operation_id=f"single_item_{half_name}",
            service="translation",
            max_tokens=self.config.max_tokens,
            temperature=self.config.temperature,
            timeout=self.config.timeout,
            analytics_context=self._analytics_context(
                "translation",
                city=city,
                zavedenia_id=zavedenia_id,
                stage="single_item_half",
                half_name=half_name,
                language_codes=list(language_codes),
            ),
            skip_analytics=True,
        )

        menu_map = self._normalize_language_map(result.get("menuItemTranslations", {}), language_codes)
        desc_map = self._normalize_language_map(result.get("descriptionTranslations", {}), language_codes)
        return menu_map, desc_map, metrics

    async def _translate_batch_item_half(
        self,
        language_from: str,
        item: Dict[str, Any],
        language_codes: Sequence[str],
        half_name: str,
        city: str = "City not set",
        zavedenia_id: int = -1
    ) -> Tuple[Dict[str, str], Dict[str, str], Dict[str, Any]]:
        """Translate a batch item to half of the languages"""
        languages_json = json.dumps(list(language_codes))
        prompt = f"""
You are a professional culinary translator specializing in restaurant menus. The source language is "{language_from}".

Item:
{json.dumps(item, ensure_ascii=False, separators=(",", ":"))}

Task:
Translate 'name' and 'description' into ALL of these language codes:
{languages_json}

CRITICAL RULES:
- Many menu items contain transliterations of international culinary terms (e.g., French techniques, Italian dishes, cooking styles). Identify these and use the correct culinary term in the target language, NOT a phonetic guess.
- Example: Bulgarian "Биск" is the culinary term "bisque" (a French cream soup) — in French it must be "bisque", NOT "biscuit".
- Preserve proper names, brand names, and place names (e.g., "Черноморски" → "de la mer Noire" in French, "Black Sea" in English).
- Use professional culinary vocabulary appropriate for fine-dining menus.

Return ONLY valid JSON in this exact structure:
{{
  "menuItemTranslations": {{"ru": "string"}},
  "descriptionTranslations": {{"ru": "string"}}
}}
Keys must be the language codes listed above. Include all codes.
"""
        result, metrics = await openai_client.json_completion(
            api_key=self.config.openai_api_key,
            model=self.config.translation_model,
            prompt=prompt,
            operation_id=f"batch_item_{item['id']}_{half_name}",
            service="translation",
            max_tokens=self.config.max_tokens,
            temperature=self.config.temperature,
            timeout=self.config.timeout,
            analytics_context=self._analytics_context(
                "translation",
                city=city,
                zavedenia_id=zavedenia_id,
                stage="batch_item_half",
                item_id=item["id"],
                language_from=language_from,
                half_name=half_name,
                language_codes=list(language_codes),
            ),
            skip_analytics=True,
        )

        menu_map = self._normalize_language_map(result.get("menuItemTranslations", {}), language_codes)
        desc_map = self._normalize_language_map(result.get("descriptionTranslations", {}), language_codes)
        return menu_map, desc_map, metrics

    async def _get_allergens_only(
        self,
        menu_item: str,
        description: str,
        city: str = "City not set",
        zavedenia_id: int = -1
    ) -> Tuple[List[int], Dict[str, Any]]:
        """Get allergens for the given menu item and description"""
        prompt = f"""
You are a food allergen detection assistant.

Given:
- Menu item: "{menu_item}"
- Description: "{description}"
- Allergen list: {self.allergen_service.get_allergens_json()}

Task:
Identify allergens present in the dish and return only their numbers as an array.

Return ONLY a JSON object with:
{{
  "allergens": [int]
}}
"""
        result, metrics = await openai_client.json_completion(
            api_key=self.config.openai_api_key,
            model=self.config.allergen_model,
            prompt=prompt,
            operation_id="allergens_only",
            service="allergens",
            max_tokens=self.config.max_tokens,
            temperature=self.config.temperature,
            timeout=self.config.timeout,
            analytics_context=self._analytics_context(
                "allergens",
                city=city,
                zavedenia_id=zavedenia_id,
                stage="single_item",
            ),
            skip_analytics=True,
        )

        return self.allergen_service.validate_allergen_numbers(result.get("allergens", [])), metrics

    async def _get_batch_allergens(
        self,
        language_from: str,
        item: Dict[str, Any],
        city: str = "City not set",
        zavedenia_id: int = -1
    ) -> Tuple[List[int], Dict[str, Any]]:
        """Get allergens for a batch item"""
        prompt = f"""
You are a food allergen detection assistant. The source language is "{language_from}".

Item:
{json.dumps(item, ensure_ascii=False, separators=(",", ":"))}

Allergen list (with numbers):
{self.allergen_service.get_allergens_json()}

Task:
Detect allergens present using both 'name' and 'description' and return only their numbers.

Return ONLY a JSON object with:
{{
  "allergens": [int]
}}
"""
        result, metrics = await openai_client.json_completion(
            api_key=self.config.openai_api_key,
            model=self.config.allergen_model,
            prompt=prompt,
            operation_id=f"batch_allergens_{item['id']}",
            service="allergens",
            max_tokens=self.config.max_tokens,
            temperature=self.config.temperature,
            timeout=self.config.timeout,
            analytics_context=self._analytics_context(
                "allergens",
                city=city,
                zavedenia_id=zavedenia_id,
                stage="batch_item",
                item_id=item["id"],
                language_from=language_from,
            ),
            skip_analytics=True,
        )

        return self.allergen_service.validate_allergen_numbers(result.get("allergens", [])), metrics

    async def translate_single_item(
        self,
        request: TranslationRequest
    ) -> Union[TranslationResponse, AllergensOnlyResponse, MenuOnlyResponse]:
        """Translate a single menu item"""
        menu_item = request.menuItem or ""
        description = request.description or ""
        menu_item_style = detect_case_style(menu_item)
        description_style = detect_case_style(description)
        request_mode = request.type or ("allergensOnly" if request.allergensOnly else None)
        allergens_only = request_mode == "allergensOnly"
        english_included = request.englishIncluded is True

        metrics = OperationMetrics("translate_single_item")
        started_at = time.time()

        logger.info(
            "single_translation_started",
            menu_item_length=len(menu_item),
            description_length=len(description),
            allergens_only=allergens_only,
            english_included=english_included
        )

        try:
            if allergens_only:
                allergens, allergen_metrics = await self._get_allergens_only(
                    menu_item,
                    description,
                    request.city,
                    request.zavedenia_id
                )
                metrics.log_completion()
                aggregated = self._merge_metrics([allergen_metrics])
                tokens_input = {
                    "prompt_tokens": aggregated.get("input_tokens", 0),
                    "cached_tokens": 0,
                    "total_tokens": aggregated.get("input_tokens", 0),
                }
                tokens_output = {
                    "completion_tokens": aggregated.get("output_tokens", 0),
                    "reasoning_tokens": 0,
                    "total_tokens": aggregated.get("output_tokens", 0),
                }
                response = AllergensOnlyResponse(allergens=allergens, version=VERSION)
                await openai_client.log_manual_analytics(
                    city=request.city,
                    zavedenia_id=request.zavedenia_id,
                    service="translation",
                    model=aggregated.get("model", self.config.translation_model),
                    tokens_input=tokens_input,
                    tokens_output=tokens_output,
                    data_input=self._analytics_context(
                        "translation",
                        city=request.city,
                        zavedenia_id=request.zavedenia_id,
                        request_body=request.model_dump(exclude_none=True),
                        request_mode=request_mode,
                        english_included=english_included,
                    ),
                    data_output=response.model_dump(),
                    execution_time=int((time.time() - started_at) * 1000),
                )
                logger.info("single_translation_completed", allergens_found=len(allergens))
                return response

            # Get main translations and allergens in parallel
            language_config = self._get_language_config(False)

            tasks = [
                self._translate_half(
                    menu_item,
                    description,
                    language_config["codes"],
                    "main",
                    request.city,
                    request.zavedenia_id
                ),
                self._get_allergens_only(menu_item, description, request.city, request.zavedenia_id)
            ]

            if english_included:
                english_prompt = f"""
You are a professional culinary translator specializing in restaurant menus. Translate to English.

Menu item: "{menu_item}"
Description: "{description}"

CRITICAL RULES:
- Recognize transliterations of international culinary terms and use the correct English culinary term (e.g., Bulgarian "Биск" = "bisque", a French cream soup — keep it as "bisque" in English too).
- Use professional culinary vocabulary appropriate for fine-dining menus.

Return ONLY a JSON object with:
{{
  "menuItemTranslation": "string",
  "descriptionTranslation": "string"
}}
"""
                tasks.append(openai_client.json_completion(
                    api_key=self.config.openai_api_key,
                    model=self.config.translation_model,
                    prompt=english_prompt,
                    operation_id="single_item_english",
                    service="translation",
                    max_tokens=self.config.max_tokens,
                    temperature=self.config.temperature,
                    timeout=self.config.timeout,
                    analytics_context=self._analytics_context(
                        "translation",
                        request.city,
                        request.zavedenia_id,
                        stage="single_item_english",
                        target_language="en",
                    ),
                    skip_analytics=True,
                ))

            results = await asyncio.gather(*tasks)
            menu_map, desc_map, translation_metrics = results[0]
            allergens, allergen_metrics = results[1]

            menu_map = self._apply_case_style_to_map(menu_map, menu_item_style)
            desc_map = self._apply_case_style_to_map(desc_map, description_style)
            menu_translations = self._build_language_blocks(menu_map, language_config["codes"])
            desc_translations = self._build_language_blocks(desc_map, language_config["codes"])

            name_en = None
            description_en = None
            if english_included:
                english_result, english_metrics = results[2]
                name_en = english_result.get("menuItemTranslation", menu_item)
                description_en = english_result.get("descriptionTranslation", description)
                name_en = apply_case_style(name_en, menu_item_style)
                description_en = apply_case_style(description_en, description_style)
            else:
                english_metrics = None

            if request_mode == "menuOnly":
                response = MenuOnlyResponse(
                    menuItemTranslations=menu_translations,
                    descriptionTranslations=desc_translations,
                    name_en=name_en,
                    description_en=description_en,
                    version=VERSION
                )
            else:
                response = TranslationResponse(
                    menuItemTranslations=menu_translations,
                    descriptionTranslations=desc_translations,
                    allergens=allergens,
                    name_en=name_en,
                    description_en=description_en,
                    version=VERSION
                )

            metrics.log_completion()

            metric_list: List[Dict[str, Any]] = [translation_metrics] if translation_metrics else []
            if allergen_metrics:
                metric_list.append(allergen_metrics)
            if english_metrics:
                metric_list.append(english_metrics)
            aggregated = self._merge_metrics(metric_list)

            tokens_input = {
                "prompt_tokens": aggregated.get("input_tokens", 0),
                "cached_tokens": 0,
                "total_tokens": aggregated.get("input_tokens", 0),
            }
            tokens_output = {
                "completion_tokens": aggregated.get("output_tokens", 0),
                "reasoning_tokens": 0,
                "total_tokens": aggregated.get("output_tokens", 0),
            }
            await openai_client.log_manual_analytics(
                city=request.city,
                zavedenia_id=request.zavedenia_id,
                service="translation",
                model=aggregated.get("model", self.config.translation_model),
                tokens_input=tokens_input,
                tokens_output=tokens_output,
                data_input=self._analytics_context(
                    "translation",
                    city=request.city,
                    zavedenia_id=request.zavedenia_id,
                    request_body=request.model_dump(exclude_none=True),
                    request_mode=request_mode,
                    english_included=english_included,
                ),
                data_output=response.model_dump(),
                execution_time=int((time.time() - started_at) * 1000),
            )

            logger.info("single_translation_completed", type=type(response).__name__)
            return response

        except Exception as e:
            metrics.set_error(str(e))
            metrics.log_completion()
            logger.error("single_translation_failed", error=str(e))
            raise TranslationException(f"Single item translation failed: {e}")

    async def translate_batch_item(
        self,
        language_from: str,
        item: Dict[str, Any],
        allergens_only: bool = False,
        english_included: bool = False,
        city: str = "City not set",
        zavedenia_id: int = -1
    ) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
        """Translate a batch item"""
        async with self.semaphore:
            if allergens_only:
                allergens, allergen_metrics = await self._get_batch_allergens(language_from, item, city, zavedenia_id)
                return {"id": item["id"], "allergens": allergens}, [allergen_metrics]

            logger.debug("batch_item_started", item_id=item["id"])

            try:
                menu_item_style = detect_case_style(item.get("name", ""))
                description_style = detect_case_style(item.get("description", ""))
                language_config = self._get_language_config(False)

                # Create tasks for translations and allergens
                tasks = [
                    self._get_batch_allergens(language_from, item, city, zavedenia_id),
                    self._translate_batch_item_half(language_from, item, language_config["first_half_codes"], "first", city, zavedenia_id),
                    self._translate_batch_item_half(language_from, item, language_config["second_half_codes"], "second", city, zavedenia_id)
                ]

                if english_included:
                    english_prompt = f"""
You are a professional culinary translator specializing in restaurant menus. The source language is "{language_from}". Translate to English.

Menu item: "{item.get('name', '')}"
Description: "{item.get('description', '')}"

CRITICAL RULES:
- Recognize transliterations of international culinary terms and use the correct English culinary term (e.g., Bulgarian "Биск" = "bisque", a French cream soup — keep it as "bisque" in English too).
- Use professional culinary vocabulary appropriate for fine-dining menus.

Return ONLY a JSON object with:
{{
  "menuItemTranslation": "string",
  "descriptionTranslation": "string"
}}
"""
                    tasks.append(openai_client.json_completion(
                        api_key=self.config.openai_api_key,
                        model=self.config.translation_model,
                        prompt=english_prompt,
                        operation_id=f"english_translation_batch_{item['id']}",
                        service="translation",
                        max_tokens=self.config.max_tokens,
                        temperature=self.config.temperature,
                        timeout=self.config.timeout,
                        analytics_context=self._analytics_context(
                            "translation",
                            city,
                            zavedenia_id,
                            stage="batch_item_english",
                            item_id=item["id"],
                            language_from=language_from,
                            target_language="en",
                        ),
                        skip_analytics=True,
                    ))

                results = await asyncio.gather(*tasks)
                allergens, allergen_metrics = results[0]
                (first_menu_map, first_desc_map, first_metrics) = results[1]
                (second_menu_map, second_desc_map, second_metrics) = results[2]

                combined_menu = self._combine_translation_maps(first_menu_map, second_menu_map)
                combined_desc = self._combine_translation_maps(first_desc_map, second_desc_map)
                combined_menu = self._apply_case_style_to_map(combined_menu, menu_item_style)
                combined_desc = self._apply_case_style_to_map(combined_desc, description_style)

                result = {
                    "id": item["id"],
                    "menuItemTranslations": self._build_language_blocks(
                        combined_menu,
                        language_config["codes"]
                    ),
                    "descriptionTranslations": self._build_language_blocks(
                        combined_desc,
                        language_config["codes"]
                    ),
                    "allergens": allergens
                }

                english_metrics = None
                if english_included and len(results) > 3:
                    english_result, english_metrics = results[3]
                    result["name_en"] = apply_case_style(
                        english_result.get("menuItemTranslation", item.get("name", "")),
                        menu_item_style
                    )
                    result["description_en"] = apply_case_style(
                        english_result.get("descriptionTranslation", item.get("description", "")),
                        description_style
                    )

                logger.debug("batch_item_completed", item_id=item["id"])
                metrics_list = [allergen_metrics, first_metrics, second_metrics]
                if english_metrics:
                    metrics_list.append(english_metrics)
                return result, metrics_list

            except Exception as e:
                logger.error("batch_item_failed", item_id=item["id"], error=str(e))
                raise TranslationException(f"Batch item {item['id']} translation failed: {e}")

    async def translate_batch(
        self,
        request: BatchTranslationRequest
    ) -> Union[BatchTranslationResponse, BatchAllergensOnlyResponse, BatchMenuOnlyResponse]:
        """Translate multiple menu items"""
        request_mode = request.type or ("allergensOnly" if request.allergensOnly else None)
        allergens_only = request_mode == "allergensOnly"
        english_included = request.englishIncluded is True

        batch_metrics = BatchMetrics(f"batch_translation_{request.language_from}")
        items_count = len(request.menu_items)
        started_at = time.time()

        logger.info(
            "batch_translation_started",
            items_count=items_count,
            language_from=request.language_from,
            allergens_only=allergens_only,
            english_included=english_included
        )

        if not request.menu_items:
            if allergens_only:
                return BatchAllergensOnlyResponse(items=[], version=VERSION)
            elif request_mode == "menuOnly":
                return BatchMenuOnlyResponse(items=[], version=VERSION)
            return BatchTranslationResponse(items=[], version=VERSION)

        try:
            items_for_translation = [
                {"id": item.id, "name": item.name or "", "description": item.description or ""}
                for item in request.menu_items
            ]

            tasks = [
                self.translate_batch_item(
                    request.language_from,
                    item,
                    allergens_only,
                    english_included,
                    request.city,
                    request.zavedenia_id
                )
                for item in items_for_translation
            ]

            results = await asyncio.gather(*tasks, return_exceptions=True)

            processed_items = []
            aggregated_openai_metrics: List[Dict[str, Any]] = []
            for i, result in enumerate(results):
                if isinstance(result, Exception):
                    batch_metrics.add_item_result({"success": False, "error": str(result)})
                    logger.error("batch_item_exception", item_id=items_for_translation[i].get("id", "unknown"), error=str(result))
                else:
                    batch_metrics.add_item_result({"success": True})
                    item_result, item_metrics = result
                    processed_items.append(item_result)
                    aggregated_openai_metrics.extend(item_metrics)

            batch_metrics.log_summary()
            logger.info("batch_translation_completed", total_items=items_count, successful_items=len(processed_items))

            # Return appropriate response type
            if allergens_only:
                items = [BatchAllergensOnlyItemResponse(**item) for item in processed_items]
                response = BatchAllergensOnlyResponse(items=items, version=VERSION)
            elif request_mode == "menuOnly":
                items = [BatchMenuOnlyItemResponse(**item) for item in processed_items]
                response = BatchMenuOnlyResponse(items=items, version=VERSION)
            else:
                items = [BatchItemResponse(**item) for item in processed_items]
                response = BatchTranslationResponse(items=items, version=VERSION)

            aggregated = self._merge_metrics(aggregated_openai_metrics)
            tokens_input = {
                "prompt_tokens": aggregated.get("input_tokens", 0),
                "cached_tokens": 0,
                "total_tokens": aggregated.get("input_tokens", 0),
            }
            tokens_output = {
                "completion_tokens": aggregated.get("output_tokens", 0),
                "reasoning_tokens": 0,
                "total_tokens": aggregated.get("output_tokens", 0),
            }
            await openai_client.log_manual_analytics(
                city=request.city,
                zavedenia_id=request.zavedenia_id,
                service="translation",
                model=aggregated.get("model", self.config.translation_model),
                tokens_input=tokens_input,
                tokens_output=tokens_output,
                data_input=self._analytics_context(
                    "translation",
                    city=request.city,
                    zavedenia_id=request.zavedenia_id,
                    request_body=request.model_dump(exclude_none=True),
                    request_mode=request_mode,
                    language_from=request.language_from,
                    english_included=english_included,
                    item_count=items_count,
                    successful_items=len(processed_items),
                ),
                data_output=response.model_dump(),
                execution_time=int((time.time() - started_at) * 1000),
            )
            return response

        except Exception as e:
            logger.error("batch_translation_failed", items_count=items_count, error=str(e))
            raise TranslationException(f"Batch translation failed: {e}")
