from typing import Dict, List, Optional
from app.models.chat import ChatResponse, EventData
from app.services.vector_service import VectorService
from app.services.bedrock_service import BedrockService
import json
from app import crud

class AssistantService:
    _instance = None
    _initialized = False

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(AssistantService, cls).__new__(cls)
        return cls._instance

    def __init__(self):
        if not self._initialized:
            self.vector_service = VectorService()
            self.bedrock_service = BedrockService()
            self._initialized = True

    async def check_info_needed(self, event, services, message, history=None):
        """
        Llama a Bedrock/Claude para saber si falta información relevante.
        Devuelve (needs_more_info: bool, info_message: str)
        """
        # Si la conversación es muy larga, forzar que está listo
        MAX_TURNS = 5
        if history and isinstance(history, list) and len(history) >= MAX_TURNS:
            return False, "Tengo suficiente información para recomendar los servicios para tu evento."

        # 1. Construir el prompt
        prompt = self._build_check_info_prompt(event, services, message, history)

        # 2. Llamar a Bedrock/Claude
        response = await self.bedrock_service.ask_if_needs_more_info(prompt)

        # 3. Parsear la respuesta (espera un diccionario con needs_more_info y mensaje)
        if isinstance(response, dict):
            return response.get("needs_more_info", False), response.get("message", "")
        else:
            # Si la respuesta no es un diccionario, intenta extraer info por heurística
            if isinstance(response, str):
                if "más información" in response.lower() or "necesitas" in response.lower():
                    return True, response
            return False, str(response)

    def _build_check_info_prompt(self, event, services, message, history):
        # Convertir el objeto EventData a un diccionario
        event_dict = {
            "name": event.name,
            "tipus": event.tipus,
            "num_invitats": event.num_invitats,
            "pressupost": event.pressupost,
            "data_inici": event.data_inici.isoformat(),
            "data_fi": event.data_fi.isoformat(),
            "espai_propi_id": str(event.espai_propi_id) if event.espai_propi_id else None,
            "espai_propi": event.espai_propi
        }

        prompt = (
            "Eres un asistente experto en organización de eventos. "
            "El usuario te ha dado la siguiente información para organizar un evento:\n\n"
            f"Evento: {json.dumps(event_dict, ensure_ascii=False, indent=2)}\n"
            f"Servicios requeridos: {services}\n"
            f"Mensaje del usuario: {message}\n"
        )
        if history:
            prompt += f"\nHistorial de la conversación:\n{history}\n"
        prompt += (
            "\n¿Tienes suficiente información para recomendar servicios para este evento? "
            "Si no, ¿qué más necesitas saber? "
            "Responde en formato JSON así: "
            '{"needs_more_info": true/false, "message": "explicación o pregunta para el usuario"}'
            "\nIMPORTANTE: Responde ÚNICAMENTE en formato JSON, sin ningún texto adicional, y usa las claves exactamente como se indica."
            "\n\nEjemplo de entrada:\n"
            '{\n'
            '  "event": {\n'
            '    "name": "Mi boda",\n'
            '    "tipus": "boda",\n'
            '    "pressupost": 10000,\n'
            '    "data_inici": "2025-07-10T12:00:00",\n'
            '    "data_fi": "2025-07-10T18:00:00",\n'
            '    "num_invitats": 100,\n'
            '    "espai_propi": {\n'
            '      "nombre": "Espacio Personalizado 1",\n'
            '      "tipus": "terraza",\n'
            '      "address": {"direccion": "Carrer de Sant 1, 81", "ciudad": "Figueres", "codigo_postal": "08487", "provincia": "Girona"},\n'
            '      "descripcion": "Espacio propio adaptado con iluminación básica y climatización, ideal para eventos íntimos.",\n'
            '      "capacitat_aproximada": 148,\n'
            '      "caracteristicas": ["zona exterior", "acceso minusválidos"]\n'
            '    }\n'
            '  },\n'
            '  "services": ["catering", "decoracion", "musica", "fotografia", "mobiliario"],\n'
            '  "message": "Planeamos una boda para 100 invitados y queremos que todo sea perfecto. Ofreceremos un menú de tapas catalanas para 100 personas, con platos tradicionales preparados con ingredientes de proximidad, ajustado a un presupuesto de 10000€. Colocaremos velas y candelabros antiguos para crear un ambiente romántico durante la cena de los 100. Contrataremos un DJ especializado en música electrónica capaz de amenizar para 100 personas. Se contratará un equipo de fotografía y vídeo para documentar el evento y crear un highlight de 3 minutos para los 100. Mobiliario reciclado y sostenible para 100, con elementos DIY que reducen costes y aportan un aire ecológico."\n'
            '}\n'
            "\nEjemplo de salida si falta información:\n"
            '{"needs_more_info": true, "message": "¿Podrías especificar si prefieres DJ con canciones propias o canciones de otros artistas para la boda?"}'
            "\nEjemplo de salida si tienes suficiente información:\n"
            '{"needs_more_info": false, "message": "Tengo toda la información necesaria para recomendar los servicios."}'
        )
        prompt += "\nIMPORTANTE: Responde ÚNICAMENTE en formato JSON, sin ningún texto adicional, y usa las claves exactamente como se indica."
        prompt = "\n\nHuman:" + prompt + "\n\nAssistant:"
        return prompt 

    async def filter_services(self, session, event, services, get_filtered: bool):
        """
        Llama a la función de filtrado avanzada de servicios usando la sesión de base de datos.
        Devuelve los servicios filtrados según los requisitos del evento.
        """
        # Usar una sesión síncrona dentro de un método async (FastAPI lo permite si no hay await internos)
        filtered = crud.assistant.filter_services(session, event, services, get_filtered)
        return filtered 

    async def vectorize_and_match(self, session, user_message, services, event_data):
        """
        Utiliza el vector_service para obtener servicios relevantes basados en el mensaje del usuario y los servicios filtrados.
        """
        # Obtener servicios relevantes usando el vector service, pasando los servicios filtrados
        relevant_services = self.vector_service.get_relevant_services(
            session=session,
            message=user_message,
            filtered_services=services,
            event_data=event_data
        )
        return relevant_services

    def build_prompt_for_ia(self, user_message, event_data, services, history=None):
        """
        Construye el prompt estructurado para la IA, usando la descripción generada en la vectorización.
        Añade dos ejemplos realistas y extensos.
        """
        servicios_ia = [
            {
                "id": s["id"],
                "category": s["category"],
                "name": s.get("name"),
                "price": s.get("price"),
                "description": s.get("description")
            }
            for s in services
        ]
        ejemplo_entrada_espai = '''
            Mensaje del usuario (ejemplo):
            "Estamos organizando una fiesta de aniversario para 60 personas en nuestro propio jardín, que cuenta con una zona cubierta y espacio para montar carpas. Queremos un catering variado con opciones vegetarianas y sin gluten, música en directo, preferiblemente primero jazz i mas tarde pop, decoración elegante con luces y flores, y un fotógrafo profesional para capturar los mejores momentos. El evento será de tarde-noche y nos gustaría que todo fuera cómodo y memorable para los invitados. El presupuesto total es de 7000€."

            Datos del evento (ejemplo):
            {
            "name": "Aniversario en casa",
            "tipus": "aniversario",
            "num_invitats": 60,
            "pressupost": 7000.0,
            "data_inici": "2025-09-12T18:00:00",
            "data_fi": "2025-09-12T23:59:00",
            "espai_propi": {
                "nombre": "Jardín familiar",
                "tipus": "jardín",
                "address": {
                "direccion": "Calle Magnolia 12",
                "ciudad": "Barcelona",
                "codigo_postal": "08022",
                "provincia": "Barcelona"
                },
                "descripcion": "Jardín privado con zona cubierta, césped y espacio para carpas. Dispone de acceso a baños y electricidad.",
                "capacitat_aproximada": 80,
                "caracteristicas": ["zona exterior", "zona cubierta", "electricidad", "baños"]
            }
            }

            Servicios candidatos (ejemplo):
            [
            {"id": "a1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1", "category": "catering", "name": "Catering Mediterráneo", "price": 2500, "description": "Buffet mediterráneo variado con opciones vegetarianas y sin gluten."},
            {"id": "b2b2b2b2-b2b2-b2b2-b2b2-b2b2b2b2b2b2", "category": "catering", "name": "Catering Gourmet", "price": 4000, "description": "Menú gourmet con platos internacionales y showcooking."},
            {"id": "c3c3c3c3-c3c3-c3c3-c3c3-c3c3c3c3c3c3", "category": "catering", "name": "Catering Finger Food", "price": 1800, "description": "Bocados variados, tapas y opciones veganas para eventos informales."},
            {"id": "d4d4d4d4-d4d4-d4d4-d4d4-d4d4d4d4d4d4", "category": "catering", "name": "Catering BBQ", "price": 2200, "description": "Barbacoa con carnes, verduras y opciones vegetarianas."},
            {"id": "e5e5e5e5-e5e5-e5e5-e5e5-e5e5e5e5e5e5", "category": "musica", "name": "Jazz Quartet", "price": 1200, "description": "Cuarteto de jazz profesional para eventos elegantes."},
            {"id": "f6f6f6f6-f6f6-f6f6-f6f6-f6f6f6f6f6f6", "category": "musica", "name": "DJ Fiesta", "price": 800, "description": "DJ con repertorio pop y música de baile."},
            {"id": "g7g7g7g7-g7g7-g7g7-g7g7-g7g7g7g7g7g7", "category": "musica", "name": "Duo Acústico", "price": 950, "description": "Dúo de guitarra y voz para ambientación suave."},
            {"id": "h8h8h8h8-h8h8-h8h8-h8h8-h8h8h8h8h8h8", "category": "decoracion", "name": "Decoración Floral Premium", "price": 900, "description": "Decoración con flores frescas y guirnaldas de luces LED."},
            {"id": "i9i9i9i9-i9i9-i9i9-i9i9-i9i9i9i9i9i9", "category": "decoracion", "name": "Decoración Temática Vintage", "price": 700, "description": "Decoración con elementos vintage y centros de mesa personalizados."},
            {"id": "j0j0j0j0-j0j0-j0j0-j0j0-j0j0j0j0j0j0", "category": "decoracion", "name": "Iluminación LED Exterior", "price": 400, "description": "Guirnaldas y focos LED para exteriores y jardines."},
            {"id": "k1k1k1k1-k1k1-k1k1-k1k1-k1k1k1k1k1k1", "category": "decoracion", "name": "Decoración Minimalista", "price": 500, "description": "Decoración sencilla y elegante con tonos neutros."},
            {"id": "l2l2l2l2-l2l2-l2l2-l2l2-l2l2l2l2l2l2", "category": "fotografia", "name": "FotoPro Eventos", "price": 600, "description": "Fotógrafo profesional con entrega de álbum digital y fotos impresas."},
            {"id": "m3m3m3m3-m3m3-m3m3-m3m3-m3m3m3m3m3m3", "category": "fotografia", "name": "Instant Memories", "price": 400, "description": "Fotografía instantánea y cabina de fotos para invitados."},
            {"id": "n4n4n4n4-n4n4-n4n4-n4n4-n4n4n4n4n4n4", "category": "fotografia", "name": "Foto Creativa", "price": 550, "description": "Fotografía artística y edición creativa para eventos."}
            ]
            '''
        ejemplo_salida_espai = '''
            Salida (ejemplo):
            {
            "message": "Hemos seleccionado el catering mediterráneo, que ofrece opciones vegetarianas y sin gluten, ideal para todos los invitados. Para la música, el cuarteto de jazz aportará elegancia y el DJ Fiesta animará la parte final de la noche. La decoración floral premium junto con la iluminación LED realzarán la belleza del jardín. El fotógrafo profesional FotoPro Eventos se encargará de capturar los mejores momentos. El resto de opciones no han sido seleccionadas por no aportar un valor diferencial suficiente o por superar el presupuesto total.",
            "selected_services": [
                {"id": "a1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1", "category": "catering"},
                {"id": "e5e5e5e5-e5e5-e5e5-e5e5-e5e5e5e5e5e5", "category": "musica"},
                {"id": "f6f6f6f6-f6f6-f6f6-f6f6-f6f6f6f6f6f6", "category": "musica"},
                {"id": "h8h8h8h8-h8h8-h8h8-h8h8-h8h8h8h8h8h8", "category": "decoracion"},
                {"id": "j0j0j0j0-j0j0-j0j0-j0j0-j0j0j0j0j0j0", "category": "decoracion"},
                {"id": "l2l2l2l2-l2l2-l2l2-l2l2-l2l2l2l2l2l2", "category": "fotografia"}
            ]
            }
            '''
        ejemplo_entrada_noespai = '''
            Mensaje del usuario (ejemplo):
            "Queremos celebrar una graduación universitaria para 80 personas en un lugar especial, preferiblemente una masía o finca con espacios interiores y exteriores. Buscamos un catering informal tipo buffet, música variada para bailar, decoración juvenil y colorida, y un fotógrafo que haga fotos espontáneas y de grupo. El evento será de día y queremos que sea divertido, cómodo y memorable para todos. El presupuesto máximo es de 9000€."

            Datos del evento (ejemplo):
            {
            "name": "Graduación 2025",
            "tipus": "graduacion",
            "num_invitats": 80,
            "pressupost": 9000.0,
            "data_inici": "2025-06-20T13:00:00",
            "data_fi": "2025-06-20T20:00:00"
            }

            Servicios candidatos (ejemplo):
            [
            {"id": "o0o0o0o0-o0o0-o0o0-o0o0-o0o0o0o0o0o0", "category": "espai", "name": "Masía Can Soler", "price": 3500, "description": "Masía con jardín, salón interior y zona de baile."},
            {"id": "p1p1p1p1-p1p1-p1p1-p1p1-p1p1p1p1p1p1", "category": "espai", "name": "Finca El Bosque", "price": 4000, "description": "Finca con amplios exteriores y carpa para eventos."},
            {"id": "q2q2q2q2-q2q2-q2q2-q2q2-q2q2q2q2q2q2", "category": "espai", "name": "Sala Urbana", "price": 2500, "description": "Sala moderna en el centro de la ciudad con terraza privada."},
            {"id": "r3r3r3r3-r3r3-r3r3-r3r3-r3r3r3r3r3r3", "category": "catering", "name": "Buffet Joven", "price": 1800, "description": "Catering tipo buffet con opciones internacionales y snacks."},
            {"id": "s4s4s4s4-s4s4-s4s4-s4s4-s4s4s4s4s4s4", "category": "catering", "name": "Catering Tradicional", "price": 2200, "description": "Menú tradicional con platos catalanes y postres caseros."},
            {"id": "t5t5t5t5-t5t5-t5t5-t5t5-t5t5t5t5t5t5", "category": "catering", "name": "Food Trucks Fiesta", "price": 2000, "description": "Varios food trucks con hamburguesas, pizzas y opciones veganas."},
            {"id": "u6u6u6u6-u6u6-u6u6-u6u6-u6u6u6u6u6u6", "category": "catering", "name": "Catering Healthy", "price": 2100, "description": "Buffet saludable con ensaladas, wraps y zumos naturales."},
            {"id": "v7v7v7v7-v7v7-v7v7-v7v7-v7v7v7v7v7v7", "category": "musica", "name": "DJ Universitario", "price": 900, "description": "DJ con música variada y animación para jóvenes."},
            {"id": "w8w8w8w8-w8w8-w8w8-w8w8-w8w8w8w8w8w8", "category": "musica", "name": "Banda Pop Covers", "price": 1500, "description": "Banda en directo con versiones de éxitos actuales."},
            {"id": "x9x9x9x9-x9x9-x9x9-x9x9-x9x9x9x9x9x9", "category": "musica", "name": "Grupo Rock Live", "price": 1700, "description": "Grupo de rock en vivo para animar la fiesta."},
            {"id": "y0y0y0y0-y0y0-y0y0-y0y0-y0y0y0y0y0y0", "category": "decoracion", "name": "Decoración Color Party", "price": 700, "description": "Decoración con globos, banderines y luces de colores."},
            {"id": "z1z1z1z1-z1z1-z1z1-z1z1-z1z1z1z1z1z1", "category": "decoracion", "name": "Decoración Temática Graduación", "price": 800, "description": "Decoración personalizada con motivos universitarios y photocall."},
            {"id": "a2a2a2a2-a2a2-a2a2-a2a2-a2a2a2a2a2a2", "category": "decoracion", "name": "Decoración Minimalista", "price": 600, "description": "Decoración sencilla y elegante con tonos neutros."},
            {"id": "b3b3b3b3-b3b3-b3b3-b3b3-b3b3b3b3b3b3", "category": "fotografia", "name": "Graduación Click", "price": 650, "description": "Fotógrafo especializado en eventos universitarios y fotos de grupo."},
            {"id": "c4c4c4c4-c4c4-c4c4-c4c4-c4c4c4c4c4c4", "category": "fotografia", "name": "PhotoFun Cabina", "price": 500, "description": "Cabina de fotos instantáneas y accesorios divertidos para invitados."},
            {"id": "d5d5d5d5-d5d5-d5d5-d5d5-d5d5d5d5d5d5", "category": "fotografia", "name": "Foto Creativa", "price": 700, "description": "Fotografía artística y edición creativa para eventos."}
            ]
            '''
        ejemplo_salida_noespai = '''
            Salida (ejemplo):
            {
            "message": "Para vuestra graduación hemos seleccionado la Masía Can Soler, que ofrece tanto espacios interiores como exteriores ideales para una celebración dinámica. El catering tipo buffet y los food trucks aportan variedad y flexibilidad para todos los gustos. El DJ Universitario animará la fiesta y la banda de pop covers aportará música en directo. La decoración Color Party y la temática de graduación crearán un ambiente juvenil y festivo. El fotógrafo especializado en graduaciones capturará los mejores momentos. El resto de opciones no han sido seleccionadas por no aportar un valor diferencial suficiente o por superar el presupuesto total.",
            "selected_services": [
                {"id": "o0o0o0o0-o0o0-o0o0-o0o0-o0o0o0o0o0o0", "category": "espai"},
                {"id": "r3r3r3r3-r3r3-r3r3-r3r3-r3r3r3r3r3r3", "category": "catering"},
                {"id": "t5t5t5t5-t5t5-t5t5-t5t5-t5t5t5t5t5t5", "category": "catering"},
                {"id": "v7v7v7v7-v7v7-v7v7-v7v7-v7v7v7v7v7v7", "category": "musica"},
                {"id": "w8w8w8w8-w8w8-w8w8-w8w8-w8w8w8w8w8w8", "category": "musica"},
                {"id": "y0y0y0y0-y0y0-y0y0-y0y0-y0y0y0y0y0y0", "category": "decoracion"},
                {"id": "z1z1z1z1-z1z1-z1z1-z1z1-z1z1z1z1z1z1", "category": "decoracion"},
                {"id": "b3b3b3b3-b3b3-b3b3-b3b3-b3b3b3b3b3b3", "category": "fotografia"}
            ]
            }
            '''
        
        prompt = f"""
            Eres un asistente experto en organizacion de eventos. Tu tarea es seleccionar los servicios más adecuados según las preferencias del usuario, los datos del evento y los servicios candidatos disponibles. Devuelve un JSON con una breve explicación y una lista de servicios seleccionados con su id y categoría. 
            """
        prompt += f"""
            Selecciona los servicios más adecuados para este evento, respetando estas condiciones muy importantes que estan ordenadas por prioridad:
            - Debes seleccionar al menos 1 servicio por categoría.
            - No debe superarse el presupuesto total del evento.
            - Máximo 1 servicio en la categoría "espai".
            - Máximo 2 servicios en "catering", "música" y "fotografia", **solo si ambos son muy relevantes para la experiencia del usuario**.
            - En "decoración" y "mobiliario", puedes seleccionar más de 2 servicios, pero **todos deben aportar valor claro y justificable**.
            - Si hay un campo "espai_propi", significa que el evento se realiza en un lugar propio, por lo tanto **no selecciones servicios de tipo "espai"**.
            - Orden de prioridad de selección de servicios:
                - 1. "espai"
                - 2. "catering"
                - 3. "música"
                - 4. "fotografia"
                - 5. "decoración"
                - 6. "mobiliario"

            Devuelve **únicamente** un objeto JSON con esta estructura:

            ```json
            {{
                "message": "Texto explicativo para el usuario, justificando la selección y resumiendo los puntos fuertes de cada servicio. Toda esta explicación debe estar dentro del campo message.",
                "selected_services": [
                    {{"id": "ID_DEL_SERVICIO", "category": "CATEGORÍA_DEL_SERVICIO"}},
                    ...
                ]
            }}
            ```
            Ejemplo de entrada (con espai_propi):
            {ejemplo_entrada_espai}
            Ejemplo de salida (con espai_propi):
            {ejemplo_salida_espai}

            Ejemplo de entrada (sin espai_propi):
            {ejemplo_entrada_noespai}
            Ejemplo de salida (sin espai_propi):
            {ejemplo_salida_noespai}
            """
        
        prompt += f"""
            El usuario ha solicitado lo siguiente:

            Mensaje del usuario:
            \"{user_message}\"

            Datos del evento:
            {event_data}

            Servicios candidatos:
            {json.dumps(servicios_ia, ensure_ascii=False, indent=2)}
            """
        
        if history:
            prompt += f"\nHistorial de la conversación:\n{history}\n"

        prompt += f"""
            Ejemplo de respuesta correcta:
            Responde en formato JSON ESTRICTO, sin explicaciones, sin introducción, solo:
            {{
            "message": "Texto explicativo aquí...",
            "selected_services": [
                {{"id": "...", "category": "..."}},
                ...
            ]
            }}

            """
        
        prompt = "\n\nHuman:" + prompt + "\n\nAssistant:"
        
        return prompt

    async def final_recommendation(self, session, event, services, message, history=None):
        """
        Llama a la IA (Bedrock) con el prompt estructurado y parsea la respuesta.
        """
        prompt = self.build_prompt_for_ia(message, event, services, history)
        ia_response = await self.bedrock_service.generate_response(
            prompt=prompt,
        )
        try:
            return ia_response.get("message", ""), ia_response.get("selected_services", [])
        except Exception:
            return ia_response, [] 