# ENHANCED TECH STACK SELECTOR - INTEGRATED VERSION WITH POSTGRESQL
# Combines FastAPI, Neo4j, PostgreSQL migration, and all endpoints into one file

import os
import sys
import json
from datetime import datetime
from typing import Dict, Any, Optional, List
from pydantic import BaseModel
from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from loguru import logger
import atexit
import anthropic

# Neo4j imports
from neo4j import GraphDatabase

# PostgreSQL imports
try:
    import psycopg2
    from psycopg2.extras import RealDictCursor
    POSTGRES_AVAILABLE = True
except ImportError:
    POSTGRES_AVAILABLE = False

# ================================================================================================
# NEO4J SERVICE CLASS
# ================================================================================================

class Neo4jService:
    def __init__(self, uri, user, password):
        self.driver = GraphDatabase.driver(
            uri,
            auth=(user, password),
            connection_timeout=5
        )
        try:
            self.driver.verify_connectivity()
        except Exception:
            pass
    
    def close(self):
        self.driver.close()

    def run_query(self, query: str, parameters: Optional[Dict[str, Any]] = None):
        with self.driver.session() as session:
            result = session.run(query, parameters or {})
            return [record.data() for record in result]

    def get_best_stack(self, domain: Optional[str], budget: Optional[int], preferred: Optional[List[str]]):
        """Return top recommended tech stacks based on domain, budget, and preferred technologies."""
        query = """
        MATCH (s:TechStack)
        WHERE ($domain IS NULL OR toLower(s.name) CONTAINS toLower($domain) OR 
               toLower(s.name) CONTAINS toLower(replace($domain, 'ecommerce', 'e-commerce')) OR
               EXISTS { MATCH (s)-[:SUITABLE_FOR]->(d:Domain) WHERE toLower(d.name) CONTAINS toLower($domain) })
        AND ($budget IS NULL OR s.monthly_cost <= $budget)
        WITH s, $preferred AS pref
        OPTIONAL MATCH (s)-[:USES_FRONTEND]->(frontend:Technology)
        OPTIONAL MATCH (s)-[:USES_BACKEND]->(backend:Technology)
        OPTIONAL MATCH (s)-[:USES_DATABASE]->(database:Technology)
        OPTIONAL MATCH (s)-[:USES_CLOUD]->(cloud:Technology)
        OPTIONAL MATCH (s)-[:USES_TESTING]->(testing:Technology)
        OPTIONAL MATCH (s)-[:USES_MOBILE]->(mobile:Technology)
        OPTIONAL MATCH (s)-[:USES_DEVOPS]->(devops:Technology)
        OPTIONAL MATCH (s)-[:USES_AI_ML]->(ai_ml:Technology)
        WITH s, frontend, backend, database, cloud, testing, mobile, devops, ai_ml, pref,
             (s.satisfaction_score * 0.4 + s.success_rate * 0.3 + 
              CASE WHEN $budget IS NOT NULL THEN (100 - (s.monthly_cost / $budget * 100)) * 0.3 ELSE 30 END) AS base_score
        WITH s, frontend, backend, database, cloud, testing, mobile, devops, ai_ml, base_score,
             CASE WHEN pref IS NOT NULL THEN 
                 size([x IN pref WHERE 
                     toLower(x) IN [toLower(frontend.name), toLower(backend.name), toLower(database.name), 
                                   toLower(cloud.name), toLower(testing.name), toLower(mobile.name), 
                                   toLower(devops.name), toLower(ai_ml.name)]]) * 5
             ELSE 0 END AS preference_bonus
        RETURN s.name AS stack_name,
               s.monthly_cost AS monthly_cost,
               s.setup_cost AS setup_cost,
               s.team_size_range AS team_size,
               s.development_time_months AS development_time,
               s.satisfaction_score AS satisfaction,
               s.success_rate AS success_rate,
               CASE WHEN frontend IS NOT NULL THEN frontend.name ELSE 'React' END AS frontend,
               CASE WHEN backend IS NOT NULL THEN backend.name ELSE 'Node.js' END AS backend,
               CASE WHEN database IS NOT NULL THEN database.name ELSE 'PostgreSQL' END AS database,
               CASE WHEN cloud IS NOT NULL THEN cloud.name ELSE 'DigitalOcean' END AS cloud,
               CASE WHEN testing IS NOT NULL THEN testing.name ELSE 'Jest' END AS testing,
               CASE WHEN mobile IS NOT NULL THEN mobile.name ELSE 'React Native' END AS mobile,
               CASE WHEN devops IS NOT NULL THEN devops.name ELSE 'GitHub Actions' END AS devops,
               CASE WHEN ai_ml IS NOT NULL THEN ai_ml.name ELSE 'Hugging Face' END AS ai_ml,
               base_score + preference_bonus AS recommendation_score
        ORDER BY recommendation_score DESC, s.monthly_cost ASC
        LIMIT 5
        """
        return self.run_query(query, {"domain": domain, "budget": budget, "preferred": preferred})

    def get_price_performance(self):
        query = """
        MATCH (t:Technology)
        RETURN t.name AS technology,
               coalesce(t.performance_rating,0) AS performance,
               coalesce(t.maturity_score,0) AS maturity,
               coalesce(t.performance_rating,0) * 10 AS estimated_monthly_cost,
               round((coalesce(t.performance_rating,0) * 1.0) / (CASE WHEN coalesce(t.performance_rating,0) = 0 THEN 1 ELSE 10 END),2) AS price_performance_index
        ORDER BY performance DESC, maturity DESC
        LIMIT 10
        """
        return self.run_query(query, {})

    # === Added: Queries from user spec ===
    def get_technology_ecosystem(self):
        query = """
        MATCH (t1:Technology)-[r:COMPATIBLE_WITH|OPTIMIZED_FOR]-(t2:Technology)
        RETURN t1.name as tech1,
               t1.category as category1,
               type(r) as relationship,
               t2.name as tech2,
               t2.category as category2,
               r.score as compatibility_score,
               r.reason as reason
        ORDER BY compatibility_score DESC
        """
        return self.run_query(query, {})

    def get_stack_trends(self):
        query = """
        MATCH (s:TechStack)-[:SUITABLE_FOR]->(d:Domain)
        WITH d.name as domain,
             collect(s) as stacks,
             avg(s.satisfaction_score) as avg_satisfaction,
             avg(s.monthly_cost) as avg_cost
        UNWIND stacks as stack
        MATCH (stack)-[:USES_FRONTEND|USES_BACKEND|USES_DATABASE|USES_CLOUD]->(t:Technology)
        RETURN domain,
               avg_satisfaction,
               avg_cost,
               collect(DISTINCT t.name) as popular_technologies,
               count(DISTINCT stack) as stack_variations
        ORDER BY avg_satisfaction DESC
        """
        return self.run_query(query, {})

    def validate_relationships(self):
        query = """
        MATCH (s:TechStack)-[r]->(n)
        RETURN type(r) as relationship_type,
               labels(n) as target_labels,
               count(*) as relationship_count
        ORDER BY relationship_count DESC
        """
        return self.run_query(query, {})

    def validate_data_completeness(self):
        query = """
        MATCH (s:TechStack)
        RETURN s.name AS name,
               exists((s)-[:BELONGS_TO_TIER]->()) as has_price_tier,
               exists((s)-[:USES_FRONTEND]->()) as has_frontend,
               exists((s)-[:USES_BACKEND]->()) as has_backend,
               exists((s)-[:USES_DATABASE]->()) as has_database,
               exists((s)-[:USES_CLOUD]->()) as has_cloud
        """
        return self.run_query(query, {})

    def validate_price_consistency(self):
        # Get inconsistencies (stacks with costs outside their tier range)
        inconsistencies_query = """
        MATCH (s:TechStack)-[:BELONGS_TO_TIER]->(p:PriceTier)
        WHERE NOT (s.monthly_cost >= p.min_price AND s.monthly_cost <= p.max_price)
        RETURN s.name AS stack,
               s.monthly_cost AS monthly_cost,
               p.name AS price_tier,
               p.min_price AS min_price,
               p.max_price AS max_price
        """
        inconsistencies = self.run_query(inconsistencies_query, {})
        
        # Get summary statistics
        summary_query = """
        MATCH (s:TechStack)-[:BELONGS_TO_TIER]->(p:PriceTier)
        RETURN count(s) AS total_stacks,
               count(CASE WHEN s.monthly_cost >= p.min_price AND s.monthly_cost <= p.max_price THEN 1 END) AS consistent_stacks,
               count(CASE WHEN NOT (s.monthly_cost >= p.min_price AND s.monthly_cost <= p.max_price) THEN 1 END) AS inconsistent_stacks
        """
        summary = self.run_query(summary_query, {})
        
        # Get all stacks with their price tier info for reference
        all_stacks_query = """
        MATCH (s:TechStack)-[:BELONGS_TO_TIER]->(p:PriceTier)
        RETURN s.name AS stack,
               s.monthly_cost AS monthly_cost,
               p.name AS price_tier,
               p.min_price AS min_price,
               p.max_price AS max_price,
               CASE WHEN s.monthly_cost >= p.min_price AND s.monthly_cost <= p.max_price THEN 'consistent' ELSE 'inconsistent' END AS status
        ORDER BY s.monthly_cost
        """
        all_stacks = self.run_query(all_stacks_query, {})
        
        return {
            "summary": summary[0] if summary else {"total_stacks": 0, "consistent_stacks": 0, "inconsistent_stacks": 0},
            "inconsistencies": inconsistencies,
            "all_stacks": all_stacks,
            "validation_passed": len(inconsistencies) == 0
        }

    def export_stacks_with_pricing(self):
        query = """
        MATCH (s:TechStack)-[:BELONGS_TO_TIER]->(p:PriceTier)
        OPTIONAL MATCH (s)-[:USES_FRONTEND|USES_BACKEND|USES_DATABASE|USES_CLOUD]->(t:Technology)
        RETURN s.name as stack_name,
               s.monthly_cost as monthly_cost,
               s.setup_cost as setup_cost,
               s.team_size_range as team_size,
               s.development_time_months as development_time,
               s.satisfaction_score as satisfaction_score,
               s.success_rate as success_rate,
               p.name as price_tier,
               s.suitable_domains as domains,
               collect(DISTINCT {name: t.name, category: t.category, cost: t.monthly_cost}) as technologies
        """
        return self.run_query(query, {})

    def export_price_tiers(self):
        query = """
        MATCH (p:PriceTier)
        OPTIONAL MATCH (s:TechStack)-[:BELONGS_TO_TIER]->(p)
        RETURN p.name as tier_name,
               p.min_price as min_price,
               p.max_price as max_price,
               p.target_audience as audience,
               p.description as description,
               count(s) as stack_count,
               collect(s.name) as available_stacks
        """
        return self.run_query(query, {})

    def apply_cql_script(self, file_path: str) -> Dict[str, Any]:
        executed: int = 0
        failed: int = 0
        errors: List[Dict[str, str]] = []
        if not os.path.isfile(file_path):
            raise FileNotFoundError(f"CQL file not found: {file_path}")
        
        try:
            with open(file_path, "r", encoding="utf-8") as f:
                raw = f.read()
            
            # Strip line comments and build statements by semicolons
            lines = []
            for line in raw.splitlines():
                stripped = line.strip()
                # Skip empty lines and comments
                if not stripped or stripped.startswith("//") or stripped.startswith("--"):
                    continue
                lines.append(line)
            
            merged = "\n".join(lines)
            statements = [s.strip() for s in merged.split(";") if s.strip()]
            
            logger.info(f"📝 Processing {len(statements)} CQL statements from {file_path}")
            
            with self.driver.session() as session:
                for i, stmt in enumerate(statements):
                    try:
                        if stmt.strip():  # Only execute non-empty statements
                            session.run(stmt)
                            executed += 1
                            if executed % 10 == 0:  # Log progress every 10 statements
                                logger.info(f"✅ Executed {executed} statements...")
                    except Exception as e:
                        failed += 1
                        error_msg = str(e)
                        # Log the error but continue processing
                        logger.warning(f"⚠️ Statement {i+1} failed: {error_msg[:100]}...")
                        errors.append({
                            "statement_number": i + 1,
                            "statement": stmt[:120] + ("..." if len(stmt) > 120 else ""), 
                            "error": error_msg
                        })
                        
            logger.info(f"📊 CQL execution completed: {executed} successful, {failed} failed")
            return {"executed": executed, "failed": failed, "errors": errors}
            
        except Exception as e:
            logger.error(f"❌ Error reading or processing CQL file: {e}")
            return {"executed": 0, "failed": 1, "errors": [{"error": str(e)}]}

    def recommend_by_budget(self, budget: float, domain: Optional[str], limit: int = 5):
        """
        Recommend tech stacks based on EXACT budget constraint.
        
        Args:
            budget: User's exact budget amount (monthly cost)
            domain: Optional domain filter (e.g., 'E-commerce', 'SaaS')
            limit: Maximum number of results to return
        
        Returns:
            Tech stacks that cost <= budget, ordered by best value
        """
        query = """
        MATCH (s:TechStack)
        WHERE s.monthly_cost <= $budget
        OPTIONAL MATCH (s)-[:SUITABLE_FOR]->(d:Domain)
        WHERE ($domain IS NULL OR (d IS NOT NULL AND toLower(d.name) CONTAINS toLower($domain)))
        OPTIONAL MATCH (s)-[:USES_FRONTEND]->(frontend:Technology)
        OPTIONAL MATCH (s)-[:USES_BACKEND]->(backend:Technology)
        OPTIONAL MATCH (s)-[:USES_DATABASE]->(database:Technology)
        OPTIONAL MATCH (s)-[:USES_CLOUD]->(cloud:Technology)
        OPTIONAL MATCH (s)-[:USES_TESTING]->(testing:Technology)
        OPTIONAL MATCH (s)-[:USES_MOBILE]->(mobile:Technology)
        OPTIONAL MATCH (s)-[:USES_DEVOPS]->(devops:Technology)
        OPTIONAL MATCH (s)-[:USES_AI_ML]->(ai_ml:Technology)
        WITH s, frontend, backend, database, cloud, testing, mobile, devops, ai_ml,
             (s.satisfaction_score * 0.4 + s.success_rate * 0.3 + 
              (100 - (s.monthly_cost / $budget * 100)) * 0.3) AS recommendation_score
        RETURN s.name AS stack_name,
               s.monthly_cost AS monthly_cost,
               s.setup_cost AS setup_cost,
               s.team_size_range AS team_size,
               s.development_time_months AS development_time,
               s.satisfaction_score AS satisfaction,
               s.success_rate AS success_rate,
               COALESCE(frontend.name, 'React') AS frontend,
               COALESCE(backend.name, 'Node.js') AS backend,
               COALESCE(database.name, 'PostgreSQL') AS database,
               COALESCE(cloud.name, 'DigitalOcean') AS cloud,
               COALESCE(testing.name, 'Jest') AS testing,
               COALESCE(mobile.name, 'React Native') AS mobile,
               COALESCE(devops.name, 'GitHub Actions') AS devops,
               COALESCE(ai_ml.name, 'Hugging Face') AS ai_ml,
               recommendation_score,
               s.monthly_cost / $budget AS budget_utilization
        ORDER BY recommendation_score DESC, s.monthly_cost ASC
        LIMIT $limit
        """
        return self.run_query(query, {
            "budget": float(budget), 
            "domain": domain,
            "limit": limit
        })

    def recommend_by_cost_limits(self, monthly_cost: Optional[float], setup_cost: Optional[float], domain: Optional[str]):
        """Recommend stacks that do not exceed the given monthly and setup cost limits."""
        query = """
        MATCH (s:TechStack)-[:BELONGS_TO_TIER]->(p:PriceTier)
        OPTIONAL MATCH (s)-[:SUITABLE_FOR]->(d:Domain)
        WITH s, p, d
        WHERE ($domain IS NULL OR toLower(d.name) CONTAINS toLower($domain))
          AND ($monthly_cost IS NULL OR s.monthly_cost <= $monthly_cost)
          AND ($setup_cost IS NULL OR s.setup_cost <= $setup_cost)
        OPTIONAL MATCH (s)-[:USES_FRONTEND]->(frontend:Technology)
        OPTIONAL MATCH (s)-[:USES_BACKEND]->(backend:Technology)
        OPTIONAL MATCH (s)-[:USES_DATABASE]->(database:Technology)
        OPTIONAL MATCH (s)-[:USES_CLOUD]->(cloud:Technology)
        OPTIONAL MATCH (s)-[:USES_TESTING]->(testing:Technology)
        OPTIONAL MATCH (s)-[:USES_MOBILE]->(mobile:Technology)
        OPTIONAL MATCH (s)-[:USES_DEVOPS]->(devops:Technology)
        OPTIONAL MATCH (s)-[:USES_AI_ML]->(ai_ml:Technology)
        WITH s, frontend, backend, database, cloud, testing, mobile, devops, ai_ml,
             (s.satisfaction_score * 0.4 + s.success_rate * 0.3 + 30) AS recommendation_score
        RETURN s.name AS stack_name,
               s.monthly_cost AS monthly_cost,
               s.setup_cost AS setup_cost,
               s.team_size_range AS team_size,
               s.development_time_months AS development_time,
               s.satisfaction_score AS satisfaction,
               s.success_rate AS success_rate,
               COALESCE(frontend.name, 'React') AS frontend,
               COALESCE(backend.name, 'Node.js') AS backend,
               COALESCE(database.name, 'PostgreSQL') AS database,
               COALESCE(cloud.name, 'DigitalOcean') AS cloud,
               COALESCE(testing.name, 'Jest') AS testing,
               COALESCE(mobile.name, 'React Native') AS mobile,
               COALESCE(devops.name, 'GitHub Actions') AS devops,
               COALESCE(ai_ml.name, 'Hugging Face') AS ai_ml,
               recommendation_score
        ORDER BY s.monthly_cost ASC, recommendation_score DESC
        LIMIT 5
        """
        return self.run_query(query, {
            "monthly_cost": None if monthly_cost is None else float(monthly_cost),
            "setup_cost": None if setup_cost is None else float(setup_cost),
            "domain": domain
        })
    
    def get_technology(self, tech_id):
        with self.driver.session() as session:
            result = session.run(
                "MATCH (t:Technology {id: $tech_id}) RETURN t",
                tech_id=tech_id
            )
            return result.single()[0] if result else None
    
    def get_compatible_tech(self, tech_id):
        with self.driver.session() as session:
            result = session.run("""
                MATCH (t:Technology {id: $tech_id})-[r:COMPATIBLE_WITH]->(other:Technology)
                RETURN other, r.compatibility_score as score
                ORDER BY score DESC
                """,
                tech_id=tech_id
            )
            return [{"tech": record["other"], "score": record["score"]} for record in result]
    
    def get_tech_by_requirements(self, requirements):
        with self.driver.session() as session:
            # Convert requirements to a list of strings if it's a dict
            if isinstance(requirements, dict):
                req_list = []
                for key, value in requirements.items():
                    if isinstance(value, str):
                        req_list.append(value)
                    elif isinstance(value, list):
                        req_list.extend([str(v) for v in value])
                requirements = req_list
            elif not isinstance(requirements, list):
                requirements = [str(requirements)]
            
            result = session.run("""
                MATCH (t:Technology)
                WHERE ANY(req IN $requirements 
                         WHERE ANY(use_case IN t.primary_use_cases WHERE toLower(use_case) CONTAINS toLower(req))
                         OR any(strength IN t.strengths WHERE toLower(strength) CONTAINS toLower(req))
                         OR toLower(t.name) CONTAINS toLower(req)
                         OR toLower(t.category) CONTAINS toLower(req)
                         OR (req = 'web-application' AND ANY(use_case IN t.primary_use_cases WHERE toLower(use_case) CONTAINS 'web'))
                         OR (req = 'payment' AND ANY(use_case IN t.primary_use_cases WHERE toLower(use_case) CONTAINS 'application'))
                         OR (req = 'security' AND ANY(use_case IN t.primary_use_cases WHERE toLower(use_case) CONTAINS 'application'))
                         OR (req = 'reporting' AND ANY(use_case IN t.primary_use_cases WHERE toLower(use_case) CONTAINS 'application'))
                         OR (req = 'platform' AND ANY(use_case IN t.primary_use_cases WHERE toLower(use_case) CONTAINS 'application')))
                RETURN t
                ORDER BY t.maturity_score DESC
                LIMIT 10
                """,
                requirements=requirements
            )
            return [record["t"] for record in result]
    
    def create_compatibility_relationships(self):
        """Create compatibility relationships between technologies"""
        with self.driver.session() as session:
            # Create relationships between technologies based on compatibility
            result = session.run("""
                MATCH (t1:Technology), (t2:Technology)
                WHERE t1.id <> t2.id
                AND (
                    (t1.category = t2.category AND t1.type <> t2.type) OR
                    (t1.category = 'Frontend Framework' AND t2.category = 'Backend Framework') OR
                    (t1.category = 'Backend Framework' AND t2.category = 'Database') OR
                    (t1.category = 'Database' AND t2.category = 'Backend Framework')
                )
                MERGE (t1)-[r:COMPATIBLE_WITH {
                    compatibility_score: CASE 
                        WHEN t1.category = t2.category THEN 0.8
                        WHEN (t1.category = 'Frontend Framework' AND t2.category = 'Backend Framework') THEN 0.9
                        WHEN (t1.category = 'Backend Framework' AND t2.category = 'Database') THEN 0.9
                        ELSE 0.7
                    END,
                    integration_effort: CASE 
                        WHEN t1.category = t2.category THEN 'Low'
                        WHEN (t1.category = 'Frontend Framework' AND t2.category = 'Backend Framework') THEN 'Medium'
                        WHEN (t1.category = 'Backend Framework' AND t2.category = 'Database') THEN 'Low'
                        ELSE 'High'
                    END,
                    notes: 'Auto-generated compatibility relationship'
                }]->(t2)
                RETURN count(r) as relationships_created
            """)
            return result.single()["relationships_created"]
    
    def get_all_technologies_with_relationships(self):
        """Get all technologies with their relationships"""
        with self.driver.session() as session:
            result = session.run("""
                MATCH (t:Technology)
                OPTIONAL MATCH (t)-[r:COMPATIBLE_WITH]->(other:Technology)
                RETURN t, collect({
                    target: other,
                    relationship: r
                }) as relationships
            """)
            technologies = []
            for record in result:
                tech = record["t"]
                relationships = record["relationships"]
                technologies.append({
                    "technology": dict(tech),
                    "relationships": [rel for rel in relationships if rel["target"] is not None]
                })
            return technologies

# ================================================================================================
# POSTGRESQL MIGRATION SERVICE
# ================================================================================================

class PostgreSQLMigrationService:
    def __init__(self, 
                 host="localhost", 
                 port=5432,
                 user="pipeline_admin", 
                 password="secure_pipeline_2024", 
                 database="dev_pipeline"):
        self.config = {
            "host": host,
            "port": port,
            "user": user,
            "password": password,
            "database": database
        }
        self.connection = None
        self.cursor = None
        self.last_error: Optional[str] = None
    
    def is_open(self) -> bool:
        try:
            return (
                self.connection is not None and
                getattr(self.connection, "closed", 1) == 0 and
                self.cursor is not None and
                not getattr(self.cursor, "closed", True)
            )
        except Exception:
            return False
    
    def connect(self):
        if not POSTGRES_AVAILABLE:
            raise Exception("PostgreSQL connector (psycopg2) not available")
        
        try:
            # If already open, reuse
            if self.is_open():
                self.last_error = None
                return True
            # Attempt fresh connection
            self.connection = psycopg2.connect(**self.config)
            self.cursor = self.connection.cursor(cursor_factory=RealDictCursor)
            logger.info("Connected to PostgreSQL successfully")
            self.last_error = None
            return True
        except Exception as e:
            logger.error(f"Error connecting to PostgreSQL: {e}")
            self.last_error = str(e)
            return False
    
    def close(self):
        try:
            if self.cursor and not getattr(self.cursor, "closed", True):
                self.cursor.close()
        finally:
            self.cursor = None
        try:
            if self.connection and getattr(self.connection, "closed", 1) == 0:
                self.connection.close()
        finally:
            self.connection = None
    
    def create_tables_if_not_exist(self):
        """Create tables if they don't exist"""
        if not self.is_open():
            if not self.connect():
                return False
        
        try:
            create_technologies_table = """
            CREATE TABLE IF NOT EXISTS technologies (
                id SERIAL PRIMARY KEY,
                name VARCHAR(255) NOT NULL,
                category VARCHAR(100),
                type VARCHAR(100),
                maturity_score INTEGER DEFAULT 0,
                learning_curve VARCHAR(50),
                performance_rating INTEGER DEFAULT 0,
                community_size VARCHAR(50),
                cost_model VARCHAR(100),
                primary_use_cases TEXT,
                strengths TEXT[],
                weaknesses TEXT[],
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            );
            """
            
            create_compatibility_table = """
            CREATE TABLE IF NOT EXISTS tech_compatibility (
                id SERIAL PRIMARY KEY,
                tech_a_id INTEGER REFERENCES technologies(id),
                tech_b_id INTEGER REFERENCES technologies(id),
                compatibility_score DECIMAL(3,2) DEFAULT 0.0,
                integration_effort VARCHAR(50),
                notes TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            );
            """
            
            self.cursor.execute(create_technologies_table)
            self.cursor.execute(create_compatibility_table)
            self.connection.commit()
            
            logger.info("PostgreSQL tables created/verified successfully")
            return True
            
        except Exception as e:
            logger.error(f"Error creating PostgreSQL tables: {e}")
            self.last_error = str(e)
            return False
    
    def get_sample_data(self):
        """Insert some sample data if tables are empty"""
        try:
            # Check if data exists
            self.cursor.execute("SELECT COUNT(*) FROM technologies")
            count = self.cursor.fetchone()['count']
            
            if count == 0:
                sample_technologies = [
                    {
                        'name': 'React',
                        'category': 'Frontend Framework',
                        'type': 'Library',
                        'maturity_score': 9,
                        'learning_curve': 'Medium',
                        'performance_rating': 8,
                        'community_size': 'Very Large',
                        'cost_model': 'Open Source',
                        'primary_use_cases': 'Single Page Applications, Component-based UIs',
                        'strengths': ['Virtual DOM', 'Large ecosystem', 'Component reusability'],
                        'weaknesses': ['Learning curve', 'Rapid changes', 'SEO challenges']
                    },
                    {
                        'name': 'Node.js',
                        'category': 'Backend Runtime',
                        'type': 'Runtime Environment',
                        'maturity_score': 9,
                        'learning_curve': 'Medium',
                        'performance_rating': 8,
                        'community_size': 'Very Large',
                        'cost_model': 'Open Source',
                        'primary_use_cases': 'API development, Real-time applications, Microservices',
                        'strengths': ['JavaScript everywhere', 'NPM ecosystem', 'Non-blocking I/O'],
                        'weaknesses': ['Single-threaded', 'CPU-intensive tasks', 'Callback complexity']
                    },
                    {
                        'name': 'PostgreSQL',
                        'category': 'Database',
                        'type': 'Relational Database',
                        'maturity_score': 10,
                        'learning_curve': 'Medium',
                        'performance_rating': 9,
                        'community_size': 'Large',
                        'cost_model': 'Open Source',
                        'primary_use_cases': 'ACID transactions, Complex queries, Data integrity',
                        'strengths': ['ACID compliance', 'JSON support', 'Extensible', 'Full-text search'],
                        'weaknesses': ['Memory usage', 'Complexity for simple apps']
                    },
                    {
                        'name': 'FastAPI',
                        'category': 'Backend Framework',
                        'type': 'Web Framework',
                        'maturity_score': 8,
                        'learning_curve': 'Low',
                        'performance_rating': 9,
                        'community_size': 'Growing',
                        'cost_model': 'Open Source',
                        'primary_use_cases': 'REST APIs, GraphQL, Microservices',
                        'strengths': ['Fast performance', 'Automatic docs', 'Type hints', 'Async support'],
                        'weaknesses': ['Relatively new', 'Smaller ecosystem']
                    }
                ]
                
                for tech in sample_technologies:
                    insert_query = """
                    INSERT INTO technologies (name, category, type, maturity_score, learning_curve, 
                                            performance_rating, community_size, cost_model, 
                                            primary_use_cases, strengths, weaknesses)
                    VALUES (%(name)s, %(category)s, %(type)s, %(maturity_score)s, %(learning_curve)s,
                            %(performance_rating)s, %(community_size)s, %(cost_model)s,
                            %(primary_use_cases)s, %(strengths)s, %(weaknesses)s)
                    """
                    self.cursor.execute(insert_query, tech)
                
                self.connection.commit()
                logger.info(f"Inserted {len(sample_technologies)} sample technologies")
            
            return True
            
        except Exception as e:
            logger.error(f"Error inserting sample data: {e}")
            self.last_error = str(e)
            return False
    
    def migrate_to_neo4j(self, neo4j_service):
        if not self.is_open():
            if not self.connect():
                return []
        
        try:
            # Migrate technologies
            self.cursor.execute("SELECT * FROM technologies")
            technologies = self.cursor.fetchall()
            
            with neo4j_service.driver.session() as session:
                for tech in technologies:
                    # Convert RealDictRow to regular dict
                    tech_dict = dict(tech)
                    session.write_transaction(self._create_technology_node, tech_dict)
            
            logger.info(f"Migrated {len(technologies)} technologies to Neo4j")
            return True
            
        except Exception as e:
            logger.error(f"Error during migration: {e}")
            self.last_error = str(e)
            return False
    
    def _create_technology_node(self, tx, tech):
        tx.run("""
            CREATE (:Technology {
                id: $id,
                name: $name,
                category: $category,
                type: $type,
                maturity_score: $maturity_score,
                learning_curve: $learning_curve,
                performance_rating: $performance_rating,
                community_size: $community_size,
                cost_model: $cost_model,
                primary_use_cases: $primary_use_cases,
                strengths: $strengths,
                weaknesses: $weaknesses
            })
        """, **tech)
    
    def get_all_technologies(self):
        """Get all technologies from PostgreSQL"""
        if not self.connection:
            if not self.connect():
                return []
        
        try:
            self.cursor.execute("""
                SELECT id, name, category, type, maturity_score, learning_curve,
                       performance_rating, community_size, cost_model, 
                       primary_use_cases, strengths, weaknesses
                FROM technologies
                ORDER BY maturity_score DESC, name
            """)
            technologies = self.cursor.fetchall()
            return [dict(tech) for tech in technologies]
        except Exception as e:
            logger.error(f"Error fetching technologies: {e}")
            return []
    
    def get_tools_by_price_tier(self, price_tier_id: int):
        """Get tools filtered by price tier"""
        if not self.connection:
            if not self.connect():
                return []
        
        try:
            self.cursor.execute("""
                SELECT t.id, t.name, t.category, t.description, t.primary_use_cases,
                       t.popularity_score, t.monthly_cost_usd, t.setup_cost_usd,
                       t.license_cost_usd, t.training_cost_usd, t.total_cost_of_ownership_score,
                       t.price_performance_ratio, pt.tier_name
                FROM tools t
                LEFT JOIN price_tiers pt ON t.price_tier_id = pt.id
                WHERE t.price_tier_id = %s
                ORDER BY t.monthly_cost_usd ASC, t.popularity_score DESC
            """, (price_tier_id,))
            tools = self.cursor.fetchall()
            return [dict(tool) for tool in tools]
        except Exception as e:
            logger.error(f"Error fetching tools by price tier: {e}")
            return []
    
    def get_tools_within_budget(self, max_monthly_cost: float, max_setup_cost: float):
        """Get tools within specified budget constraints"""
        if not self.connection:
            if not self.connect():
                return []
        
        try:
            self.cursor.execute("""
                SELECT t.id, t.name, t.category, t.description, t.primary_use_cases,
                       t.popularity_score, t.monthly_cost_usd, t.setup_cost_usd,
                       t.license_cost_usd, t.training_cost_usd, t.total_cost_of_ownership_score,
                       t.price_performance_ratio, pt.tier_name
                FROM tools t
                LEFT JOIN price_tiers pt ON t.price_tier_id = pt.id
                WHERE t.monthly_cost_usd <= %s AND t.setup_cost_usd <= %s
                ORDER BY t.monthly_cost_usd ASC, t.total_cost_of_ownership_score DESC
            """, (max_monthly_cost, max_setup_cost))
            tools = self.cursor.fetchall()
            return [dict(tool) for tool in tools]
        except Exception as e:
            logger.error(f"Error fetching tools within budget: {e}")
            return []
    
    def get_tools_by_category(self, category: str):
        """Get tools by category with pricing information"""
        if not self.connection:
            if not self.connect():
                return []
        
        try:
            self.cursor.execute("""
                SELECT t.id, t.name, t.category, t.description, t.primary_use_cases,
                       t.popularity_score, t.monthly_cost_usd, t.setup_cost_usd,
                       t.license_cost_usd, t.training_cost_usd, t.total_cost_of_ownership_score,
                       t.price_performance_ratio, pt.tier_name
                FROM tools t
                LEFT JOIN price_tiers pt ON t.price_tier_id = pt.id
                WHERE t.category = %s
                ORDER BY t.monthly_cost_usd ASC, t.popularity_score DESC
            """, (category,))
            tools = self.cursor.fetchall()
            return [dict(tool) for tool in tools]
        except Exception as e:
            logger.error(f"Error fetching tools by category: {e}")
            return []
    
    def get_all_tools(self):
        """Get all tools with pricing information"""
        if not self.connection:
            if not self.connect():
                return []
        
        try:
            self.cursor.execute("""
                SELECT t.id, t.name, t.category, t.description, t.primary_use_cases,
                       t.popularity_score, t.monthly_cost_usd, t.setup_cost_usd,
                       t.license_cost_usd, t.training_cost_usd, t.total_cost_of_ownership_score,
                       t.price_performance_ratio, pt.tier_name
                FROM tools t
                LEFT JOIN price_tiers pt ON t.price_tier_id = pt.id
                ORDER BY t.monthly_cost_usd ASC, t.popularity_score DESC
            """)
            tools = self.cursor.fetchall()
            return [dict(tool) for tool in tools]
        except Exception as e:
            logger.error(f"Error fetching all tools: {e}")
            return []
    
    def apply_migration(self, file_path: str):
        """Apply SQL migration file"""
        executed = 0
        failed = 0
        errors = []
        
        if not os.path.isfile(file_path):
            raise FileNotFoundError(f"Migration file not found: {file_path}")
        
        if not self.is_open():
            if not self.connect():
                raise Exception("Could not connect to PostgreSQL")
        
        try:
            with open(file_path, "r", encoding="utf-8") as f:
                raw = f.read()
            
            # Strip line comments
            lines = []
            for line in raw.splitlines():
                stripped = line.strip()
                if stripped.startswith("--") or stripped.startswith("//"):
                    continue
                lines.append(line)
            
            merged = "\n".join(lines)
            
            # Split by semicolon but handle dollar-quoted strings
            statements = []
            current_stmt = ""
            in_dollar_quote = False
            dollar_tag = ""
            
            i = 0
            while i < len(merged):
                char = merged[i]
                
                if not in_dollar_quote:
                    if char == '$':
                        # Check for start of dollar-quoted string
                        j = i + 1
                        while j < len(merged) and merged[j] != '$':
                            j += 1
                        if j < len(merged):
                            dollar_tag = merged[i:j+1]
                            in_dollar_quote = True
                            current_stmt += char
                    elif char == ';':
                        # End of statement
                        if current_stmt.strip():
                            statements.append(current_stmt.strip())
                            current_stmt = ""
                    else:
                        current_stmt += char
                else:
                    # Inside dollar-quoted string
                    current_stmt += char
                    if merged[i:i+len(dollar_tag)] == dollar_tag:
                        in_dollar_quote = False
                        dollar_tag = ""
                
                i += 1
            
            # Add final statement if exists
            if current_stmt.strip():
                statements.append(current_stmt.strip())
            
            # Filter out empty statements
            statements = [s for s in statements if s.strip()]
            
            for stmt in statements:
                try:
                    self.cursor.execute(stmt)
                    self.connection.commit()
                    executed += 1
                except Exception as e:
                    failed += 1
                    errors.append({"statement": stmt[:100] + "...", "error": str(e)})
                    logger.error(f"Migration failed: {e}")
            
            return {"executed": executed, "failed": failed, "errors": errors}
            
        except Exception as e:
            logger.error(f"Error applying migration: {e}")
            return {"executed": executed, "failed": failed, "errors": errors}

    def recommend_stacks_by_budget(self, budget: float, domain: Optional[str] = None, limit: int = 5):
        """
        Recommend tech stacks based on exact budget constraint using PostgreSQL.
        
        Args:
            budget: User's exact monthly budget
            domain: Optional domain filter
            limit: Maximum number of results
            
        Returns:
            List of tech stacks that fit within budget
        """
        if not self.connect():
            return {"error": "Could not connect to PostgreSQL"}
        
        try:
            # Query for stacks within budget
            query = """
            SELECT 
                pbs.id,
                pbs.stack_name,
                pbs.total_monthly_cost_usd,
                pbs.total_setup_cost_usd,
                pbs.frontend_tech,
                pbs.backend_tech,
                pbs.database_tech,
                pbs.cloud_tech,
                pbs.testing_tech,
                pbs.mobile_tech,
                pbs.devops_tech,
                pbs.ai_ml_tech,
                pbs.team_size_range,
                pbs.development_time_months,
                pbs.maintenance_complexity,
                pbs.scalability_ceiling,
                pbs.recommended_domains,
                pbs.success_rate_percentage,
                pbs.user_satisfaction_score,
                pbs.description,
                pbs.pros,
                pbs.cons,
                pt.tier_name,
                pt.target_audience,
                (pbs.user_satisfaction_score * 0.4 + pbs.success_rate_percentage * 0.3 + 
                 (100 - (pbs.total_monthly_cost_usd / %s * 100)) * 0.3) AS recommendation_score,
                (pbs.total_monthly_cost_usd / %s) AS budget_utilization
            FROM price_based_stacks pbs
            JOIN price_tiers pt ON pbs.price_tier_id = pt.id
            WHERE pbs.total_monthly_cost_usd <= %s
            AND (%s IS NULL OR %s = ANY(pbs.recommended_domains))
            ORDER BY recommendation_score DESC, pbs.total_monthly_cost_usd ASC
            LIMIT %s
            """
            
            self.cursor.execute(query, (budget, budget, budget, domain, domain, limit))
            stacks = self.cursor.fetchall()
            
            return {
                "success": True,
                "budget": budget,
                "domain": domain,
                "stacks_found": len(stacks),
                "stacks": [dict(stack) for stack in stacks]
            }
            
        except Exception as e:
            logger.error(f"Error in budget recommendation: {e}")
            return {"error": str(e)}
        finally:
            self.close()

    def calculate_custom_stack_cost(self, frontend: str, backend: str, database: str, cloud: str, 
                                  testing: str = None, mobile: str = None, devops: str = None, ai_ml: str = None):
        """
        Calculate the cost of a custom tech stack by looking up individual technology costs.
        
        Args:
            frontend, backend, database, cloud: Required technologies
            testing, mobile, devops, ai_ml: Optional technologies
            
        Returns:
            Dictionary with cost breakdown
        """
        if not self.connect():
            return {"error": "Could not connect to PostgreSQL"}
        
        try:
            # Get costs for all technologies
            tech_categories = {
                'frontend': frontend,
                'backend': backend, 
                'database': database,
                'cloud': cloud
            }
            
            if testing:
                tech_categories['testing'] = testing
            if mobile:
                tech_categories['mobile'] = mobile
            if devops:
                tech_categories['devops'] = devops
            if ai_ml:
                tech_categories['ai-ml'] = ai_ml
            
            total_monthly_cost = 0
            total_setup_cost = 0
            cost_breakdown = {}
            
            for category, tech_name in tech_categories.items():
                query = """
                SELECT 
                    tech_name,
                    tech_category,
                    monthly_operational_cost_usd,
                    development_cost_usd + training_cost_usd as setup_cost,
                    total_cost_of_ownership_score,
                    price_performance_ratio
                FROM tech_pricing 
                WHERE tech_name = %s AND tech_category = %s
                """
                
                self.cursor.execute(query, (tech_name, category))
                result = self.cursor.fetchone()
                
                if result:
                    monthly_cost = float(result['monthly_operational_cost_usd'] or 0)
                    setup_cost = float(result['setup_cost'] or 0)
                    
                    total_monthly_cost += monthly_cost
                    total_setup_cost += setup_cost
                    
                    cost_breakdown[tech_name] = {
                        'category': category,
                        'monthly_cost': monthly_cost,
                        'setup_cost': setup_cost,
                        'tco_score': result['total_cost_of_ownership_score'],
                        'price_performance': result['price_performance_ratio']
                    }
                else:
                    # If technology not found, use default costs
                    default_monthly = 10 if category in ['frontend', 'backend'] else 20
                    default_setup = 100 if category in ['frontend', 'backend'] else 200
                    
                    total_monthly_cost += default_monthly
                    total_setup_cost += default_setup
                    
                    cost_breakdown[tech_name] = {
                        'category': category,
                        'monthly_cost': default_monthly,
                        'setup_cost': default_setup,
                        'tco_score': 70,
                        'price_performance': 70,
                        'note': 'Estimated cost - technology not found in database'
                    }
            
            return {
                "success": True,
                "total_monthly_cost": total_monthly_cost,
                "total_setup_cost": total_setup_cost,
                "cost_breakdown": cost_breakdown,
                "technologies": list(tech_categories.values())
            }
            
        except Exception as e:
            logger.error(f"Error calculating custom stack cost: {e}")
            return {"error": str(e)}
        finally:
            self.close()

    def find_alternatives_within_budget(self, current_tech: str, tech_category: str, budget: float):
        """
        Find alternative technologies within the same category that fit the budget.
        
        Args:
            current_tech: Current technology name
            tech_category: Technology category (frontend, backend, etc.)
            budget: Maximum monthly cost
            
        Returns:
            List of alternative technologies within budget
        """
        if not self.connect():
            return {"error": "Could not connect to PostgreSQL"}
        
        try:
            query = """
            SELECT 
                tech_name,
                monthly_operational_cost_usd,
                development_cost_usd + training_cost_usd as setup_cost,
                total_cost_of_ownership_score,
                price_performance_ratio
            FROM tech_pricing 
            WHERE tech_category = %s 
            AND monthly_operational_cost_usd <= %s
            AND tech_name != %s
            ORDER BY price_performance_ratio DESC, monthly_operational_cost_usd ASC
            """
            
            self.cursor.execute(query, (tech_category, budget, current_tech))
            alternatives = self.cursor.fetchall()
            
            return {
                "success": True,
                "current_tech": current_tech,
                "category": tech_category,
                "budget": budget,
                "alternatives": [dict(alt) for alt in alternatives]
            }
            
        except Exception as e:
            logger.error(f"Error finding alternatives: {e}")
            return {"error": str(e)}
        finally:
            self.close()

# ================================================================================================
# ENHANCED TECH STACK SELECTOR
# ================================================================================================

class EnhancedTechStackSelector:
    def __init__(self, api_key):
        self.claude_client = anthropic.Anthropic(api_key=api_key)
        logger.info("Enhanced Tech Stack Selector initialized")

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

app = FastAPI(
    title="Enhanced Tech Stack Selector - PostgreSQL Integrated",
    description="Complete tech stack selector with Neo4j, PostgreSQL migration, and AI recommendations",
    version="13.0.0"
)

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

# ================================================================================================
# CONFIGURATION
# ================================================================================================

logger.remove()
logger.add(sys.stdout, level="INFO", format="{time} | {level} | {message}")

CLAUDE_API_KEY = "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"

if not os.getenv("CLAUDE_API_KEY") and CLAUDE_API_KEY:
    os.environ["CLAUDE_API_KEY"] = CLAUDE_API_KEY

# Debug logging for API key
api_key = os.getenv("CLAUDE_API_KEY") or CLAUDE_API_KEY
logger.info(f"🔑 Claude API Key loaded: {api_key[:20]}..." if api_key else "❌ No Claude API Key found")

# Initialize services
NEO4J_URI = os.getenv("NEO4J_URI", "bolt://localhost:7687")
NEO4J_USER = os.getenv("NEO4J_USER", "neo4j")
NEO4J_PASSWORD = os.getenv("NEO4J_PASSWORD", "password")

neo4j_service = Neo4jService(
    uri=NEO4J_URI,
    user=NEO4J_USER,
    password=NEO4J_PASSWORD
)

# PostgreSQL configuration - using environment variables
postgres_migration_service = PostgreSQLMigrationService(
    host=os.getenv("POSTGRES_HOST", "localhost"),
    port=int(os.getenv("POSTGRES_PORT", "5432")),
    user=os.getenv("POSTGRES_USER", "pipeline_admin"),
    password=os.getenv("POSTGRES_PASSWORD", "secure_pipeline_2024"),
    database=os.getenv("POSTGRES_DB", "dev_pipeline")
)

enhanced_selector = EnhancedTechStackSelector(os.getenv("CLAUDE_API_KEY") or CLAUDE_API_KEY)

# ================================================================================================
# SHUTDOWN HANDLER
# ================================================================================================

@app.on_event("shutdown")
async def shutdown_event():
    neo4j_service.close()
    postgres_migration_service.close()

atexit.register(lambda: neo4j_service.close())
atexit.register(lambda: postgres_migration_service.close())

# ================================================================================================
# STARTUP EVENT
# ================================================================================================

@app.on_event("startup")
async def startup_event():
    """Initialize PostgreSQL tables and sample data on startup"""
    try:
        if postgres_migration_service.connect():
            postgres_migration_service.create_tables_if_not_exist()
            postgres_migration_service.get_sample_data()
            postgres_migration_service.close()
            logger.info("✅ PostgreSQL initialization completed")
        else:
            logger.warning("⚠️ PostgreSQL connection failed during startup")

        # Automatic migration: PostgreSQL -> Neo4j, then apply Neo4j.cql
        try:
            if POSTGRES_AVAILABLE:
                logger.info("🔁 Starting automatic migration Postgres -> Neo4j...")
                # Ensure a fresh connection for migration
                if postgres_migration_service.connect():
                    migrated = postgres_migration_service.migrate_to_neo4j(neo4j_service)
                    postgres_migration_service.close()
                    logger.info(f"✅ Migration completed: {migrated}")
                else:
                    logger.warning("⚠️ Skipping migration: PostgreSQL not connected")

                # Apply bundled Neo4j.cql if present
                default_cql = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "Neo4j.cql"))
                if os.path.isfile(default_cql):
                    logger.info(f"📥 Applying Neo4j CQL from {default_cql}...")
                    try:
                        result = neo4j_service.apply_cql_script(default_cql)
                        if result.get("failed", 0) > 0:
                            logger.warning(f"⚠️ CQL apply completed with {result['failed']} failures out of {result.get('executed', 0) + result.get('failed', 0)} statements")
                            # Log detailed errors if present (limit to first 5 errors to avoid spam)
                            for i, error in enumerate(result.get("errors", [])[:5]):
                                logger.error(f"❌ CQL Error {i+1}: {error.get('error', 'Unknown error')}")
                            if len(result.get("errors", [])) > 5:
                                logger.warning(f"⚠️ ... and {len(result.get('errors', [])) - 5} more errors (see logs above)")
                        else:
                            logger.info(f"✅ Neo4j CQL applied successfully: {result.get('executed', 0)} statements executed")
                    except Exception as cql_err:
                        logger.error(f"❌ Failed to apply CQL script: {cql_err}")
                else:
                    logger.info("ℹ️ No bundled Neo4j.cql found; skipping graph schema apply")
                
                # Apply tools pricing migration
                tools_migration = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "db", "003_tools_pricing_migration.sql"))
                if os.path.isfile(tools_migration):
                    logger.info(f"📥 Applying tools pricing migration from {tools_migration}...")
                    try:
                        result = postgres_migration_service.apply_migration(tools_migration)
                        if result.get("failed", 0) > 0:
                            logger.warning(f"⚠️ Tools migration completed with {result['failed']} failures out of {result.get('executed', 0) + result.get('failed', 0)} statements")
                            for i, error in enumerate(result.get("errors", [])[:5]):
                                logger.error(f"❌ Tools Migration Error {i+1}: {error.get('error', 'Unknown error')}")
                            if len(result.get("errors", [])) > 5:
                                logger.warning(f"⚠️ ... and {len(result.get('errors', [])) - 5} more errors (see logs above)")
                        else:
                            logger.info(f"✅ Tools pricing migration applied successfully: {result.get('executed', 0)} statements executed")
                    except Exception as tools_err:
                        logger.error(f"❌ Failed to apply tools migration: {tools_err}")
                else:
                    logger.info("ℹ️ No tools pricing migration found; skipping tools pricing setup")
        except Exception as mig_err:
            logger.error(f"❌ Automatic migration/apply-cql error: {mig_err}")
    except Exception as e:
        logger.error(f"❌ PostgreSQL startup error: {e}")

# ================================================================================================
# ENDPOINTS
# ================================================================================================

@app.get("/health")
async def health_check():
    return {
        "status": "healthy",
        "service": "enhanced-tech-stack-selector-postgresql",
        "version": "13.0.0",
        "features": ["neo4j", "postgresql_migration", "claude_ai", "fastapi"]
    }

@app.get("/api/diagnostics")
async def diagnostics():
    diagnostics_result = {
        "service": "enhanced-tech-stack-selector-postgresql",
        "version": "13.0.0",
        "timestamp": datetime.utcnow().isoformat(),
        "checks": {}
    }

    # Check Neo4j
    neo4j_check = {"status": "unknown"}
    try:
        with neo4j_service.driver.session() as session:
            result = session.run("MATCH (n) RETURN count(n) AS count")
            node_count = result.single().get("count", 0)
        neo4j_check.update({
            "status": "ok",
            "node_count": int(node_count)
        })
    except Exception as e:
        neo4j_check.update({
            "status": "error",
            "error": str(e)
        })
    diagnostics_result["checks"]["neo4j"] = neo4j_check

    # Check Claude
    claude_check = {
        "status": "unknown",
        "api_key_present": bool(os.getenv("CLAUDE_API_KEY"))
    }
    try:
        client = enhanced_selector.claude_client
        if client is None:
            claude_check.update({
                "status": "error",
                "error": "Claude client not initialized"
            })
        else:
            try:
                # Simple test to check if client works
                claude_check.update({
                    "status": "ok",
                    "client_initialized": True
                })
            except Exception as api_err:
                claude_check.update({
                    "status": "error",
                    "error": str(api_err)
                })
    except Exception as e:
        claude_check.update({
            "status": "error",
            "error": str(e)
        })
    diagnostics_result["checks"]["claude_anthropic"] = claude_check

    # Check PostgreSQL
    postgres_check = {"status": "unknown"}
    try:
        if POSTGRES_AVAILABLE:
            if postgres_migration_service.connect():
                # Test query
                postgres_migration_service.cursor.execute("SELECT version()")
                version = postgres_migration_service.cursor.fetchone()
                postgres_migration_service.cursor.execute("SELECT COUNT(*) FROM technologies")
                tech_count = postgres_migration_service.cursor.fetchone()['count']
                
                postgres_check.update({
                    "status": "ok", 
                    "available": True,
                    "version": version[0] if version else "unknown",
                    "technologies_count": tech_count
                })
                postgres_migration_service.close()
            else:
                postgres_check.update({"status": "error", "available": False})
        else:
            postgres_check.update({"status": "not_available", "available": False})
    except Exception as e:
        postgres_check.update({"status": "error", "error": str(e)})
    diagnostics_result["checks"]["postgresql"] = postgres_check

    return diagnostics_result

@app.get("/api/postgres/technologies")
async def get_all_postgres_technologies():
    """Get all technologies from PostgreSQL"""
    try:
        technologies = postgres_migration_service.get_all_technologies()
        return {"success": True, "data": technologies, "count": len(technologies)}
    except Exception as e:
        return {"success": False, "error": str(e)}

@app.post("/api/postgres/init")
async def initialize_postgres_tables():
    """Initialize PostgreSQL tables and sample data"""
    try:
        if not postgres_migration_service.connect():
            return {"success": False, "error": "Could not connect to PostgreSQL"}
        
        tables_created = postgres_migration_service.create_tables_if_not_exist()
        sample_data_inserted = postgres_migration_service.get_sample_data()
        postgres_migration_service.close()
        
        return {
            "success": True, 
            "tables_created": tables_created,
            "sample_data_inserted": sample_data_inserted,
            "message": "PostgreSQL initialization completed"
        }
    except Exception as e:
        return {"success": False, "error": str(e)}

@app.get("/api/neo4j/technologies")
async def get_all_technologies():
    try:
        with neo4j_service.driver.session() as session:
            result = session.run("MATCH (t:Technology) RETURN t")
            technologies = []
            for record in result:
                t = record["t"]
                technologies.append({
                    "id": t.get("id", f"tech_{t.get('name', 'unknown').lower().replace(' ', '_')}"),
                    "name": t.get("name", "Unknown Technology"),
                    "category": t.get("category", "unknown"),
                    "type": t.get("type") or t.get("framework_type") or t.get("language_base") or t.get("database_type") or t.get("service_type") or "general",
                    "maturity_score": t.get("maturity_score", 50),
                    "learning_curve": t.get("learning_curve", "medium"),
                    "performance_rating": t.get("performance_rating", 70),
                    "community_size": t.get("community_size", "medium"),
                    "cost_model": t.get("cost_model") or ("free" if t.get("monthly_cost", 0) == 0 else "paid"),
                    "primary_use_cases": t.get("primary_use_cases", ["General purpose"]),
                    "strengths": t.get("strengths", ["Good performance", "Active community"]),
                    "weaknesses": t.get("weaknesses", ["Learning curve", "Documentation could be better"])
                })
        return {"success": True, "data": technologies}
    except Exception as e:
        return {"success": False, "error": str(e)}

@app.get("/api/neo4j/tech_compatibility")
async def get_tech_compatibility():
    try:
        with neo4j_service.driver.session() as session:
            query = """
            MATCH (a:Technology)-[r:COMPATIBLE_WITH|OPTIMIZED_FOR]->(b:Technology)
            RETURN a.name AS tech_a_name,
                   b.name AS tech_b_name,
                   coalesce(r.score, r.compatibility_score) AS score,
                   coalesce(r.effort, r.integration_effort) AS effort,
                   coalesce(r.reason, r.notes) AS notes,
                   type(r) AS relationship
            """
            result = session.run(query)
            compatibilities = [record.data() for record in result]
        return {"success": True, "data": compatibilities}
    except Exception as e:
        return {"success": False, "error": str(e)}

@app.post("/api/migrate/postgres-to-neo4j")
async def migrate_postgres_to_neo4j():
    """Migrate data from PostgreSQL to Neo4j"""
    try:
        if not POSTGRES_AVAILABLE:
            return {"success": False, "error": "PostgreSQL connector not available"}
        
        success = postgres_migration_service.migrate_to_neo4j(neo4j_service)
        if success:
            # Create relationships after migration
            relationships_created = neo4j_service.create_compatibility_relationships()
            return {
                "success": True, 
                "message": "Migration from PostgreSQL to Neo4j completed successfully",
                "relationships_created": relationships_created
            }
        else:
            return {"success": False, "error": "Migration failed", "details": postgres_migration_service.last_error}
    except Exception as e:
        return {"success": False, "error": str(e)}

@app.post("/api/neo4j/create-relationships")
async def create_neo4j_relationships():
    """Create compatibility relationships in Neo4j"""
    try:
        relationships_created = neo4j_service.create_compatibility_relationships()
        return {
            "success": True,
            "message": f"Created {relationships_created} compatibility relationships",
            "relationships_created": relationships_created
        }
    except Exception as e:
        return {"success": False, "error": str(e)}

@app.get("/api/neo4j/technologies-with-relationships")
async def get_technologies_with_relationships():
    """Get all technologies with their relationships"""
    try:
        technologies = neo4j_service.get_all_technologies_with_relationships()
        return {"success": True, "data": technologies, "count": len(technologies)}
    except Exception as e:
        return {"success": False, "error": str(e)}

@app.get("/api/test/neo4j")
async def test_neo4j_connection():
    try:
        try:
            neo4j_service.driver.verify_connectivity()
            connectivity = "ok"
        except Exception as conn_err:
            connectivity = f"error: {conn_err}"
        
        with neo4j_service.driver.session() as session:
            result = session.run("MATCH (t:Technology) RETURN count(t) as count")
            single = result.single()
            count = single["count"] if single else 0
            
        sample_tech = []
        with neo4j_service.driver.session() as session:
            result = session.run("""
                MATCH (t:Technology) 
                RETURN coalesce(t.name, 'Unknown') as name, coalesce(t.category, 'Unknown') as category 
                LIMIT 5
            """)
            for record in result:
                sample_tech.append(dict(record))
                
        return {
            "status": "success",
            "neo4j_connection": connectivity,
            "total_technologies": count,
            "sample_technologies": sample_tech
        }
        
    except Exception as e:
        return {
            "status": "error",
            "message": str(e)
        }

class RecommendBestRequest(BaseModel):
    domain: Optional[str] = None
    budget: Optional[int] = None
    preferredTechnologies: Optional[List[str]] = None

class ApplyCQLRequest(BaseModel):
    path: Optional[str] = None  # defaults to bundled file

class BudgetRecommendRequest(BaseModel):
    budget: Optional[float] = None
    monthly_cost: Optional[float] = None
    setup_cost: Optional[float] = None
    domain: Optional[str] = None
    limit: Optional[int] = 5  # Maximum number of results to return

class CustomStackCostRequest(BaseModel):
    frontend: str
    backend: str
    database: str
    cloud: str
    testing: Optional[str] = None
    mobile: Optional[str] = None
    devops: Optional[str] = None
    ai_ml: Optional[str] = None

class AlternativeTechRequest(BaseModel):
    current_tech: str
    tech_category: str
    budget: float

@app.post("/recommend/best")
async def recommend_best(req: RecommendBestRequest):
    try:
        rows = neo4j_service.get_best_stack(req.domain, req.budget, req.preferredTechnologies)
        return rows
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/analysis/price-performance")
async def analysis_price_performance():
    try:
        rows = neo4j_service.get_price_performance()
        return rows
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

# === Added: Routes for user's queries ===
@app.get("/analysis/technology-ecosystem")
async def analysis_technology_ecosystem():
    try:
        return neo4j_service.get_technology_ecosystem()
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/analysis/stack-trends")
async def analysis_stack_trends():
    try:
        return neo4j_service.get_stack_trends()
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/validate/relationships")
async def validate_relationships():
    try:
        return neo4j_service.validate_relationships()
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/validate/completeness")
async def validate_completeness():
    try:
        return neo4j_service.validate_data_completeness()
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/validate/price-consistency")
async def validate_price_consistency():
    try:
        return neo4j_service.validate_price_consistency()
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/export/stacks-with-pricing")
async def export_stacks_with_pricing():
    try:
        return neo4j_service.export_stacks_with_pricing()
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/export/price-tiers")
async def export_price_tiers():
    try:
        return neo4j_service.export_price_tiers()
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/neo4j/apply-cql")
async def apply_cql(req: ApplyCQLRequest):
    try:
        default_path = os.path.join(os.path.dirname(__file__), "..", "Neo4j.cql")
        default_path = os.path.abspath(default_path)
        cql_path = req.path or default_path
        result = neo4j_service.apply_cql_script(cql_path)
        return {"success": result.get("failed", 0) == 0, **result, "path": cql_path}
    except FileNotFoundError as nf:
        raise HTTPException(status_code=404, detail=str(nf))
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/neo4j/query")
async def run_neo4j_query(req: dict):
    """Run a custom Neo4j query"""
    try:
        query = req.get("query", "")
        params = req.get("params", {})
        result = neo4j_service.run_query(query, params)
        return {"success": True, "data": result}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/recommend/budget")
async def recommend_by_budget(req: BudgetRecommendRequest):
    try:
        # New behavior: allow monthly_cost/setup_cost caps
        if req.monthly_cost is not None or req.setup_cost is not None:
            return neo4j_service.recommend_by_cost_limits(
                monthly_cost=req.monthly_cost,
                setup_cost=req.setup_cost,
                domain=req.domain
            )
        # Backward compatibility with original budget field
        if req.budget is None:
            raise HTTPException(status_code=400, detail="budget or monthly_cost/setup_cost is required")
        return neo4j_service.recommend_by_budget(req.budget, req.domain, req.limit)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/v2/recommend/budget")
async def recommend_stacks_by_budget_v2(req: BudgetRecommendRequest):
    """
    ROBUST BUDGET-BASED TECH STACK RECOMMENDATION
    
    This endpoint provides accurate budget-based recommendations using PostgreSQL.
    It finds all stacks that cost <= user's budget, not just stacks from a price tier.
    
    Example: If user gives $15, it will return stacks costing $15 or less.
    """
    try:
        if req.budget is None:
            raise HTTPException(status_code=400, detail="budget is required")
        
        if req.budget <= 0:
            raise HTTPException(status_code=400, detail="budget must be greater than 0")
        
        result = postgres_migration_service.recommend_stacks_by_budget(
            budget=req.budget,
            domain=req.domain,
            limit=req.limit
        )
        
        if "error" in result:
            raise HTTPException(status_code=500, detail=result["error"])
        
        return {
            "success": True,
            "message": f"Found {result['stacks_found']} tech stacks within ${req.budget} budget",
            "data": result
        }
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Error in budget recommendation v2: {e}")
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/v2/calculate/custom-stack")
async def calculate_custom_stack_cost(req: CustomStackCostRequest):
    """
    Calculate the cost of a custom tech stack by looking up individual technology costs.
    
    This allows users to see the exact cost of their preferred technology combination.
    """
    try:
        result = postgres_migration_service.calculate_custom_stack_cost(
            frontend=req.frontend,
            backend=req.backend,
            database=req.database,
            cloud=req.cloud,
            testing=req.testing,
            mobile=req.mobile,
            devops=req.devops,
            ai_ml=req.ai_ml
        )
        
        if "error" in result:
            raise HTTPException(status_code=500, detail=result["error"])
        
        return {
            "success": True,
            "message": "Custom stack cost calculated successfully",
            "data": result
        }
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Error calculating custom stack cost: {e}")
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/v2/find/alternatives")
async def find_alternatives_within_budget(req: AlternativeTechRequest):
    """
    Find alternative technologies within the same category that fit the budget.
    
    Useful when a user wants to replace a specific technology with a cheaper alternative.
    """
    try:
        result = postgres_migration_service.find_alternatives_within_budget(
            current_tech=req.current_tech,
            tech_category=req.tech_category,
            budget=req.budget
        )
        
        if "error" in result:
            raise HTTPException(status_code=500, detail=result["error"])
        
        return {
            "success": True,
            "message": f"Found {len(result['alternatives'])} alternatives for {req.current_tech} within ${req.budget} budget",
            "data": result
        }
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Error finding alternatives: {e}")
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/api/v2/budget/analysis")
async def budget_analysis(budget: float, domain: Optional[str] = None):
    """
    Get comprehensive budget analysis including:
    - Available stacks within budget
    - Budget utilization
    - Cost breakdown by category
    - Recommendations for optimization
    """
    try:
        if budget <= 0:
            raise HTTPException(status_code=400, detail="budget must be greater than 0")
        
        # Get stacks within budget
        stacks_result = postgres_migration_service.recommend_stacks_by_budget(
            budget=budget,
            domain=domain,
            limit=10
        )
        
        if "error" in stacks_result:
            raise HTTPException(status_code=500, detail=stacks_result["error"])
        
        # Analyze budget utilization
        if stacks_result["stacks"]:
            avg_cost = sum(float(stack["total_monthly_cost_usd"]) for stack in stacks_result["stacks"]) / len(stacks_result["stacks"])
            budget_utilization = (avg_cost / float(budget)) * 100
            
            # Get cost breakdown by category
            cost_breakdown = {}
            for stack in stacks_result["stacks"]:
                for tech in ["frontend_tech", "backend_tech", "database_tech", "cloud_tech"]:
                    tech_name = stack.get(tech)
                    if tech_name:
                        category = tech.replace("_tech", "")
                        if category not in cost_breakdown:
                            cost_breakdown[category] = []
                        cost_breakdown[category].append(tech_name)
        else:
            avg_cost = 0
            budget_utilization = 0
            cost_breakdown = {}
        
        return {
            "success": True,
            "budget_analysis": {
                "user_budget": budget,
                "stacks_found": stacks_result["stacks_found"],
                "average_stack_cost": round(avg_cost, 2),
                "budget_utilization_percentage": round(budget_utilization, 2),
                "cost_breakdown_by_category": cost_breakdown,
                "recommendations": {
                    "budget_efficiency": "Excellent" if budget_utilization > 80 else "Good" if budget_utilization > 60 else "Consider increasing budget",
                    "savings_potential": f"${budget - avg_cost:.2f} per month" if avg_cost < budget else "No savings available",
                    "scaling_room": f"${budget * 0.2:.2f} available for scaling" if budget_utilization < 80 else "Limited scaling room"
                }
            },
            "stacks": stacks_result["stacks"]
        }
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Error in budget analysis: {e}")
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/api/test/postgres")
async def test_postgres_connection():
    """Test PostgreSQL connection"""
    try:
        if not POSTGRES_AVAILABLE:
            return {
                "status": "error",
                "message": "PostgreSQL connector (psycopg2) not available"
            }
        
        if postgres_migration_service.connect():
            # Test basic query
            postgres_migration_service.cursor.execute("SELECT version()")
            version_info = postgres_migration_service.cursor.fetchone()
            
            postgres_migration_service.cursor.execute("SELECT COUNT(*) FROM technologies")
            tech_count = postgres_migration_service.cursor.fetchone()['count']
            
            # Get sample technologies
            postgres_migration_service.cursor.execute("""
                SELECT name, category FROM technologies LIMIT 5
            """)
            sample_tech = postgres_migration_service.cursor.fetchall()
            
            postgres_migration_service.close()
            
            return {
                "status": "success",
                "postgres_connection": "ok",
                "version": version_info[0] if version_info else "unknown",
                "total_technologies": tech_count,
                "sample_technologies": [dict(tech) for tech in sample_tech]
            }
        else:
            return {
                "status": "error",
                "message": "Could not connect to PostgreSQL"
            }
            
    except Exception as e:
        return {
            "status": "error",
            "message": str(e)
        }

@app.get("/api/tools")
async def get_tools(category: Optional[str] = None, price_tier_id: Optional[int] = None):
    """Get all tools with optional filtering"""
    try:
        if category:
            tools = postgres_migration_service.get_tools_by_category(category)
        elif price_tier_id:
            tools = postgres_migration_service.get_tools_by_price_tier(price_tier_id)
        else:
            tools = postgres_migration_service.get_all_tools()
        return {"success": True, "data": tools, "count": len(tools)}
    except Exception as e:
        logger.error(f"Error fetching tools: {e}")
        return {"success": False, "error": str(e)}

@app.get("/api/tools/budget")
async def get_tools_within_budget(max_monthly_cost: float, max_setup_cost: float):
    """Get tools within specified budget constraints"""
    try:
        tools = postgres_migration_service.get_tools_within_budget(max_monthly_cost, max_setup_cost)
        return {"success": True, "data": tools, "count": len(tools)}
    except Exception as e:
        logger.error(f"Error fetching tools within budget: {e}")
        return {"success": False, "error": str(e)}

@app.get("/api/tools/categories")
async def get_tool_categories():
    """Get all tool categories"""
    try:
        if not postgres_migration_service.is_open():
            if not postgres_migration_service.connect():
                return {"success": False, "error": "Could not connect to PostgreSQL"}
        
        postgres_migration_service.cursor.execute("SELECT DISTINCT category FROM tools ORDER BY category")
        categories = [row[0] for row in postgres_migration_service.cursor.fetchall()]
        return {"success": True, "data": categories, "count": len(categories)}
    except Exception as e:
        logger.error(f"Error fetching tool categories: {e}")
        return {"success": False, "error": str(e)}

@app.get("/api/tools/price-tiers")
async def get_tools_by_price_tier(price_tier_id: int):
    """Get tools by price tier"""
    try:
        tools = postgres_migration_service.get_tools_by_price_tier(price_tier_id)
        return {"success": True, "data": tools, "count": len(tools)}
    except Exception as e:
        logger.error(f"Error fetching tools by price tier: {e}")
        return {"success": False, "error": str(e)}

@app.post("/api/v1/select")
async def select_enhanced_tech_stack(request: Request):
    try:
        request_data = await request.json()
        
        logger.info("=== RECEIVED ENHANCED DATA START ===")
        logger.info(json.dumps(request_data, indent=2, default=str))
        logger.info("=== RECEIVED ENHANCED DATA END ===")
        
        extracted_data = extract_enhanced_data(request_data)
        
        # If no features found, try to extract from description
        if not extracted_data["features"] and not extracted_data["feature_name"]:
            logger.warning("⚠️ No features found, attempting to extract from description")
            
            # Try to extract features from description
            description = extracted_data.get("description", "")
            if description:
                extracted_features = extract_features_from_description(description)
                if extracted_features:
                    extracted_data["features"] = extracted_features
                    extracted_data["feature_name"] = extracted_features[0] if extracted_features else ""
                    logger.info(f"✅ Extracted {len(extracted_features)} features from description: {extracted_features}")
                else:
                    # If still no features, create a generic feature from the project name
                    project_name = extracted_data.get("project_name", "Unknown Project")
                    extracted_data["features"] = [project_name]
                    extracted_data["feature_name"] = project_name
                    logger.info(f"✅ Created generic feature from project name: {project_name}")
            else:
                logger.error("❌ NO FEATURES OR FEATURE DATA FOUND")
                return {
                    "error": "No features or feature data found in request",
                    "received_data_keys": list(request_data.keys()) if isinstance(request_data, dict) else "not_dict",
                    "extraction_attempted": "enhanced_data_extraction"
                }
        
        context = build_comprehensive_context(extracted_data)
        claude_recommendations = await generate_enhanced_recommendations(context)
        
        # Neo4j recommendations - extract technical requirements from features and description
        technical_requirements = []
        
        # Add features as requirements
        if extracted_data["features"]:
            technical_requirements.extend(extracted_data["features"])
        
        # Add description keywords
        description = extracted_data.get("description", "")
        if description:
            # Extract technical keywords from description
            tech_keywords = ["payment", "security", "reporting", "multi-currency", "web", "application", "platform", "transaction", "financial", "enterprise", "api", "database", "frontend", "backend"]
            for keyword in tech_keywords:
                if keyword.lower() in description.lower():
                    technical_requirements.append(keyword)
        
        # Add project type
        if extracted_data.get("project_type"):
            technical_requirements.append(extracted_data["project_type"])
        
        recommendations_from_neo4j = []
        
        try:
            logger.info(f"🔍 Searching Neo4j with technical requirements: {technical_requirements}")
            matching_tech = neo4j_service.get_tech_by_requirements(technical_requirements)
            logger.info(f"📊 Found {len(matching_tech)} matching technologies from Neo4j")
            for tech in matching_tech:
                compatible_tech = neo4j_service.get_compatible_tech(tech["id"])
                recommendations_from_neo4j.append({
                    "technology": dict(tech),
                    "compatible_technologies": [
                        {"name": t["tech"]["name"], "score": t["score"]}
                        for t in compatible_tech
                    ]
                })
        except Exception as neo_err:
            logger.error(f"Neo4j integration failed: {neo_err}")
            recommendations_from_neo4j = [{"error": str(neo_err)}]
        
        # PostgreSQL recommendations
        postgres_recommendations = []
        try:
            postgres_technologies = postgres_migration_service.get_all_technologies()
            postgres_recommendations = postgres_technologies[:5]  # Top 5 for demo
        except Exception as pg_err:
            logger.error(f"PostgreSQL integration failed: {pg_err}")
            postgres_recommendations = [{"error": str(pg_err)}]
        
        complete_response = {
            "success": True,
            "enhanced_analysis": True,
            
            "project_context": {
                "project_name": extracted_data["project_name"],
                "project_type": extracted_data["project_type"],
                "features_analyzed": len(extracted_data["features"]),
                "business_questions_answered": len(extracted_data["business_answers"]),
                "complexity": extracted_data["complexity"],
                "detailed_requirements_count": len(extracted_data.get("detailed_requirements", [])),
                "total_tagged_rules": extracted_data.get("total_tagged_rules", 0)
            },
            
            "functional_requirements": {
                "feature_name": extracted_data["feature_name"],
                "description": extracted_data["description"],
                "technical_requirements": extracted_data["requirements"],
                "business_logic_rules": extracted_data["logic_rules"],
                "complexity_level": extracted_data["complexity"],
                "all_features": extracted_data["features"],
                "detailed_requirements": extracted_data.get("detailed_requirements", []),
                "tagged_rules": extracted_data.get("tagged_rules", []),
                "business_context": {
                    "questions": extracted_data["business_questions"],
                    "answers": extracted_data["business_answers"]
                }
            },
            
            "claude_recommendations": claude_recommendations,
            "neo4j_recommendations": recommendations_from_neo4j,
            "postgres_recommendations": postgres_recommendations,
            
            "analysis_timestamp": datetime.utcnow().isoformat(),
            "ready_for_architecture_design": True
        }
        
        logger.info("✅ Enhanced tech stack analysis + Neo4j + PostgreSQL integration completed")
        return complete_response
        
    except Exception as e:
        logger.error(f"💥 ERROR in merged enhanced tech stack selection: {e}")
        return {
            "error": str(e),
            "debug": "Check service logs for detailed error information"
        }

# ================================================================================================
# HELPER FUNCTIONS
# ================================================================================================

def extract_features_from_description(description: str) -> List[str]:
    """Extract features from project description using keyword matching"""
    if not description:
        return []
    
    # Define feature keywords and their mappings
    feature_keywords = {
        "payment": ["payment", "pay", "transaction", "billing", "invoice", "checkout"],
        "security": ["security", "secure", "authentication", "authorization", "encryption", "ssl", "https"],
        "reporting": ["report", "reporting", "analytics", "dashboard", "metrics", "statistics"],
        "multi-currency": ["multi-currency", "currency", "multi currency", "international", "forex"],
        "user-management": ["user", "users", "profile", "account", "registration", "login"],
        "api": ["api", "rest", "graphql", "endpoint", "service"],
        "database": ["database", "data", "storage", "persistence"],
        "frontend": ["frontend", "ui", "interface", "web", "mobile", "responsive"],
        "backend": ["backend", "server", "service", "microservice"],
        "real-time": ["real-time", "realtime", "live", "instant", "websocket"],
        "notification": ["notification", "alert", "email", "sms", "push"],
        "search": ["search", "filter", "query", "find"],
        "file-upload": ["upload", "file", "document", "media", "image"],
        "integration": ["integration", "connect", "sync", "import", "export"],
        "workflow": ["workflow", "process", "automation", "pipeline"]
    }
    
    extracted_features = []
    description_lower = description.lower()
    
    for feature, keywords in feature_keywords.items():
        if any(keyword in description_lower for keyword in keywords):
            extracted_features.append(feature)
    
    return extracted_features

def extract_enhanced_data(request_data: Dict) -> Dict:
    extracted = {
        "project_name": "Unknown Project",
        "project_type": "unknown",
        "feature_name": "",
        "description": "",
        "requirements": [],
        "complexity": "medium",
        "logic_rules": [],
        "business_questions": [],
        "business_answers": [],
        "features": [],
        "all_features": [],
        "detailed_requirements": [],
        "tagged_rules": [],
        "total_tagged_rules": 0
    }
    
    if isinstance(request_data, dict):
        extracted["feature_name"] = request_data.get("featureName", "")
        extracted["description"] = request_data.get("description", "")
        extracted["requirements"] = request_data.get("requirements", [])
        extracted["complexity"] = request_data.get("complexity", "medium")
        extracted["logic_rules"] = request_data.get("logicRules", [])
        extracted["business_questions"] = request_data.get("businessQuestions", [])
        extracted["business_answers"] = request_data.get("businessAnswers", [])
        extracted["project_name"] = request_data.get("projectName", "Unknown Project")
        extracted["project_type"] = request_data.get("projectType", "unknown")
        extracted["all_features"] = request_data.get("allFeatures", [])
        
        if isinstance(extracted["business_answers"], dict):
            ba_list = []
            for key, value in extracted["business_answers"].items():
                if isinstance(value, str) and value.strip():
                    question_idx = int(key) if key.isdigit() else 0
                    if question_idx < len(extracted["business_questions"]):
                        ba_list.append({
                            "question": extracted["business_questions"][question_idx],
                            "answer": value.strip()
                        })
            extracted["business_answers"] = ba_list
        
        if extracted["feature_name"]:
            extracted["features"] = [extracted["feature_name"]]
        
        if extracted["all_features"]:
            feature_names = []
            for feature in extracted["all_features"]:
                if isinstance(feature, dict):
                    feature_name = feature.get("name", feature.get("featureName", ""))
                    feature_names.append(feature_name)
                    
                    requirement_analysis = feature.get("requirementAnalysis", [])
                    if requirement_analysis:
                        for req_analysis in requirement_analysis:
                            requirement_name = req_analysis.get("requirement", "Unknown Requirement")
                            requirement_rules = req_analysis.get("logicRules", [])
                            
                            detailed_req = {
                                "feature_name": feature_name,
                                "requirement_name": requirement_name,
                                "description": feature.get("description", ""),
                                "complexity": req_analysis.get("complexity", "medium"),
                                "rules": requirement_rules
                            }
                            extracted["detailed_requirements"].append(detailed_req)
                            
                            for rule_idx, rule in enumerate(requirement_rules):
                                if rule and rule.strip():
                                    tagged_rule = {
                                        "rule_id": f"R{rule_idx + 1}",
                                        "rule_text": rule.strip(),
                                        "feature_name": feature_name,
                                        "requirement_name": requirement_name
                                    }
                                    extracted["tagged_rules"].append(tagged_rule)
                                    extracted["total_tagged_rules"] += 1
                    
                    elif feature.get("logicRules"):
                        regular_rules = feature.get("logicRules", [])
                        extracted["logic_rules"].extend(regular_rules)
                        
                else:
                    feature_names.append(str(feature))
            
            extracted["features"].extend([f for f in feature_names if f])
    
    return extracted

def build_comprehensive_context(extracted_data: Dict) -> Dict:
    functional_requirements = []
    if extracted_data["feature_name"]:
        functional_requirements.append(f"Core Feature: {extracted_data['feature_name']}")
    
    if extracted_data["requirements"]:
        functional_requirements.extend([f"• {req}" for req in extracted_data["requirements"]])
    
    if extracted_data["features"]:
        for feature in extracted_data["features"]:
            if feature and feature != extracted_data["feature_name"]:
                functional_requirements.append(f"• {feature}")
    
    detailed_requirements_text = []
    for detailed_req in extracted_data.get("detailed_requirements", []):
        req_text = f"📋 {detailed_req['feature_name']} → {detailed_req['requirement_name']}:"
        for rule in detailed_req["rules"]:
            req_text += f"\n  - {rule}"
        detailed_requirements_text.append(req_text)
    
    if detailed_requirements_text:
        functional_requirements.extend(detailed_requirements_text)
    
    business_context = {}
    if extracted_data["business_answers"]:
        for answer_data in extracted_data["business_answers"]:
            if isinstance(answer_data, dict):
                question = answer_data.get("question", "")
                answer = answer_data.get("answer", "")
                if question and answer:
                    if any(keyword in question.lower() for keyword in ["user", "scale", "concurrent"]):
                        business_context["scale_requirements"] = business_context.get("scale_requirements", [])
                        business_context["scale_requirements"].append(f"{question}: {answer}")
                    elif any(keyword in question.lower() for keyword in ["compliance", "security", "encryption"]):
                        business_context["security_requirements"] = business_context.get("security_requirements", [])
                        business_context["security_requirements"].append(f"{question}: {answer}")
                    elif any(keyword in question.lower() for keyword in ["budget", "timeline"]):
                        business_context["project_constraints"] = business_context.get("project_constraints", [])
                        business_context["project_constraints"].append(f"{question}: {answer}")
                    else:
                        business_context["other_requirements"] = business_context.get("other_requirements", [])
                        business_context["other_requirements"].append(f"{question}: {answer}")
    
    return {
        "project_name": extracted_data["project_name"],
        "project_type": extracted_data["project_type"],
        "complexity": extracted_data["complexity"],
        "functional_requirements": functional_requirements,
        "business_context": business_context,
        "logic_rules": extracted_data["logic_rules"],
        "detailed_requirements": extracted_data.get("detailed_requirements", []),
        "tagged_rules": extracted_data.get("tagged_rules", [])
    }

async def generate_enhanced_recommendations(context: Dict) -> Dict:
    if not enhanced_selector.claude_client:
        logger.error("❌ Claude client not available")
        return {
            "error": "Claude AI not available",
            "fallback": "Basic recommendations would go here"
        }
    
    functional_reqs_text = "\n".join(context["functional_requirements"])
    
    business_context_text = ""
    for category, requirements in context["business_context"].items():
        business_context_text += f"\n{category.replace('_', ' ').title()}:\n"
        business_context_text += "\n".join([f"  - {req}" for req in requirements]) + "\n"
    
    logic_rules_text = "\n".join([f"  - {rule}" for rule in context["logic_rules"]])
    
    tagged_rules_text = ""
    if context.get("tagged_rules"):
        tagged_rules_text = f"\n\nDETAILED TAGGED RULES:\n"
        for tagged_rule in context["tagged_rules"][:10]:
            tagged_rules_text += f"  {tagged_rule['rule_id']}: {tagged_rule['rule_text']} (Feature: {tagged_rule['feature_name']})\n"
        if len(context["tagged_rules"]) > 10:
            tagged_rules_text += f"  ... and {len(context['tagged_rules']) - 10} more tagged rules\n"
    
    prompt = f"""You are a senior software architect. Analyze this comprehensive project context and recommend the optimal technology stack.

PROJECT CONTEXT:
- Name: {context["project_name"]}
- Type: {context["project_type"]}
- Complexity: {context["complexity"]}

FUNCTIONAL REQUIREMENTS:
{functional_reqs_text}

BUSINESS CONTEXT & CONSTRAINTS:
{business_context_text}

BUSINESS LOGIC RULES:
{logic_rules_text}
{tagged_rules_text}

Based on this comprehensive analysis, provide detailed technology recommendations as a JSON object:

{{
  "technology_recommendations": {{
    "frontend": {{
      "framework": "recommended framework",
      "libraries": ["lib1", "lib2", "lib3"],
      "reasoning": "detailed reasoning based on requirements and business context"
    }},
    "backend": {{
      "framework": "recommended backend framework",
      "language": "programming language",
      "libraries": ["lib1", "lib2", "lib3"],
      "reasoning": "detailed reasoning based on complexity and business needs"
    }},
    "database": {{
      "primary": "primary database choice",
      "secondary": ["cache", "search", "analytics"],
      "reasoning": "database choice based on data requirements and scale"
    }},
    "infrastructure": {{
      "cloud_provider": "recommended cloud provider",
      "orchestration": "container/orchestration choice",
      "services": ["service1", "service2", "service3"],
      "reasoning": "infrastructure reasoning based on scale and budget"
    }},
    "security": {{
      "authentication": "auth strategy",
      "authorization": "authorization approach",
      "data_protection": "data protection measures",
      "compliance": "compliance approach",
      "reasoning": "security reasoning based on business context"
    }},
    "third_party_services": {{
      "communication": "communication services",
      "monitoring": "monitoring solution",
      "payment": "payment processing",
      "other_services": ["service1", "service2"],
      "reasoning": "third-party service reasoning"
    }}
  }},
  "implementation_strategy": {{
    "architecture_pattern": "recommended architecture pattern",
    "development_phases": ["phase1", "phase2", "phase3"],
    "deployment_strategy": "deployment approach",
    "scalability_approach": "scalability strategy",
    "timeline_estimate": "development timeline estimate"
  }},
  "business_alignment": {{
    "addresses_scale_requirements": "how recommendations address scale needs",
    "addresses_security_requirements": "how recommendations address security needs", 
    "addresses_budget_constraints": "how recommendations fit budget",
    "addresses_timeline_constraints": "how recommendations fit timeline",
    "compliance_considerations": "compliance alignment"
  }}
}}

CRITICAL: Return ONLY valid JSON, no additional text. Base all recommendations on the provided functional requirements and business context."""

    try:
        logger.info("📞 Calling Claude for enhanced recommendations with functional requirements and tagged rules...")
        message = enhanced_selector.claude_client.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=8000,
            temperature=0.1,
            messages=[{"role": "user", "content": prompt}]
        )
        
        claude_response = message.content[0].text.strip()
        logger.info("✅ Received Claude response for enhanced recommendations")
        
        try:
            recommendations = json.loads(claude_response)
            logger.info("✅ Successfully parsed enhanced recommendations JSON")
            return recommendations
        except json.JSONDecodeError as e:
            logger.error(f"❌ JSON parse error: {e}")
            return {
                "parse_error": str(e),
                "raw_response": claude_response[:1000] + "..." if len(claude_response) > 1000 else claude_response
            }
            
    except Exception as e:
        logger.error(f"❌ Claude API error: {e}")
        return {
            "error": str(e),
            "fallback": "Enhanced recommendations generation failed"
        }

# ================================================================================================
# MAIN ENTRY POINT
# ================================================================================================

if __name__ == "__main__":
    import uvicorn
    
    logger.info("="*60)
    logger.info("🚀 ENHANCED TECH STACK SELECTOR v13.0 - POSTGRESQL INTEGRATED")
    logger.info("="*60)
    logger.info("✅ FastAPI application")
    logger.info("✅ Neo4j service integration")
    logger.info("✅ PostgreSQL migration service")
    logger.info("✅ Claude AI recommendations")
    logger.info("✅ All endpoints integrated")
    logger.info("✅ Enhanced data extraction and tagged rules")
    logger.info("✅ PostgreSQL table initialization on startup")
    logger.info("="*60)
    
    uvicorn.run("main:app", host="0.0.0.0", port=8002, log_level="info")