# Copied from template-manager (2)/template-manager/tech_stack_service.py
# See original for full implementation details


#!/usr/bin/env python3
"""
Complete Tech Stack Recommendation Service
Consolidated service that includes all essential functionality:
- AI-powered tech stack recommendations
- Claude API integration
- Feature extraction
- Neo4j knowledge graph operations
- Database operations
"""

import os
import sys
import json
import asyncio
import asyncpg
from datetime import datetime
from typing import Dict, List, Any, Optional
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
import uvicorn
from loguru import logger
import anthropic
import requests
from neo4j import AsyncGraphDatabase

# Configure logging
logger.remove()
# Check if running as command line tool
if len(sys.argv) > 2 and sys.argv[1] == "--template-id":
    # For command line usage, output logs to stderr
    logger.add(lambda msg: print(msg, end="", file=sys.stderr), level="ERROR", format="{time} | {level} | {message}")
else:
    # For server usage, output logs to stdout
    logger.add(lambda msg: print(msg, end=""), level="INFO", format="{time} | {level} | {message}")

# ============================================================================
# PYDANTIC MODELS
# ============================================================================

class TechRecommendationRequest(BaseModel):
    template_id: str = Field(..., description="Template ID to get recommendations for")

class TechRecommendationResponse(BaseModel):
    template_id: str
    stack_name: str
    monthly_cost: float
    setup_cost: float
    team_size: str
    development_time: int
    satisfaction: int
    success_rate: int
    frontend: str
    backend: str
    database: str
    cloud: str
    testing: str
    mobile: str
    devops: str
    ai_ml: str
    # Single recommended tool
    recommended_tool: str = ""
    recommendation_score: float
    created_at: datetime

# ============================================================================
# CLAUDE CLIENT
# ============================================================================

class ClaudeClient:
    """Claude API client for tech stack recommendations"""
    
    def __init__(self):
        # Claude API configuration
        self.api_key = os.getenv("CLAUDE_API_KEY")
        
        if not self.api_key:
            logger.warning("CLAUDE_API_KEY environment variable not set - AI features will be limited")
            self.client = None
        else:
            # Initialize Anthropic client
            self.client = anthropic.Anthropic(api_key=self.api_key)
        
        # Database configuration with fallback
        self.db_config = self._get_db_config()
        
        logger.info("ClaudeClient initialized")
    
    def _get_db_config(self):
        """Get database configuration with fallback options"""
        # Try environment variables first
        host = os.getenv("POSTGRES_HOST")
        if not host:
            # Check if running inside Docker (postgres hostname available)
            try:
                import socket
                socket.gethostbyname("postgres")
                host = "postgres"  # Docker internal network
            except socket.gaierror:
                # Not in Docker, use localhost
                host = "localhost"
        
        return {
            "host": host,
            "port": int(os.getenv("POSTGRES_PORT", "5432")),
            "database": os.getenv("POSTGRES_DB", "dev_pipeline"),
            "user": os.getenv("POSTGRES_USER", "pipeline_admin"),
            "password": os.getenv("POSTGRES_PASSWORD", "secure_pipeline_2024")
        }
    
    async def connect_db(self):
        """Create database connection"""
        try:
            conn = await asyncpg.connect(**self.db_config)
            logger.info("Database connected successfully")
            return conn
        except Exception as e:
            logger.error(f"Database connection failed: {e}")
            raise
    
    def create_prompt(self, template_data: Dict[str, Any], keywords: List[str]) -> str:
        """Create a prompt for Claude API"""
        prompt = f"""
You are a tech stack recommendation expert. Based on the following template information and extracted keywords, recommend a complete tech stack solution including both technologies and ONE essential business tool.

Template Information:
- Type: {template_data.get('type', 'N/A')}
- Title: {template_data.get('title', 'N/A')}
- Description: {template_data.get('description', 'N/A')}
- Category: {template_data.get('category', 'N/A')}

Extracted Keywords: {', '.join(keywords) if keywords else 'None'}

Please provide a complete tech stack recommendation in the following JSON format. Include realistic cost estimates, team size, development time, success metrics, and ONE relevant business tool.

{{
  "stack_name": "MVP Startup Stack",
  "monthly_cost": 65.0,
  "setup_cost": 850.0,
  "team_size": "2-4",
  "development_time": 3,
  "satisfaction": 85,
  "success_rate": 88,
  "frontend": "Next.js",
  "backend": "Node.js",
  "database": "PostgreSQL",
  "cloud": "Railway",
  "testing": "Jest",
  "mobile": "React Native",
  "devops": "GitHub Actions",
  "ai_ml": "Hugging Face",
  "recommended_tool": "Shopify",
  "recommendation_score": 96.5
}}

Guidelines:
- Choose technologies that work well together
- Provide realistic cost estimates based on the template complexity
- Estimate development time in months
- Include satisfaction and success rate percentages (0-100)
- Set recommendation_score based on how well the stack fits the requirements (0-100)
- Use modern, popular technologies
- Consider the template's business domain and technical requirements
- Select ONLY ONE tool total that best complements the entire tech stack
- Choose the most appropriate tool for the template's specific needs and industry
- The tool should be the most essential business tool for this particular template

IMPORTANT TOOL SELECTION RULES:
- For E-commerce/Online Store templates: Use Shopify, WooCommerce, or Magento
- For CRM/Customer Management: Use Salesforce, HubSpot, or Zoho CRM
- For Analytics/Data: Use Google Analytics, Mixpanel, or Tableau
- For Payment Processing: Use Stripe, PayPal, or Razorpay
- For Communication/Collaboration: Use Slack, Microsoft Teams, or Discord
- For Project Management: Use Trello, Jira, or Asana
- For Marketing: Use Mailchimp, SendGrid, or Constant Contact
- For Social Media: Use Hootsuite, Buffer, or Sprout Social
- For AI/ML projects: Use TensorFlow, PyTorch, or Hugging Face
- For Mobile Apps: Use Firebase, AWS Amplify, or App Store Connect
- For Enterprise: Use Microsoft 365, Google Workspace, or Atlassian
- For Startups: Use Notion, Airtable, or Zapier

Choose the tool that BEST matches the template's primary business function and industry.

Provide only the JSON response, no additional text.
"""
        return prompt
    
    async def get_recommendation(self, template_id: str) -> Dict[str, Any]:
        """Get tech stack recommendation from Claude API"""
        try:
            if not self.client:
                raise HTTPException(status_code=503, detail="Claude API not available - API key not configured")
            
            conn = await self.connect_db()
            
            # Get template data - check both templates and custom_templates tables
            template_query = """
                SELECT id, type, title, description, category 
                FROM templates 
                WHERE id = $1
            """
            template_result = await conn.fetchrow(template_query, template_id)
            
            if not template_result:
                # Try custom_templates table
                template_query = """
                    SELECT id, type, title, description, category 
                    FROM custom_templates 
                    WHERE id = $1
                """
                template_result = await conn.fetchrow(template_query, template_id)
            
            if not template_result:
                await conn.close()
                raise HTTPException(status_code=404, detail="Template not found")
            
            template_data = dict(template_result)
            
            # Get extracted keywords
            keywords_result = await conn.fetchrow('''
                SELECT keywords_json FROM extracted_keywords 
                WHERE template_id = $1 AND keywords_json IS NOT NULL
                ORDER BY created_at DESC
                LIMIT 1
            ''', template_id)
            
            keywords = []
            if keywords_result:
                keywords = json.loads(keywords_result['keywords_json'])
            
            await conn.close()
            
            # Create prompt with extracted keywords
            prompt = self.create_prompt(template_data, keywords)
            
            # Call Claude API
            response = self.client.messages.create(
                model="claude-3-5-sonnet-20241022",
                max_tokens=2000,
                temperature=0.7,
                messages=[{"role": "user", "content": prompt}]
            )
            
            # Parse response
            response_text = response.content[0].text.strip()
            
            # Extract JSON from response
            if response_text.startswith('```json'):
                response_text = response_text[7:-3]
            elif response_text.startswith('```'):
                response_text = response_text[3:-3]
            
            response_data = json.loads(response_text)
            
            # Store recommendation
            await self.store_tech_recommendations(template_id, response_data)
            
            # Auto-migrate new recommendation to Neo4j
            try:
                await self.auto_migrate_single_recommendation(template_id)
            except Exception as e:
                logger.warning(f"Auto-migration failed for template {template_id}: {e}")
            
            return response_data
            
        except Exception as e:
            logger.error(f"Error getting recommendation: {e}")
            raise HTTPException(status_code=500, detail=f"Failed to get recommendation: {str(e)}")
    
    async def store_tech_recommendations(self, template_id: str, response_data: Dict[str, Any]):
        """Store tech recommendations in tech_stack_recommendations table"""
        try:
            conn = await self.connect_db()
            
            # Clear existing recommendations for this template
            await conn.execute(
                "DELETE FROM tech_stack_recommendations WHERE template_id = $1",
                template_id
            )
            
            # Handle fields that could be dict or string
            def format_field(field_value):
                if isinstance(field_value, dict):
                    return json.dumps(field_value)
                return str(field_value) if field_value is not None else ''
            
            # Handle single tool
            def format_tool(tool_value):
                if isinstance(tool_value, str):
                    return tool_value
                return ''
            
            # Store the complete tech stack in the proper table
            await conn.execute(
                """
                INSERT INTO tech_stack_recommendations 
                (template_id, stack_name, monthly_cost, setup_cost, team_size, development_time, 
                 satisfaction, success_rate, frontend, backend, database, cloud, testing, 
                 mobile, devops, ai_ml, recommended_tool, recommendation_score)
                VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
                """,
                template_id,
                response_data.get('stack_name', 'Tech Stack'),
                response_data.get('monthly_cost', 0.0),
                response_data.get('setup_cost', 0.0),
                response_data.get('team_size', '1-2'),
                response_data.get('development_time', 1),
                response_data.get('satisfaction', 0),
                response_data.get('success_rate', 0),
                format_field(response_data.get('frontend', '')),
                format_field(response_data.get('backend', '')),
                format_field(response_data.get('database', '')),
                format_field(response_data.get('cloud', '')),
                format_field(response_data.get('testing', '')),
                format_field(response_data.get('mobile', '')),
                format_field(response_data.get('devops', '')),
                format_field(response_data.get('ai_ml', '')),
                format_tool(response_data.get('recommended_tool', '')),
                response_data.get('recommendation_score', 0.0)
            )
            
            await conn.close()
            logger.info(f"Stored complete tech stack with tools for template {template_id} in tech_stack_recommendations table")
        except Exception as e:
            logger.error(f"Error storing tech recommendations: {e}")
    
    async def auto_migrate_single_recommendation(self, template_id: str):
        """Auto-migrate a single recommendation from tech_stack_recommendations table to Neo4j"""
        try:
            logger.info(f"Starting auto-migration for template {template_id}")
            conn = await self.connect_db()
            
            # Get recommendation from tech_stack_recommendations table
            rec_query = """
                SELECT * FROM tech_stack_recommendations 
                WHERE template_id = $1
                ORDER BY created_at DESC LIMIT 1
            """
            rec = await conn.fetchrow(rec_query, template_id)
            
            if not rec:
                logger.warning(f"No recommendation found in tech_stack_recommendations for template {template_id}")
                await conn.close()
                return
            
            logger.info(f"Found recommendation: {rec['stack_name']} for template {template_id}")
            
            # Get template data for context - check both templates and custom_templates tables
            template_query = """
                SELECT id, title, description, category, type
                FROM templates 
                WHERE id = $1
            """
            template_result = await conn.fetchrow(template_query, template_id)
            
            if not template_result:
                # Try custom_templates table
                template_query = """
                    SELECT id, title, description, category, type
                    FROM custom_templates 
                    WHERE id = $1
                """
                template_result = await conn.fetchrow(template_query, template_id)
            
            if not template_result:
                logger.warning(f"Template {template_id} not found in templates or custom_templates tables")
                await conn.close()
                return
            
            template_data = dict(template_result)
            template_data['id'] = str(template_data['id'])
            
            # Get extracted keywords
            keywords_result = await conn.fetchrow('''
                SELECT keywords_json FROM extracted_keywords 
                WHERE template_id = $1 AND keywords_json IS NOT NULL
                ORDER BY created_at DESC
                LIMIT 1
            ''', template_id)
            
            keywords = []
            if keywords_result:
                keywords = json.loads(keywords_result['keywords_json'])
            
            await conn.close()
            
            # Create template node in Neo4j
            await neo4j_client.create_template_node(template_data)
            
            # Create tech stack node
            tech_stack_data = {
                "name": rec['stack_name'],
                "category": "tech_stack",
                "maturity_score": 0.9,
                "learning_curve": "medium",
                "performance_rating": float(rec['recommendation_score']) / 100.0
            }
            await neo4j_client.create_technology_node(tech_stack_data)
            
            # Create recommendation relationship
            await neo4j_client.create_recommendation_relationship(
                str(template_id),
                rec['stack_name'],
                "tech_stack",
                float(rec['recommendation_score']) / 100.0
            )
            
            # Create individual technology nodes and relationships
            tech_fields = ['frontend', 'backend', 'database', 'cloud', 'testing', 'mobile', 'devops', 'ai_ml']
            
            for field in tech_fields:
                tech_value = rec[field]
                if tech_value and tech_value.strip():
                    # Parse JSON if it's a string
                    if isinstance(tech_value, str) and tech_value.startswith('{'):
                        try:
                            tech_value = json.loads(tech_value)
                            if isinstance(tech_value, dict):
                                tech_name = tech_value.get('name', str(tech_value))
                            else:
                                tech_name = str(tech_value)
                        except:
                            tech_name = str(tech_value)
                    else:
                        tech_name = str(tech_value)
                    
                    # Create technology node
                    tech_data = {
                        "name": tech_name,
                        "category": field,
                        "maturity_score": 0.8,
                        "learning_curve": "medium",
                        "performance_rating": 0.8
                    }
                    await neo4j_client.create_technology_node(tech_data)
                    
                    # Create relationship
                    await neo4j_client.create_recommendation_relationship(
                        str(template_id),
                        tech_name,
                        field,
                        0.8
                    )
            
            # Create tool node for single recommended tool
            recommended_tool = rec.get('recommended_tool', '')
            if recommended_tool and recommended_tool.strip():
                # Create tool node
                tool_data = {
                    "name": recommended_tool,
                    "category": "business_tool",
                    "type": "Tool",
                    "maturity_score": 0.8,
                    "learning_curve": "easy",
                    "performance_rating": 0.8
                }
                await neo4j_client.create_technology_node(tool_data)
                
                # Create relationship
                await neo4j_client.create_recommendation_relationship(
                    str(template_id),
                    recommended_tool,
                    "business_tool",
                    0.8
                )
            
            # Create keyword relationships
            if keywords and len(keywords) > 0:
                logger.info(f"Creating {len(keywords)} keyword relationships for template {template_id}")
                for keyword in keywords:
                    if keyword and keyword.strip():
                        await neo4j_client.create_keyword_relationship(str(template_id), keyword)
            else:
                logger.warning(f"No keywords found for template {template_id}, skipping keyword relationships")
            
            # Create TemplateRecommendation node with rich data
            recommendation_data = {
                'stack_name': rec['stack_name'],
                'description': template_data.get('description', ''),
                'project_scale': 'medium',
                'team_size': 3,
                'experience_level': 'intermediate',
                'confidence_score': int(rec['recommendation_score']),
                'recommendation_reasons': [
                    f"Tech stack: {rec['stack_name']}",
                    f"Score: {rec['recommendation_score']}/100",
                    "AI-generated recommendation"
                ],
                'key_features': [
                    f"Frontend: {rec.get('frontend', 'N/A')}",
                    f"Backend: {rec.get('backend', 'N/A')}",
                    f"Database: {rec.get('database', 'N/A')}",
                    f"Cloud: {rec.get('cloud', 'N/A')}"
                ],
                'estimated_development_time_months': rec.get('development_time', 3),
                'complexity_level': 'medium',
                'budget_range_usd': f"${rec.get('monthly_cost', 0):.0f} - ${rec.get('setup_cost', 0):.0f}",
                'time_to_market_weeks': rec.get('development_time', 3) * 4,
                'scalability_requirements': 'moderate',
                'security_requirements': 'standard',
                'success_rate_percentage': rec.get('success_rate', 85),
                'user_satisfaction_score': rec.get('satisfaction', 85)
            }
            await neo4j_client.create_template_recommendation_node(str(template_id), recommendation_data)
            
            # Create HAS_RECOMMENDATION relationship between Template and TemplateRecommendation
            await neo4j_client.create_has_recommendation_relationship(str(template_id), f"rec-{template_id}")
            
            logger.info(f"✅ Successfully auto-migrated template {template_id} to Neo4j knowledge graph")
            
        except Exception as e:
            logger.error(f"Error in auto-migration for template {template_id}: {e}")

# ============================================================================
# FEATURE EXTRACTOR
# ============================================================================

class FeatureExtractor:
    """Extracts features from templates and gets tech stack recommendations"""
    
    def __init__(self):
        # Database configurations with fallback
        self.template_db_config = self._get_db_config()
        
        # Claude API configuration
        self.claude_api_key = os.getenv("CLAUDE_API_KEY")
        if not self.claude_api_key:
            logger.warning("CLAUDE_API_KEY not set - AI features will be limited")
        
        self.claude_client = anthropic.Anthropic(api_key=self.claude_api_key) if self.claude_api_key else None
        
        logger.info("FeatureExtractor initialized")
    
    def _get_db_config(self):
        """Get database configuration with fallback options"""
        # Try environment variables first
        host = os.getenv("POSTGRES_HOST")
        if not host:
            # Check if running inside Docker (postgres hostname available)
            try:
                import socket
                socket.gethostbyname("postgres")
                host = "postgres"  # Docker internal network
            except socket.gaierror:
                # Not in Docker, use localhost
                host = "localhost"
        
        return {
            "host": host,
            "port": int(os.getenv("POSTGRES_PORT", "5432")),
            "database": os.getenv("POSTGRES_DB", "dev_pipeline"),
            "user": os.getenv("POSTGRES_USER", "pipeline_admin"),
            "password": os.getenv("POSTGRES_PASSWORD", "secure_pipeline_2024")
        }

    async def connect_db(self):
        """Create database connection"""
        try:
            conn = await asyncpg.connect(**self.template_db_config)
            logger.info("Database connected successfully")
            return conn
        except Exception as e:
            logger.error(f"Database connection failed: {e}")
            raise

    async def extract_keywords_from_template(self, template_data: Dict[str, Any]) -> List[str]:
        """Extract keywords from template using local NLP processing"""
        try:
            # Combine all text data
            text_content = f"{template_data.get('title', '')} {template_data.get('description', '')} {template_data.get('category', '')}"
            
            # Clean and process text
            keywords = self._extract_keywords_local(text_content)
            
            logger.info(f"Extracted {len(keywords)} keywords locally: {keywords}")
            return keywords
            
        except Exception as e:
            logger.error(f"Error extracting keywords: {e}")
            return []
    
    def _extract_keywords_local(self, text: str) -> List[str]:
        """Extract keywords using local text processing"""
        import re
        from collections import Counter
        
        # Define technical and business keywords
        tech_keywords = {
            'web', 'api', 'database', 'frontend', 'backend', 'mobile', 'cloud', 'ai', 'ml', 'analytics',
            'ecommerce', 'e-commerce', 'payment', 'authentication', 'security', 'testing', 'deployment',
            'microservices', 'rest', 'graphql', 'react', 'angular', 'vue', 'node', 'python', 'java',
            'javascript', 'typescript', 'docker', 'kubernetes', 'aws', 'azure', 'gcp', 'postgresql',
            'mysql', 'mongodb', 'redis', 'elasticsearch', 'rabbitmq', 'kafka', 'nginx', 'jenkins',
            'gitlab', 'github', 'ci', 'cd', 'devops', 'monitoring', 'logging', 'caching', 'scaling'
        }
        
        business_keywords = {
            'healthcare', 'medical', 'patient', 'appointment', 'records', 'telehealth', 'pharmacy',
            'finance', 'banking', 'payment', 'invoice', 'accounting', 'trading', 'investment',
            'education', 'learning', 'student', 'course', 'training', 'certification', 'lms',
            'retail', 'inventory', 'shopping', 'cart', 'checkout', 'order', 'shipping', 'warehouse',
            'crm', 'sales', 'marketing', 'lead', 'customer', 'support', 'ticket', 'workflow',
            'automation', 'process', 'approval', 'document', 'file', 'content', 'management',
            'enterprise', 'business', 'solution', 'platform', 'service', 'application', 'system'
        }
        
        # Clean text
        text = re.sub(r'[^\w\s-]', ' ', text.lower())
        words = re.findall(r'\b\w+\b', text)
        
        # Filter out common stop words
        stop_words = {
            'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with',
            'by', 'from', 'up', 'about', 'into', 'through', 'during', 'before', 'after',
            'above', 'below', 'between', 'among', 'is', 'are', 'was', 'were', 'be', 'been',
            'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
            'should', 'may', 'might', 'must', 'can', 'this', 'that', 'these', 'those',
            'i', 'you', 'he', 'she', 'it', 'we', 'they', 'me', 'him', 'her', 'us', 'them'
        }
        
        # Filter words
        filtered_words = [word for word in words if len(word) > 2 and word not in stop_words]
        
        # Count word frequency
        word_counts = Counter(filtered_words)
        
        # Extract relevant keywords
        keywords = []
        
        # Add technical keywords found in text
        for word in filtered_words:
            if word in tech_keywords or word in business_keywords:
                keywords.append(word)
        
        # Add most frequent meaningful words (excluding already added keywords)
        remaining_words = [word for word, count in word_counts.most_common(10) 
                          if word not in keywords and count > 1]
        keywords.extend(remaining_words[:5])
        
        # Remove duplicates and limit to 10 keywords
        unique_keywords = list(dict.fromkeys(keywords))[:10]
        
        return unique_keywords

    async def get_template_data(self, template_id: str) -> Optional[Dict[str, Any]]:
        """Get template data from database"""
        try:
            conn = await self.connect_db()
            
            # Try templates table first
            template = await conn.fetchrow(
                """
                SELECT id, title, description, category, type
                FROM templates 
                WHERE id = $1
                """,
                template_id
            )
            
            if not template:
                # Try custom_templates table
                template = await conn.fetchrow(
                    """
                    SELECT id, title, description, category, type
                    FROM custom_templates 
                    WHERE id = $1
                    """,
                    template_id
                )
            
            await conn.close()
            
            if template:
                return dict(template)
            return None
            
        except Exception as e:
            logger.error(f"Error getting template data: {e}")
            return None

    async def get_all_templates(self) -> List[Dict[str, Any]]:
        """Get all templates from both tables"""
        try:
            conn = await self.connect_db()
            
            # Get from templates table
            templates = await conn.fetch(
                """
                SELECT id, title, description, category, type
                FROM templates 
                WHERE type NOT IN ('_system', '_migration', '_test')
                """
            )
            
            # Get from custom_templates table
            custom_templates = await conn.fetch(
                """
                SELECT id, title, description, category, type
                FROM custom_templates
                """
            )
            
            await conn.close()
            
            # Combine results
            all_templates = []
            for template in templates:
                all_templates.append(dict(template))
            for template in custom_templates:
                all_templates.append(dict(template))
            
            return all_templates
            
        except Exception as e:
            logger.error(f"Error getting all templates: {e}")
            return []

    async def store_extracted_keywords(self, template_id: str, keywords: List[str]):
        """Store extracted keywords in database"""
        try:
            conn = await self.connect_db()
            
            # Determine template source
            template_source = 'templates'
            template = await conn.fetchrow("SELECT id FROM templates WHERE id = $1", template_id)
            if not template:
                template_source = 'custom_templates'
            
            # Store keywords
            await conn.execute(
                """
                INSERT INTO extracted_keywords (template_id, template_source, keywords_json, created_at)
                VALUES ($1, $2, $3, $4)
                ON CONFLICT (template_id, template_source) 
                DO UPDATE SET keywords_json = $3, updated_at = $4
                """,
                template_id,
                template_source,
                json.dumps(keywords),
                datetime.now()
            )
            
            await conn.close()
            logger.info(f"Stored keywords for template {template_id} from {template_source}")
            
        except Exception as e:
            logger.error(f"Error storing extracted keywords: {e}")

    async def store_keywords(self, template_id: str, keywords: List[str]):
        """Store extracted keywords in database"""
        try:
            conn = await self.connect_db()
            
            # Store keywords
            await conn.execute(
                """
                INSERT INTO extracted_keywords (template_id, keywords_json, created_at)
                VALUES ($1, $2, $3)
                ON CONFLICT (template_id) 
                DO UPDATE SET keywords_json = $2, updated_at = $3
                """,
                template_id,
                json.dumps(keywords),
                datetime.now()
            )
            
            await conn.close()
            logger.info(f"Stored keywords for template {template_id}")
            
        except Exception as e:
            logger.error(f"Error storing keywords: {e}")


# ============================================================================
# NEO4J CLIENT
# ============================================================================

class Neo4jClient:
    """Neo4j client for knowledge graph operations"""
    
    def __init__(self):
        # Neo4j configuration - try multiple connection options
        self.uri = self._get_neo4j_uri()
        self.username = os.getenv("NEO4J_USERNAME", "neo4j")
        self.password = os.getenv("NEO4J_PASSWORD", "password")
        
        # Create driver
        self._create_driver()
    
    def _get_neo4j_uri(self):
        """Get Neo4j URI with fallback options"""
        # Try environment variable first
        uri = os.getenv("NEO4J_URI")
        if uri:
            return uri
        
        # Check if running inside Docker (neo4j hostname available)
        try:
            import socket
            socket.gethostbyname("neo4j")
            return "bolt://neo4j:7687"  # Docker internal network
        except socket.gaierror:
            # Not in Docker, use localhost
            return "bolt://localhost:7687"
    
    def _create_driver(self):
        """Create Neo4j driver"""
        self.driver = AsyncGraphDatabase.driver(
            self.uri,
            auth=(self.username, self.password)
        )
        logger.info(f"Neo4jClient initialized with URI: {self.uri}")
    
    async def close(self):
        """Close the Neo4j driver"""
        await self.driver.close()
        logger.info("Neo4j connection closed")
    
    async def test_connection(self):
        """Test Neo4j connection"""
        try:
            async with self.driver.session() as session:
                result = await session.run("RETURN 1 as test")
                record = await result.single()
                if record and record["test"] == 1:
                    logger.info("Neo4j connection successful")
                    return True
                else:
                    logger.error("Neo4j connection test failed")
                    return False
        except Exception as e:
            logger.error(f"Neo4j connection failed: {e}")
            return False
    
    async def create_constraints(self):
        """Create Neo4j constraints"""
        try:
            async with self.driver.session() as session:
                # Create constraints
                constraints = [
                    "CREATE CONSTRAINT template_id_unique IF NOT EXISTS FOR (t:Template) REQUIRE t.id IS UNIQUE",
                    "CREATE CONSTRAINT technology_name_unique IF NOT EXISTS FOR (tech:Technology) REQUIRE tech.name IS UNIQUE",
                    "CREATE CONSTRAINT keyword_name_unique IF NOT EXISTS FOR (k:Keyword) REQUIRE k.name IS UNIQUE"
                ]
                
                for constraint in constraints:
                    try:
                        await session.run(constraint)
                    except Exception as e:
                        logger.warning(f"Constraint creation warning: {e}")
                
                logger.info("Neo4j constraints created successfully")
        except Exception as e:
            logger.error(f"Error creating constraints: {e}")
    
    async def create_template_node(self, template_data: Dict[str, Any]):
        """Create or update template node"""
        try:
            async with self.driver.session() as session:
                await session.run(
                    """
                    MERGE (t:Template {id: $id})
                    SET t.name = $name,
                        t.description = $description,
                        t.category = $category,
                        t.type = $type,
                        t.updated_at = datetime()
                    """,
                    id=template_data.get('id'),
                    name=template_data.get('name', template_data.get('title', '')),
                    description=template_data.get('description', ''),
                    category=template_data.get('category', ''),
                    type=template_data.get('type', '')
                )
                logger.info(f"Created/updated template node: {template_data.get('name', template_data.get('title', ''))}")
        except Exception as e:
            logger.error(f"Error creating template node: {e}")
    
    async def create_technology_node(self, tech_data: Dict[str, Any]):
        """Create or update technology node"""
        try:
            async with self.driver.session() as session:
                await session.run(
                    """
                    MERGE (tech:Technology {name: $name})
                    SET tech.category = $category,
                        tech.type = $type,
                        tech.maturity_score = $maturity_score,
                        tech.learning_curve = $learning_curve,
                        tech.performance_rating = $performance_rating,
                        tech.updated_at = datetime()
                    """,
                    name=tech_data.get('name'),
                    category=tech_data.get('category', ''),
                    type=tech_data.get('type', 'Technology'),
                    maturity_score=tech_data.get('maturity_score', 0.8),
                    learning_curve=tech_data.get('learning_curve', 'medium'),
                    performance_rating=tech_data.get('performance_rating', 0.8)
                )
                logger.info(f"Created/updated technology node: {tech_data.get('name')}")
        except Exception as e:
            logger.error(f"Error creating technology node: {e}")
    
    async def create_recommendation_relationship(self, template_id: str, tech_name: str, category: str, score: float):
        """Create recommendation relationship"""
        try:
            async with self.driver.session() as session:
                await session.run(
                    """
                    MATCH (t:Template {id: $template_id})
                    MATCH (tech:Technology {name: $tech_name})
                    MERGE (t)-[r:RECOMMENDED_TECHNOLOGY {category: $category, score: $score}]->(tech)
                    SET r.updated_at = datetime()
                    """,
                    template_id=template_id,
                    tech_name=tech_name,
                    category=category,
                    score=score
                )
                logger.info(f"Created recommendation relationship: {template_id} -> {tech_name}")
        except Exception as e:
            logger.error(f"Error creating recommendation relationship: {e}")
    
    async def create_keyword_relationship(self, template_id: str, keyword: str):
        """Create keyword relationship"""
        try:
            async with self.driver.session() as session:
                # Create keyword node
                await session.run(
                    """
                    MERGE (k:Keyword {name: $keyword})
                    SET k.updated_at = datetime()
                    """,
                    keyword=keyword
                )
                
                # Create relationship
                await session.run(
                    """
                    MATCH (t:Template {id: $template_id})
                    MATCH (k:Keyword {name: $keyword})
                    MERGE (t)-[r:HAS_KEYWORD]->(k)
                    SET r.updated_at = datetime()
                    """,
                    template_id=template_id,
                    keyword=keyword
                )
                logger.info(f"Created keyword relationship: {template_id} -> {keyword}")
        except Exception as e:
            logger.error(f"Error creating keyword relationship: {e}")
    
    async def create_has_recommendation_relationship(self, template_id: str, recommendation_id: str):
        """Create HAS_RECOMMENDATION relationship between Template and TemplateRecommendation"""
        try:
            async with self.driver.session() as session:
                await session.run(
                    """
                    MATCH (t:Template {id: $template_id})
                    MATCH (tr:TemplateRecommendation {id: $recommendation_id})
                    MERGE (t)-[r:HAS_RECOMMENDATION]->(tr)
                    SET r.created_at = datetime(),
                        r.updated_at = datetime()
                    """,
                    template_id=template_id,
                    recommendation_id=recommendation_id
                )
                logger.info(f"Created HAS_RECOMMENDATION relationship: {template_id} -> {recommendation_id}")
        except Exception as e:
            logger.error(f"Error creating HAS_RECOMMENDATION relationship: {e}")
    
    async def get_recommendations_from_neo4j(self, template_id: str) -> Optional[Dict[str, Any]]:
        """Get tech stack recommendations from Neo4j knowledge graph"""
        try:
            # Convert UUID to string if needed
            template_id_str = str(template_id)
            
            async with self.driver.session() as session:
                # Query for template recommendations from Neo4j
                result = await session.run(
                    """
                    MATCH (t:Template {id: $template_id})-[:HAS_RECOMMENDATION]->(tr:TemplateRecommendation)
                    OPTIONAL MATCH (t)-[r:RECOMMENDED_TECHNOLOGY]->(tech:Technology)
                    WITH tr, collect({
                        name: tech.name,
                        category: r.category,
                        score: r.score,
                        type: tech.type,
                        maturity_score: tech.maturity_score,
                        learning_curve: tech.learning_curve,
                        performance_rating: tech.performance_rating
                    }) as technologies
                    RETURN tr.business_domain as business_domain,
                           tr.project_type as project_type,
                           tr.team_size as team_size,
                           tr.confidence_score as confidence_score,
                           tr.estimated_development_time_months as development_time,
                           tr.success_rate_percentage as success_rate,
                           tr.user_satisfaction_score as satisfaction,
                           tr.budget_range_usd as budget_range,
                           tr.complexity_level as complexity_level,
                           technologies
                    ORDER BY tr.created_at DESC
                    LIMIT 1
                    """,
                    template_id=template_id_str
                )
                
                record = await result.single()
                if record:
                    # Process technologies by category
                    tech_categories = {}
                    for tech in record['technologies']:
                        category = tech['category']
                        if category not in tech_categories:
                            tech_categories[category] = []
                        tech_categories[category].append(tech)
                    
                    # Build recommendation response
                    recommendation = {
                        'stack_name': f"{record['business_domain']} {record['project_type']} Stack",
                        'monthly_cost': record['budget_range'] / 12 if record['budget_range'] else 1000,
                        'setup_cost': record['budget_range'] if record['budget_range'] else 5000,
                        'team_size': record['team_size'] or '2-4',
                        'development_time': record['development_time'] or 6,
                        'satisfaction': record['satisfaction'] or 85,
                        'success_rate': record['success_rate'] or 80,
                        'frontend': '',
                        'backend': '',
                        'database': '',
                        'cloud': '',
                        'testing': '',
                        'mobile': '',
                        'devops': '',
                        'ai_ml': '',
                        'recommended_tool': '',
                        'recommendation_score': record['confidence_score'] or 85.0
                    }
                    
                    # Map technologies to categories
                    for category, techs in tech_categories.items():
                        if techs:
                            best_tech = max(techs, key=lambda x: x['score'])
                            if category.lower() == 'frontend':
                                recommendation['frontend'] = best_tech['name']
                            elif category.lower() == 'backend':
                                recommendation['backend'] = best_tech['name']
                            elif category.lower() == 'database':
                                recommendation['database'] = best_tech['name']
                            elif category.lower() == 'cloud':
                                recommendation['cloud'] = best_tech['name']
                            elif category.lower() == 'testing':
                                recommendation['testing'] = best_tech['name']
                            elif category.lower() == 'mobile':
                                recommendation['mobile'] = best_tech['name']
                            elif category.lower() == 'devops':
                                recommendation['devops'] = best_tech['name']
                            elif category.lower() in ['ai', 'ml', 'ai_ml']:
                                recommendation['ai_ml'] = best_tech['name']
                            elif category.lower() == 'tool':
                                recommendation['recommended_tool'] = best_tech['name']
                    
                    logger.info(f"Found recommendations in Neo4j for template {template_id}: {recommendation['stack_name']}")
                    return recommendation
                else:
                    logger.info(f"No recommendations found in Neo4j for template {template_id}")
                    return None
                    
        except Exception as e:
            logger.error(f"Error getting recommendations from Neo4j: {e}")
            return None

    async def create_template_recommendation_node(self, template_id: str, recommendation_data: Dict[str, Any]):
        """Create TemplateRecommendation node with rich data"""
        try:
            async with self.driver.session() as session:
                # Extract business domain from template category or description
                business_domain = self._extract_business_domain(recommendation_data)
                project_type = self._extract_project_type(recommendation_data)
                
                # Create TemplateRecommendation node
                await session.run(
                    """
                    MERGE (tr:TemplateRecommendation {id: $id})
                    SET tr.business_domain = $business_domain,
                        tr.project_type = $project_type,
                        tr.project_scale = $project_scale,
                        tr.team_size = $team_size,
                        tr.experience_level = $experience_level,
                        tr.confidence_score = $confidence_score,
                        tr.recommendation_reasons = $recommendation_reasons,
                        tr.key_features = $key_features,
                        tr.estimated_development_time_months = $estimated_development_time_months,
                        tr.complexity_level = $complexity_level,
                        tr.budget_range_usd = $budget_range_usd,
                        tr.time_to_market_weeks = $time_to_market_weeks,
                        tr.scalability_requirements = $scalability_requirements,
                        tr.security_requirements = $security_requirements,
                        tr.success_rate_percentage = $success_rate_percentage,
                        tr.user_satisfaction_score = $user_satisfaction_score,
                        tr.created_by_system = $created_by_system,
                        tr.recommendation_source = $recommendation_source,
                        tr.is_active = $is_active,
                        tr.usage_count = $usage_count,
                        tr.created_at = datetime(),
                        tr.updated_at = datetime()
                    """,
                    id=f"rec-{template_id}",
                    business_domain=business_domain,
                    project_type=project_type,
                    project_scale=recommendation_data.get('project_scale', 'medium'),
                    team_size=recommendation_data.get('team_size', 3),
                    experience_level=recommendation_data.get('experience_level', 'intermediate'),
                    confidence_score=recommendation_data.get('confidence_score', 85),
                    recommendation_reasons=recommendation_data.get('recommendation_reasons', ['AI-generated recommendation']),
                    key_features=recommendation_data.get('key_features', []),
                    estimated_development_time_months=recommendation_data.get('estimated_development_time_months', 3),
                    complexity_level=recommendation_data.get('complexity_level', 'medium'),
                    budget_range_usd=recommendation_data.get('budget_range_usd', '$5,000 - $15,000'),
                    time_to_market_weeks=recommendation_data.get('time_to_market_weeks', 12),
                    scalability_requirements=recommendation_data.get('scalability_requirements', 'moderate'),
                    security_requirements=recommendation_data.get('security_requirements', 'standard'),
                    success_rate_percentage=recommendation_data.get('success_rate_percentage', 85),
                    user_satisfaction_score=recommendation_data.get('user_satisfaction_score', 85),
                    created_by_system=True,
                    recommendation_source='ai_model',
                    is_active=True,
                    usage_count=0
                )
                
                # Create relationship from Template to TemplateRecommendation
                await session.run(
                    """
                    MATCH (t:Template {id: $template_id})
                    MATCH (tr:TemplateRecommendation {id: $rec_id})
                    MERGE (t)-[:RECOMMENDED_FOR]->(tr)
                    """,
                    template_id=template_id,
                    rec_id=f"rec-{template_id}"
                )
                
                logger.info(f"Created TemplateRecommendation node: rec-{template_id}")
        except Exception as e:
            logger.error(f"Error creating TemplateRecommendation node: {e}")
    
    def _extract_business_domain(self, recommendation_data: Dict[str, Any]) -> str:
        """Extract business domain from recommendation data"""
        # Try to extract from stack name or description
        stack_name = recommendation_data.get('stack_name', '').lower()
        description = recommendation_data.get('description', '').lower()
        
        if any(word in stack_name or word in description for word in ['ecommerce', 'e-commerce', 'shop', 'store', 'retail']):
            return 'E-commerce'
        elif any(word in stack_name or word in description for word in ['social', 'community', 'network']):
            return 'Social Media'
        elif any(word in stack_name or word in description for word in ['finance', 'payment', 'banking', 'fintech']):
            return 'Fintech'
        elif any(word in stack_name or word in description for word in ['health', 'medical', 'care']):
            return 'Healthcare'
        elif any(word in stack_name or word in description for word in ['education', 'learning', 'course']):
            return 'Education'
        else:
            return 'General Business'
    
    def _extract_project_type(self, recommendation_data: Dict[str, Any]) -> str:
        """Extract project type from recommendation data"""
        stack_name = recommendation_data.get('stack_name', '').lower()
        description = recommendation_data.get('description', '').lower()
        
        if any(word in stack_name or word in description for word in ['web', 'website', 'portal']):
            return 'Web Application'
        elif any(word in stack_name or word in description for word in ['mobile', 'app', 'ios', 'android']):
            return 'Mobile Application'
        elif any(word in stack_name or word in description for word in ['api', 'service', 'microservice']):
            return 'API Service'
        elif any(word in stack_name or word in description for word in ['dashboard', 'admin', 'management']):
            return 'Management Dashboard'
        else:
            return 'Web Application'

# ============================================================================
# FASTAPI APPLICATION
# ============================================================================

# Initialize FastAPI app
app = FastAPI(
    title="Tech Stack Recommendation Service",
    description="AI-powered tech stack recommendations with tools integration",
    version="1.0.0"
)

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Initialize clients
claude_client = ClaudeClient()
feature_extractor = FeatureExtractor()
neo4j_client = Neo4jClient()

@app.on_event("startup")
async def startup_event():
    """Initialize services on startup"""
    print("🚀 STARTING TECH STACK RECOMMENDATION SERVICE")
    print("=" * 50)
    print("✅ AI Service will be available at: http://localhost:8013")
    print("✅ API Documentation: http://localhost:8013/docs")
    print("✅ Test endpoint: POST http://localhost:8013/ai/recommendations")
    print("=" * 50)
    
    # Automatic migration on startup
    print("🔄 Starting automatic migration to Neo4j...")
    try:
        await migrate_to_neo4j()
        print("✅ Automatic migration completed successfully!")
    except Exception as e:
        print(f"⚠️ Migration warning: {e}")
        print("✅ Service will continue running with existing data")
    print("=" * 50)

@app.get("/")
async def root():
    """Root endpoint"""
    return {
        "message": "Tech Stack Recommendation Service",
        "version": "1.0.0",
        "status": "running",
        "endpoints": {
            "recommendations": "POST /ai/recommendations",
            "docs": "GET /docs"
        }
    }

@app.get("/health")
async def health_check():
    """Health check endpoint"""
    return {"status": "healthy", "timestamp": datetime.now()}

@app.post("/ai/recommendations/formatted")
async def get_formatted_tech_recommendations(request: TechRecommendationRequest):
    """Get tech stack recommendations in a formatted, user-friendly way"""
    try:
        logger.info(f"Getting formatted recommendations for template: {request.template_id}")
        
        # Get the standard recommendation
        conn = await claude_client.connect_db()
        
        recommendations = await conn.fetch('''
            SELECT template_id, stack_name, monthly_cost, setup_cost, team_size, 
                   development_time, satisfaction, success_rate, frontend, backend, 
                   database, cloud, testing, mobile, devops, ai_ml, recommended_tool,
                   recommendation_score, created_at, updated_at
            FROM tech_stack_recommendations 
            WHERE template_id = $1
            ORDER BY created_at DESC
            LIMIT 1
        ''', request.template_id)
        
        if recommendations:
            rec = dict(recommendations[0])
            
            await conn.close()
            
            # Format the response in a user-friendly way
            formatted_response = {
                "template_id": request.template_id,
                "tech_stack": {
                    "name": rec.get('stack_name', 'Tech Stack'),
                    "score": f"{rec.get('recommendation_score', 0.0)}/100",
                    "technologies": {
                        "Frontend": rec.get('frontend', ''),
                        "Backend": rec.get('backend', ''),
                        "Database": rec.get('database', ''),
                        "Cloud": rec.get('cloud', ''),
                        "Testing": rec.get('testing', ''),
                        "Mobile": rec.get('mobile', ''),
                        "DevOps": rec.get('devops', ''),
                        "AI/ML": rec.get('ai_ml', '')
                    },
                    "recommended_tool": rec.get('recommended_tool', ''),
                    "costs": {
                        "monthly": f"${rec.get('monthly_cost', 0.0)}",
                        "setup": f"${rec.get('setup_cost', 0.0)}"
                    },
                    "team": {
                        "size": rec.get('team_size', '1-2'),
                        "development_time": f"{rec.get('development_time', 1)} months"
                    },
                    "metrics": {
                        "satisfaction": f"{rec.get('satisfaction', 0)}%",
                        "success_rate": f"{rec.get('success_rate', 0)}%"
                    }
                },
                "created_at": rec.get('created_at', datetime.now())
            }
            
            return formatted_response
        else:
            await conn.close()
            return {"error": "No recommendations found for this template"}
            
    except Exception as e:
        logger.error(f"Error getting formatted recommendations: {e}")
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/extract-keywords/{template_id}")
async def get_extracted_keywords(template_id: str):
    """Get extracted keywords for a specific template"""
    try:
        logger.info(f"Getting keywords for template: {template_id}")
        
        conn = await feature_extractor.connect_db()
        
        # Get keywords from database
        keywords_result = await conn.fetchrow('''
            SELECT keywords_json, created_at, template_source
            FROM extracted_keywords
            WHERE template_id = $1 AND keywords_json IS NOT NULL
            ORDER BY created_at DESC
            LIMIT 1
        ''', template_id)
        
        await conn.close()
        
        if not keywords_result:
            raise HTTPException(status_code=404, detail="No keywords found for this template")
        
        keywords = json.loads(keywords_result['keywords_json']) if keywords_result['keywords_json'] else []
        
        return {
            "template_id": template_id,
            "keywords": keywords,
            "count": len(keywords),
            "created_at": keywords_result['created_at'],
            "template_source": keywords_result['template_source']
        }
        
    except Exception as e:
        logger.error(f"Error getting keywords: {e}")
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/extract-keywords/{template_id}")
async def extract_keywords_for_template(template_id: str):
    """Extract keywords for a specific template"""
    try:
        logger.info(f"Extracting keywords for template: {template_id}")
        
        # Get template data from database
        template_data = await feature_extractor.get_template_data(template_id)
        
        if not template_data:
            raise HTTPException(status_code=404, detail="Template not found")
        
        # Extract keywords using local NLP
        keywords = await feature_extractor.extract_keywords_from_template(template_data)
        
        # Store keywords in database
        await feature_extractor.store_extracted_keywords(template_id, keywords)
        
        return {
            "template_id": template_id,
            "keywords": keywords,
            "count": len(keywords)
        }
        
    except Exception as e:
        logger.error(f"Error extracting keywords: {e}")
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/extract-keywords-all")
async def extract_keywords_for_all_templates():
    """Extract keywords for all templates"""
    try:
        logger.info("Extracting keywords for all templates")
        
        # Get all templates from database
        templates = await feature_extractor.get_all_templates()
        
        results = []
        for template in templates:
            try:
                # Extract keywords using Claude AI
                keywords = await feature_extractor.extract_keywords_from_template(template)
                
                # Store keywords in database
                await feature_extractor.store_extracted_keywords(template['id'], keywords)
                
                results.append({
                    "template_id": template['id'],
                    "title": template['title'],
                    "keywords": keywords,
                    "count": len(keywords)
                })
            except Exception as e:
                logger.error(f"Error extracting keywords for template {template['id']}: {e}")
                results.append({
                    "template_id": template['id'],
                    "title": template['title'],
                    "error": str(e)
                })
        
        return {
            "total_templates": len(templates),
            "processed": len(results),
            "results": results
        }
        
    except Exception as e:
        logger.error(f"Error in bulk keyword extraction: {e}")
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/auto-workflow/{template_id}")
async def trigger_automatic_workflow(template_id: str):
    """Trigger complete automatic workflow for a new template"""
    try:
        logger.info(f"🚀 Starting automatic workflow for template: {template_id}")
        
        # Step 1: Extract keywords
        logger.info("📝 Step 1: Extracting keywords...")
        template_data = await feature_extractor.get_template_data(template_id)
        
        if not template_data:
            raise HTTPException(status_code=404, detail="Template not found")
        
        keywords = await feature_extractor.extract_keywords_from_template(template_data)
        await feature_extractor.store_extracted_keywords(template_id, keywords)
        logger.info(f"✅ Keywords extracted and stored: {len(keywords)} keywords")
        
        # Step 2: Generate tech stack recommendation
        logger.info("🤖 Step 2: Generating tech stack recommendation...")
        try:
            recommendation_data = await claude_client.get_recommendation(template_id)
            logger.info(f"✅ Tech stack recommendation generated: {recommendation_data.get('stack_name', 'Unknown')}")
        except Exception as e:
            logger.warning(f"⚠️ Claude AI failed (likely billing issue): {e}")
            logger.info("🔄 Using database fallback for recommendation...")
            
            # Check if recommendation already exists in database
            conn = await claude_client.connect_db()
            existing_rec = await conn.fetchrow('''
                SELECT * FROM tech_stack_recommendations
                WHERE template_id = $1
                ORDER BY created_at DESC LIMIT 1
            ''', template_id)
            
            if existing_rec:
                recommendation_data = dict(existing_rec)
                logger.info(f"✅ Found existing recommendation: {recommendation_data.get('stack_name', 'Unknown')}")
            else:
                # Create a basic recommendation
                recommendation_data = {
                    'stack_name': f'{template_data.get("title", "Template")} Tech Stack',
                    'monthly_cost': 100.0,
                    'setup_cost': 2000.0,
                    'team_size': '3-5',
                    'development_time': 6,
                    'satisfaction': 85,
                    'success_rate': 90,
                    'frontend': 'React.js',
                    'backend': 'Node.js',
                    'database': 'PostgreSQL',
                    'cloud': 'AWS',
                    'testing': 'Jest',
                    'mobile': 'React Native',
                    'devops': 'Docker',
                    'ai_ml': 'TensorFlow',
                    'recommended_tool': 'Custom Tool',
                    'recommendation_score': 85.0
                }
                logger.info(f"✅ Created basic recommendation: {recommendation_data.get('stack_name', 'Unknown')}")
            
            await conn.close()
        
        # Step 3: Auto-migrate to Neo4j
        logger.info("🔄 Step 3: Auto-migrating to Neo4j knowledge graph...")
        await claude_client.auto_migrate_single_recommendation(template_id)
        logger.info("✅ Auto-migration to Neo4j completed")
        
        return {
            "template_id": template_id,
            "workflow_status": "completed",
            "steps_completed": [
                "keyword_extraction",
                "tech_stack_recommendation", 
                "neo4j_migration"
            ],
            "keywords_count": len(keywords),
            "stack_name": recommendation_data.get('stack_name', 'Unknown'),
            "message": "Complete workflow executed successfully"
        }
        
    except Exception as e:
        logger.error(f"Error in automatic workflow for template {template_id}: {e}")
        raise HTTPException(status_code=500, detail=f"Workflow failed: {str(e)}")

@app.post("/auto-workflow-batch")
async def trigger_automatic_workflow_batch():
    """Trigger automatic workflow for all templates without recommendations"""
    try:
        logger.info("🚀 Starting batch automatic workflow for all templates")
        
        # Get all templates without recommendations
        conn = await claude_client.connect_db()
        
        templates_query = """
            SELECT t.id, t.title, t.description, t.category, t.type
            FROM templates t
            LEFT JOIN tech_stack_recommendations tsr ON t.id = tsr.template_id
            WHERE tsr.template_id IS NULL
            AND t.type NOT LIKE '_%'
            UNION
            SELECT ct.id, ct.title, ct.description, ct.category, ct.type
            FROM custom_templates ct
            LEFT JOIN tech_stack_recommendations tsr ON ct.id = tsr.template_id
            WHERE tsr.template_id IS NULL
            AND ct.type NOT LIKE '_%'
        """
        
        templates = await conn.fetch(templates_query)
        await conn.close()
        
        logger.info(f"📋 Found {len(templates)} templates without recommendations")
        
        results = []
        for i, template in enumerate(templates, 1):
            try:
                logger.info(f"🔄 Processing {i}/{len(templates)}: {template['title']}")
                
                # Trigger workflow for this template
                workflow_result = await trigger_automatic_workflow(template['id'])
                
                results.append({
                    "template_id": template['id'],
                    "title": template['title'],
                    "status": "success",
                    "workflow_result": workflow_result
                })
                
            except Exception as e:
                logger.error(f"Error processing template {template['id']}: {e}")
                results.append({
                    "template_id": template['id'],
                    "title": template['title'],
                    "status": "failed",
                    "error": str(e)
                })
        
        success_count = len([r for r in results if r['status'] == 'success'])
        failed_count = len([r for r in results if r['status'] == 'failed'])
        
        return {
            "message": f"Batch workflow completed: {success_count} success, {failed_count} failed",
            "total_templates": len(templates),
            "success_count": success_count,
            "failed_count": failed_count,
            "results": results
        }
        
    except Exception as e:
        logger.error(f"Error in batch automatic workflow: {e}")
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/ai/recommendations")
async def get_tech_recommendations(request: TechRecommendationRequest):
    """Get tech stack recommendations for a template"""
    try:
        logger.info(f"Getting recommendations for template: {request.template_id}")
        
        # 1. FIRST: Check Neo4j knowledge graph for recommendations
        logger.info("🔍 Checking Neo4j knowledge graph for recommendations...")
        neo4j_recommendation = await neo4j_client.get_recommendations_from_neo4j(request.template_id)
        
        if neo4j_recommendation:
            logger.info(f"✅ Found recommendations in Neo4j: {neo4j_recommendation['stack_name']}")
            
            # Format the response from Neo4j data
            response = TechRecommendationResponse(
                template_id=request.template_id,
                stack_name=neo4j_recommendation.get('stack_name', 'Tech Stack'),
                monthly_cost=float(neo4j_recommendation.get('monthly_cost', 0.0)),
                setup_cost=float(neo4j_recommendation.get('setup_cost', 0.0)),
                team_size=neo4j_recommendation.get('team_size', '1-2'),
                development_time=neo4j_recommendation.get('development_time', 1),
                satisfaction=neo4j_recommendation.get('satisfaction', 0),
                success_rate=neo4j_recommendation.get('success_rate', 0),
                frontend=neo4j_recommendation.get('frontend', ''),
                backend=neo4j_recommendation.get('backend', ''),
                database=neo4j_recommendation.get('database', ''),
                cloud=neo4j_recommendation.get('cloud', ''),
                testing=neo4j_recommendation.get('testing', ''),
                mobile=neo4j_recommendation.get('mobile', ''),
                devops=neo4j_recommendation.get('devops', ''),
                ai_ml=neo4j_recommendation.get('ai_ml', ''),
                recommended_tool=neo4j_recommendation.get('recommended_tool', ''),
                recommendation_score=float(neo4j_recommendation.get('recommendation_score', 0.0)),
                created_at=datetime.now()
            )
            
            # Log the complete tech stack with tool for visibility
            logger.info(f"📋 Complete Tech Stack Recommendation:")
            logger.info(f"   🎯 Stack: {response.stack_name}")
            logger.info(f"   💻 Frontend: {response.frontend}")
            logger.info(f"   ⚙️ Backend: {response.backend}")
            logger.info(f"   🗄️ Database: {response.database}")
            logger.info(f"   ☁️ Cloud: {response.cloud}")
            logger.info(f"   🧪 Testing: {response.testing}")
            logger.info(f"   📱 Mobile: {response.mobile}")
            logger.info(f"   🚀 DevOps: {response.devops}")
            logger.info(f"   🤖 AI/ML: {response.ai_ml}")
            logger.info(f"   🔧 Recommended Tool: {response.recommended_tool}")
            logger.info(f"   ⭐ Score: {response.recommendation_score}")
            
            # Return in the requested format with recommendations array
            return {
                "recommendations": [
                    {
                        "template_id": response.template_id,
                        "stack_name": response.stack_name,
                        "monthly_cost": response.monthly_cost,
                        "setup_cost": response.setup_cost,
                        "team_size": response.team_size,
                        "development_time": response.development_time,
                        "satisfaction": response.satisfaction,
                        "success_rate": response.success_rate,
                        "frontend": response.frontend,
                        "backend": response.backend,
                        "database": response.database,
                        "cloud": response.cloud,
                        "testing": response.testing,
                        "mobile": response.mobile,
                        "devops": response.devops,
                        "ai_ml": response.ai_ml,
                        "recommendation_score": response.recommendation_score
                    }
                ]
            }
        else:
            # 2. SECOND: Check database as fallback
            logger.info("🔍 Neo4j not found, checking database as fallback...")
            conn = await claude_client.connect_db()
            
            recommendations = await conn.fetch('''
                SELECT template_id, stack_name, monthly_cost, setup_cost, team_size, 
                       development_time, satisfaction, success_rate, frontend, backend, 
                       database, cloud, testing, mobile, devops, ai_ml, recommended_tool,
                       recommendation_score, created_at, updated_at
                FROM tech_stack_recommendations 
                WHERE template_id = $1
                ORDER BY created_at DESC
                LIMIT 1
            ''', request.template_id)
            
            if recommendations:
                rec = dict(recommendations[0])
                logger.info(f"✅ Found recommendations in database: {rec.get('stack_name', 'Unknown')}")
                
                # Auto-migrate to Neo4j when found in database
                try:
                    logger.info("🔄 Auto-migrating database recommendation to Neo4j...")
                    await claude_client.auto_migrate_single_recommendation(request.template_id)
                except Exception as e:
                    logger.warning(f"Auto-migration failed for template {request.template_id}: {e}")
                
                await conn.close()
                
                # Format the response from database
                response = TechRecommendationResponse(
                    template_id=request.template_id,
                    stack_name=rec.get('stack_name', 'Tech Stack'),
                    monthly_cost=float(rec.get('monthly_cost', 0.0)),
                    setup_cost=float(rec.get('setup_cost', 0.0)),
                    team_size=rec.get('team_size', '1-2'),
                    development_time=rec.get('development_time', 1),
                    satisfaction=rec.get('satisfaction', 0),
                    success_rate=rec.get('success_rate', 0),
                    frontend=rec.get('frontend', ''),
                    backend=rec.get('backend', ''),
                    database=rec.get('database', ''),
                    cloud=rec.get('cloud', ''),
                    testing=rec.get('testing', ''),
                    mobile=rec.get('mobile', ''),
                    devops=rec.get('devops', ''),
                    ai_ml=rec.get('ai_ml', ''),
                    recommended_tool=rec.get('recommended_tool', ''),
                    recommendation_score=float(rec.get('recommendation_score', 0.0)),
                    created_at=datetime.now()
                )
                
                # Log the complete tech stack with tool for visibility
                logger.info(f"📋 Complete Tech Stack Recommendation (from database):")
                logger.info(f"   🎯 Stack: {response.stack_name}")
                logger.info(f"   💻 Frontend: {response.frontend}")
                logger.info(f"   ⚙️ Backend: {response.backend}")
                logger.info(f"   🗄️ Database: {response.database}")
                logger.info(f"   ☁️ Cloud: {response.cloud}")
                logger.info(f"   🧪 Testing: {response.testing}")
                logger.info(f"   📱 Mobile: {response.mobile}")
                logger.info(f"   🚀 DevOps: {response.devops}")
                logger.info(f"   🤖 AI/ML: {response.ai_ml}")
                logger.info(f"   🔧 Recommended Tool: {response.recommended_tool}")
                logger.info(f"   ⭐ Score: {response.recommendation_score}")
                
                # Return in the requested format with recommendations array
                return {
                    "recommendations": [
                        {
                            "template_id": response.template_id,
                            "stack_name": response.stack_name,
                            "monthly_cost": response.monthly_cost,
                            "setup_cost": response.setup_cost,
                            "team_size": response.team_size,
                            "development_time": response.development_time,
                            "satisfaction": response.satisfaction,
                            "success_rate": response.success_rate,
                            "frontend": response.frontend,
                            "backend": response.backend,
                            "database": response.database,
                            "cloud": response.cloud,
                            "testing": response.testing,
                            "mobile": response.mobile,
                            "devops": response.devops,
                            "ai_ml": response.ai_ml,
                            "recommendation_score": response.recommendation_score
                        }
                    ]
                }
            else:
                # 3. THIRD: Generate new recommendations using Claude AI
                logger.info("🔍 No existing recommendations found, generating new ones with Claude AI...")
                await conn.close()
                response_data = await claude_client.get_recommendation(request.template_id)
            
            # Get keywords
            conn = await claude_client.connect_db()
            keywords_result = await conn.fetchrow('''
                SELECT keywords_json FROM extracted_keywords 
                WHERE template_id = $1 AND keywords_json IS NOT NULL
                ORDER BY template_source
                LIMIT 1
            ''', request.template_id)
            
            keywords = []
            if keywords_result:
                keywords = json.loads(keywords_result['keywords_json'])
            
            await conn.close()
            
            # Return in the requested format with recommendations array
            return {
                "recommendations": [
                    {
                        "template_id": request.template_id,
                        "stack_name": response_data.get('stack_name', 'Tech Stack'),
                        "monthly_cost": float(response_data.get('monthly_cost', 0.0)),
                        "setup_cost": float(response_data.get('setup_cost', 0.0)),
                        "team_size": response_data.get('team_size', '1-2'),
                        "development_time": response_data.get('development_time', 1),
                        "satisfaction": response_data.get('satisfaction', 0),
                        "success_rate": response_data.get('success_rate', 0),
                        "frontend": response_data.get('frontend', ''),
                        "backend": response_data.get('backend', ''),
                        "database": response_data.get('database', ''),
                        "cloud": response_data.get('cloud', ''),
                        "testing": response_data.get('testing', ''),
                        "mobile": response_data.get('mobile', ''),
                        "devops": response_data.get('devops', ''),
                        "ai_ml": response_data.get('ai_ml', ''),
                        "recommendation_score": float(response_data.get('recommendation_score', 0.0))
                    }
                ]
            }
            
    except Exception as e:
        logger.error(f"Error getting recommendations: {e}")
        raise HTTPException(status_code=500, detail=str(e))

# ============================================================================
# MIGRATION FUNCTIONALITY
# ============================================================================

async def migrate_to_neo4j():
    """Migrate tech stack recommendations to Neo4j knowledge graph"""
    print("🚀 Migrating Tech Stack Recommendations to Neo4j Knowledge Graph")
    print("=" * 70)
    
    try:
        # Test Neo4j connection
        if not await neo4j_client.test_connection():
            print("❌ Neo4j connection failed")
            return
        
        # Create constraints
        await neo4j_client.create_constraints()
        print("✅ Neo4j constraints created")
        
        # Connect to PostgreSQL
        conn = await claude_client.connect_db()
        print("✅ PostgreSQL connected")
        
        # Get templates with recommendations
        templates_query = """
            SELECT DISTINCT t.id, t.title, t.description, t.category, t.type, t.created_at
            FROM templates t
            JOIN tech_stack_recommendations tsr ON t.id = tsr.template_id
            ORDER BY t.created_at DESC
        """
        templates = await conn.fetch(templates_query)
        print(f"📋 Found {len(templates)} templates to migrate")
        
        for i, template in enumerate(templates, 1):
            print(f"\n📝 Processing {i}/{len(templates)}: {template['title']}")
            
            # Get recommendation
            rec_query = """
                SELECT * FROM tech_stack_recommendations 
                WHERE template_id = $1
                ORDER BY created_at DESC LIMIT 1
            """
            rec = await conn.fetchrow(rec_query, template['id'])
            
            if not rec:
                print("   ⚠️ No recommendations found for this template")
                continue
            
            print(f"   🔍 Found recommendation: {rec['stack_name']}")
            
            # Get keywords for this template
            keywords_query = """
                SELECT keywords_json FROM extracted_keywords 
                WHERE template_id = $1 AND template_source = 'templates'
                ORDER BY created_at DESC LIMIT 1
            """
            keywords_result = await conn.fetchrow(keywords_query, template['id'])
            keywords = []
            if keywords_result and keywords_result['keywords_json']:
                keywords_data = keywords_result['keywords_json']
                # Parse JSON if it's a string
                if isinstance(keywords_data, str):
                    try:
                        import json
                        keywords = json.loads(keywords_data)
                    except:
                        keywords = []
                elif isinstance(keywords_data, list):
                    keywords = keywords_data
            print(f"   🔑 Found {len(keywords)} keywords")
            
            # Create template node in Neo4j
            template_data = dict(template)
            template_data['id'] = str(template_data['id'])
            await neo4j_client.create_template_node(template_data)
            
            # Create tech stack node
            tech_stack_data = {
                "name": rec['stack_name'],
                "category": "tech_stack",
                "maturity_score": 0.9,
                "learning_curve": "medium",
                "performance_rating": float(rec['recommendation_score']) / 100.0
            }
            await neo4j_client.create_technology_node(tech_stack_data)
            
            # Create recommendation relationship
            await neo4j_client.create_recommendation_relationship(
                str(template['id']),
                rec['stack_name'],
                "tech_stack",
                float(rec['recommendation_score']) / 100.0
            )
            
            # Create individual technology nodes and relationships
            tech_fields = ['frontend', 'backend', 'database', 'cloud', 'testing', 'mobile', 'devops', 'ai_ml']
            
            for field in tech_fields:
                tech_value = rec[field]
                if tech_value and tech_value.strip():
                    # Parse JSON if it's a string
                    if isinstance(tech_value, str) and tech_value.startswith('{'):
                        try:
                            tech_value = json.loads(tech_value)
                            if isinstance(tech_value, dict):
                                tech_name = tech_value.get('name', str(tech_value))
                            else:
                                tech_name = str(tech_value)
                        except:
                            tech_name = str(tech_value)
                    else:
                        tech_name = str(tech_value)
                    
                    # Create technology node
                    tech_data = {
                        "name": tech_name,
                        "category": field,
                        "maturity_score": 0.8,
                        "learning_curve": "medium",
                        "performance_rating": 0.8
                    }
                    await neo4j_client.create_technology_node(tech_data)
                    
                    # Create relationship
                    await neo4j_client.create_recommendation_relationship(
                        str(template['id']),
                        tech_name,
                        field,
                        0.8
                    )
            
            # Create tool node for single recommended tool
            recommended_tool = rec.get('recommended_tool', '')
            if recommended_tool and recommended_tool.strip():
                # Create tool node
                tool_data = {
                    "name": recommended_tool,
                    "category": "business_tool",
                    "type": "Tool",
                    "maturity_score": 0.8,
                    "learning_curve": "easy",
                    "performance_rating": 0.8
                }
                await neo4j_client.create_technology_node(tool_data)
                
                # Create relationship
                await neo4j_client.create_recommendation_relationship(
                    str(template['id']),
                    recommended_tool,
                    "business_tool",
                    0.8
                )
                print(f"   🔧 Created tool: {recommended_tool}")
            
            # Create keyword relationships
            if isinstance(keywords, list):
                print(f"   🔑 Processing {len(keywords)} keywords: {keywords[:3]}...")
                for keyword in keywords:
                    if keyword and keyword.strip():
                        await neo4j_client.create_keyword_relationship(str(template['id']), keyword)
            else:
                print(f"   ⚠️ Keywords not in expected list format: {type(keywords)}")
            
            # Create TemplateRecommendation node with rich data
            recommendation_data = {
                'stack_name': rec['stack_name'],
                'description': template.get('description', ''),
                'project_scale': 'medium',
                'team_size': 3,
                'experience_level': 'intermediate',
                'confidence_score': int(rec['recommendation_score']),
                'recommendation_reasons': [
                    f"Tech stack: {rec['stack_name']}",
                    f"Score: {rec['recommendation_score']}/100",
                    "AI-generated recommendation"
                ],
                'key_features': [
                    f"Frontend: {rec.get('frontend', 'N/A')}",
                    f"Backend: {rec.get('backend', 'N/A')}",
                    f"Database: {rec.get('database', 'N/A')}",
                    f"Cloud: {rec.get('cloud', 'N/A')}"
                ],
                'estimated_development_time_months': rec.get('development_time', 3),
                'complexity_level': 'medium',
                'budget_range_usd': f"${rec.get('monthly_cost', 0):.0f} - ${rec.get('setup_cost', 0):.0f}",
                'time_to_market_weeks': rec.get('development_time', 3) * 4,
                'scalability_requirements': 'moderate',
                'security_requirements': 'standard',
                'success_rate_percentage': rec.get('success_rate', 85),
                'user_satisfaction_score': rec.get('satisfaction', 85)
            }
            await neo4j_client.create_template_recommendation_node(str(template['id']), recommendation_data)
            print(f"   📋 Created TemplateRecommendation node")
            
            print(f"   ✅ Successfully migrated to Neo4j")
        
        await conn.close()
        await neo4j_client.close()
        
        print("\n🎉 MIGRATION COMPLETED!")
        print(f"📊 Successfully migrated: {len(templates)} templates")
        print("🔗 Neo4j knowledge graph created with tech stack relationships")
        
    except Exception as e:
        print(f"❌ Migration failed: {e}")

# ============================================================================
# MAIN EXECUTION
# ============================================================================

if __name__ == "__main__":
    import sys
    
    if len(sys.argv) > 1 and sys.argv[1] == "migrate":
        # Run migration
        asyncio.run(migrate_to_neo4j())
    elif len(sys.argv) > 2 and sys.argv[1] == "--template-id":
        # Generate recommendations for specific template
        template_id = sys.argv[2]
        
        # Configure logger to output to stderr for command line usage
        import logging
        logging.basicConfig(level=logging.ERROR, stream=sys.stderr)
        
        async def get_recommendation():
            try:
                claude_client = ClaudeClient()
                result = await claude_client.get_recommendation(template_id)
                # Only output JSON to stdout
                print(json.dumps(result, default=str))
            except Exception as e:
                error_result = {
                    "error": str(e),
                    "template_id": template_id
                }
                print(json.dumps(error_result))
        
        asyncio.run(get_recommendation())
    else:
        # Start FastAPI server
        uvicorn.run(
            app,
            host="0.0.0.0",
            port=8013,
            log_level="info"
        )
