from typing import Dict, Any, Type, TypeVar, List
from abc import ABC, abstractmethod
from flask import current_app

from common.extensions import cache_manager, db
from common.models.interaction import EveAIAgent, EveAITask, EveAITool, Specialist
from common.utils.cache.crewai_configuration import (
    ProcessedAgentConfig, ProcessedTaskConfig, ProcessedToolConfig,
    SpecialistProcessedConfig
)

T = TypeVar('T')  # For generic model types


class BaseCrewAIConfigProcessor:
    """Base processor for specialist configurations"""

    # Standard mapping between model fields and template placeholders
    AGENT_FIELD_MAPPING = {
        'role': 'custom_role',
        'goal': 'custom_goal',
        'backstory': 'custom_backstory'
    }

    TASK_FIELD_MAPPING = {
        'task_description': 'custom_description',
        'expected_output': 'custom_expected_output'
    }

    def __init__(self, tenant_id: int, specialist_id: int):
        self.tenant_id = tenant_id
        self.specialist_id = specialist_id
        self.specialist = self._get_specialist()
        self.verbose = self._get_verbose_setting()

    def _get_specialist(self) -> Specialist:
        """Get specialist and verify existence"""
        specialist = Specialist.query.get(self.specialist_id)
        if not specialist:
            raise ValueError(f"Specialist {self.specialist_id} not found")
        return specialist

    def _get_verbose_setting(self) -> bool:
        """Get verbose setting from specialist"""
        return bool(self.specialist.tuning)

    def _get_db_items(self, model_class: Type[T], type_list: List[str]) -> Dict[str, T]:
        """Get database items of specified type"""
        items = (model_class.query
                 .filter_by(specialist_id=self.specialist_id)
                 .filter(model_class.type.in_(type_list))
                 .all())
        return {item.type: item for item in items}

    def _apply_replacements(self, text: str, replacements: Dict[str, str]) -> str:
        """Apply text replacements to a string"""
        result = text
        for key, value in replacements.items():
            if value is not None:  # Only replace if value exists
                placeholder = "{" + key + "}"
                result = result.replace(placeholder, str(value))
        return result

    def _process_agent_configs(self, specialist_config: Dict[str, Any]) -> Dict[str, ProcessedAgentConfig]:
        """Process all agent configurations"""
        agent_configs = {}

        if 'agents' not in specialist_config:
            return agent_configs

        # Get all DB agents at once
        agent_types = [agent_def['type'] for agent_def in specialist_config['agents']]
        db_agents = self._get_db_items(EveAIAgent, agent_types)

        for agent_def in specialist_config['agents']:
            agent_type = agent_def['type']
            agent_type_lower = agent_type.lower()
            db_agent = db_agents.get(agent_type)

            # Get full configuration
            config = cache_manager.agents_config_cache.get_config(
                agent_type,
                agent_def.get('version', '1.0')
            )

            # Start with YAML values
            role = config['role']
            goal = config['goal']
            backstory = config['backstory']

            # Apply DB values if they exist
            if db_agent:
                for model_field, placeholder in self.AGENT_FIELD_MAPPING.items():
                    value = getattr(db_agent, model_field)
                    if value:
                        placeholder_text = "{" + placeholder + "}"
                        role = role.replace(placeholder_text, value)
                        goal = goal.replace(placeholder_text, value)
                        backstory = backstory.replace(placeholder_text, value)

            agent_configs[agent_type_lower] = ProcessedAgentConfig(
                role=role,
                goal=goal,
                backstory=backstory,
                name=agent_def.get('name') or config.get('name', agent_type_lower),
                type=agent_type,
                description=agent_def.get('description') or config.get('description'),
                verbose=self.verbose
            )

        return agent_configs

    def _process_task_configs(self, specialist_config: Dict[str, Any]) -> Dict[str, ProcessedTaskConfig]:
        """Process all task configurations"""
        task_configs = {}

        if 'tasks' not in specialist_config:
            return task_configs

        # Get all DB tasks at once
        task_types = [task_def['type'] for task_def in specialist_config['tasks']]
        db_tasks = self._get_db_items(EveAITask, task_types)

        for task_def in specialist_config['tasks']:
            task_type = task_def['type']
            task_type_lower = task_type.lower()
            db_task = db_tasks.get(task_type)

            # Get full configuration
            config = cache_manager.tasks_config_cache.get_config(
                task_type,
                task_def.get('version', '1.0')
            )

            # Start with YAML values
            task_description = config['task_description']
            expected_output = config['expected_output']

            # Apply DB values if they exist
            if db_task:
                for model_field, placeholder in self.TASK_FIELD_MAPPING.items():
                    value = getattr(db_task, model_field)
                    if value:
                        placeholder_text = "{" + placeholder + "}"
                        task_description = task_description.replace(placeholder_text, value)
                        expected_output = expected_output.replace(placeholder_text, value)

            task_configs[task_type_lower] = ProcessedTaskConfig(
                task_description=task_description,
                expected_output=expected_output,
                name=task_def.get('name') or config.get('name', task_type_lower),
                type=task_type,
                description=task_def.get('description') or config.get('description'),
                verbose=self.verbose
            )

        return task_configs

    def _process_tool_configs(self, specialist_config: Dict[str, Any]) -> Dict[str, ProcessedToolConfig]:
        """Process all tool configurations"""
        tool_configs = {}

        if 'tools' not in specialist_config:
            return tool_configs

        # Get all DB tools at once
        tool_types = [tool_def['type'] for tool_def in specialist_config['tools']]
        db_tools = self._get_db_items(EveAITool, tool_types)

        for tool_def in specialist_config['tools']:
            tool_type = tool_def['type']
            tool_type_lower = tool_type.lower()
            db_tool = db_tools.get(tool_type)

            # Get full configuration
            config = cache_manager.tools_config_cache.get_config(
                tool_type,
                tool_def.get('version', '1.0')
            )

            # Combine configuration
            tool_config = config.get('configuration', {})
            if db_tool and db_tool.configuration:
                tool_config.update(db_tool.configuration)

            tool_configs[tool_type_lower] = ProcessedToolConfig(
                name=tool_def.get('name') or config.get('name', tool_type_lower),
                type=tool_type,
                description=tool_def.get('description') or config.get('description'),
                configuration=tool_config,
                verbose=self.verbose
            )

        return tool_configs

    def process_config(self) -> SpecialistProcessedConfig:
        """Process complete specialist configuration"""
        try:
            # Get full specialist configuration
            specialist_config = cache_manager.specialists_config_cache.get_config(
                self.specialist.type,
                self.specialist.type_version
            )

            if not specialist_config:
                raise ValueError(f"No configuration found for {self.specialist.type}")

            # Process all configurations
            processed_config = SpecialistProcessedConfig(
                agents=self._process_agent_configs(specialist_config),
                tasks=self._process_task_configs(specialist_config),
                tools=self._process_tool_configs(specialist_config)
            )
            return processed_config

        except Exception as e:
            current_app.logger.error(f"Error processing specialist configuration: {e}")
            raise