import logging
import time
import uuid
from typing import Any

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

from app.core.config import Settings, get_settings
from app.models.conversation import Conversation
from app.models.conversation_message import ConversationMessage
from app.providers.llm.factory import create_llm_provider
from app.providers.stt.factory import create_stt_provider
from app.providers.tts.factory import create_tts_provider
from app.repositories.character_repository import CharacterRepository
from app.repositories.conversation_repository import ConversationRepository
from app.repositories.product_repository import ProductRepository
from app.schemas.conversation import (
    AudioConversationResponse,
    ConversationCreate,
    ConversationOut,
    MessageOut,
)
from app.schemas.metrics import MetricEvent
from app.services.audio_service import AudioService
from app.services.metrics_service import MetricsService

logger = logging.getLogger(__name__)


class ConversationService:
    def __init__(self, db: Session, settings: Settings | None = None) -> None:
        self.db = db
        self.settings = settings or get_settings()
        self.conversations = ConversationRepository(db)
        self.products = ProductRepository(db)
        self.characters = CharacterRepository(db)
        self.metrics = MetricsService(db)
        self.audio = AudioService(self.settings)
        self.llm = create_llm_provider(self.settings)
        self.stt = create_stt_provider(self.settings)
        self.tts = create_tts_provider(self.settings)

    def create(self, payload: ConversationCreate) -> ConversationOut:
        product = self.products.get_by_id(payload.product_id)
        if not product:
            raise HTTPException(status_code=404, detail="Product not found")

        conversation = Conversation(
            id=f"conv_{uuid.uuid4().hex[:10]}",
            product_id=product.id,
            character_id=product.character_id,
            status="active",
            device_id=payload.device_id,
        )
        saved = self.conversations.create(conversation)
        self.metrics.track(
            MetricEvent(
                event="conversation_started",
                product_id=product.id,
                character_id=product.character_id,
                conversation_id=saved.id,
                device_id=payload.device_id,
            )
        )
        return ConversationOut(
            conversation_id=saved.id,
            product_id=saved.product_id,
            character_id=saved.character_id,
            status=saved.status,
            messages=[],
        )

    def get(self, conversation_id: str) -> ConversationOut:
        conversation = self.conversations.get_by_id(conversation_id)
        if not conversation:
            raise HTTPException(status_code=404, detail="Conversation not found")
        return ConversationOut(
            conversation_id=conversation.id,
            product_id=conversation.product_id,
            character_id=conversation.character_id,
            status=conversation.status,
            messages=[MessageOut.model_validate(m) for m in conversation.messages],
        )

    async def process_audio(
        self,
        conversation_id: str,
        audio_bytes: bytes,
    ) -> AudioConversationResponse:
        conversation = self.conversations.get_by_id(conversation_id)
        if not conversation:
            raise HTTPException(status_code=404, detail="Conversation not found")

        character = self.characters.get_by_id(conversation.character_id)
        if not character:
            raise HTTPException(status_code=404, detail="Character not found")

        product = self.products.get_by_id(conversation.product_id)
        latency: dict[str, Any] = {}

        # STT
        t0 = time.perf_counter()
        user_text = await self.stt.transcribe(
            audio_bytes, language=self.settings.stt_language
        )
        latency["stt_ms"] = int((time.perf_counter() - t0) * 1000)
        if not user_text:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail="Could not transcribe audio",
            )

        self.conversations.add_message(
            ConversationMessage(
                conversation_id=conversation.id,
                role="user",
                text=user_text,
            )
        )

        history = [
            {"role": m.role, "content": m.text}
            for m in self.conversations.get_by_id(conversation.id).messages
        ]

        system_prompt = character.system_prompt
        if product:
            system_prompt += (
                f"\n\nProducto asociado: {product.name} ({product.brand}). "
                f"{product.description}"
            )

        # LLM
        t1 = time.perf_counter()
        assistant_text = await self.llm.generate(history, system_prompt)
        latency["llm_ms"] = int((time.perf_counter() - t1) * 1000)

        # TTS
        t2 = time.perf_counter()
        audio_content = await self.tts.synthesize(assistant_text, character.voice_id)
        latency["tts_ms"] = int((time.perf_counter() - t2) * 1000)
        latency["total_ms"] = sum(latency.values())

        extension = "wav" if self.settings.tts_provider == "mock" else "mp3"
        audio_url = await self.audio.save_audio(audio_content, extension=extension)

        self.conversations.add_message(
            ConversationMessage(
                conversation_id=conversation.id,
                role="assistant",
                text=assistant_text,
                audio_url=audio_url,
                latency_ms=latency,
            )
        )

        self.metrics.track(
            MetricEvent(
                event="conversation_message",
                product_id=conversation.product_id,
                character_id=conversation.character_id,
                conversation_id=conversation.id,
                metadata=latency,
            )
        )

        logger.info(
            "conversation=%s stt=%sms llm=%sms tts=%sms",
            conversation.id,
            latency.get("stt_ms"),
            latency.get("llm_ms"),
            latency.get("tts_ms"),
        )

        return AudioConversationResponse(
            conversation_id=conversation.id,
            user_text=user_text,
            assistant_text=assistant_text,
            audio_url=audio_url,
            character_state="talking",
            latency=latency,
        )
