import os
import io
import uvicorn
from typing import Optional
from fastapi import FastAPI, Query, Request, HTTPException, UploadFile, File, Form
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from dotenv import load_dotenv
from PIL import Image
from datetime import datetime
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded

# Import internal modules
from fitness_app.vector_store import MealGenVectorStore, ScannerVectorStore
from fitness_app.data_manager import (
    MealPlanResponse, SwapMealRequest, SwapMealResponse,
    MealImageRequest, MealImageResponse, FoodScanResponse,
    DailyNutritionResponse, Macronutrients
)
from fitness_app.ai_services import EnhancedMealPlanGenerator, FoodScanner
from translation_service import translation_service

load_dotenv()

limiter = Limiter(key_func=get_remote_address, storage_uri=os.getenv("REDIS_URL", "redis://localhost:6379"))
app = FastAPI(title="BloodFIT Meal Plan Generator - Scalable API")
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

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

# ----------------------------------------------------------------------------
# Initialize vector stores and AI services
# ----------------------------------------------------------------------------
meal_store = MealGenVectorStore(index_path="faiss_mealgen")
meal_store.load_or_create_index("diet/meal_generation/mealgen_foods.json", force_rebuild=False)  # Force rebuild to ensure new structure is indexed

scanner_store = ScannerVectorStore(index_path="faiss_scanner")
scanner_store.load_or_create_index("diet/meal_scanner/scanner_foods.json", force_rebuild=False)  # Force rebuild to ensure new structure is indexed

meal_generator = EnhancedMealPlanGenerator(meal_store)
food_scanner = FoodScanner(scanner_store)

# ----------------------------------------------------------------------------
# Helper functions
# ----------------------------------------------------------------------------
def normalize_blood_type(blood_type: str) -> str:
    blood_type = blood_type.strip().upper()
    return blood_type.split("+")[0].split("-")[0]

def normalize_sub_category(sub_category: str) -> str:
    sub_category = sub_category.lower().strip()
    mapping = {
        "protein-packed": "protein_packed",
        "protein packed": "protein_packed",
        "light & fresh": "light_fresh",
        "light and fresh": "light_fresh",
        "healthy & comforting": "healthy_comforting",
        "healthy and comforting": "healthy_comforting"
    }
    return mapping.get(sub_category, sub_category.replace(' ', '_').replace('-', '_'))

def translate_response(response_data: dict, language: str, response_type: str = 'meal_plan') -> dict:
    if language == 'en' or not translation_service.is_translation_enabled():
        return response_data
    if response_type == 'meal_plan':
        return translation_service.translate_meal_plan_response(response_data, language)
    elif response_type == 'food_scan':
        return translation_service.translate_food_scan_response(response_data, language)
    elif response_type == 'swap_meal':
        translated_alternatives = [
            translation_service.translate_meal(meal, language)
            for meal in response_data.get('alternatives', [])
        ]
        response_data['alternatives'] = translated_alternatives
        return response_data
    return response_data

def get_language_from_request(request: Request) -> str:
    return request.query_params.get('language', 'en')

# ----------------------------------------------------------------------------
# Endpoints
# ----------------------------------------------------------------------------

@app.get("/calculate-daily-nutrition", response_model=DailyNutritionResponse)
@limiter.limit("20/minute")   # per IP per minute
async def calculate_daily_nutrition(
    request: Request,
    user_id: str = Query(...),
    blood_type: str = Query(...),
    diet_type: str = Query(...),
    age: Optional[int] = Query(None),
    weight: Optional[float] = Query(None),
    height: Optional[float] = Query(None),
    country: str = Query(""),
    food_dislikes: str = Query(""),
    allergies: str = Query(""),
    main_goal: Optional[str] = Query("Stay Fit"),
    desired_weight: Optional[float] = Query(None, gt=0, le=300)
):
    language = get_language_from_request(request)
    blood_type = normalize_blood_type(blood_type)
    diet_type = diet_type.lower()

    valid_blood_types = ["O", "A", "B", "AB"]
    valid_diet_types = ["classic", "vegan", "pescatarian", "vegetarian"]

    if blood_type not in valid_blood_types:
        error_msg = "Invalid blood type. Must be O, A, B, or AB."
        if language == 'ko':
            error_msg = "잘못된 혈액형입니다. O, A, B, AB 중 하나여야 합니다."
        raise HTTPException(status_code=400, detail=error_msg)

    if diet_type not in valid_diet_types:
        error_msg = "Invalid diet type. Must be classic, vegan, pescatarian,  or vegetarian."
        if language == 'ko':
            error_msg = "잘못된 식단 유형입니다. 클래식, 비건, 페스카테리안, 카니보어, 비건 중 하나여야 합니다."
        raise HTTPException(status_code=400, detail=error_msg)
    
    if diet_type == "classic":
        diet_type = "standard"

    user_data = {
        "user_id": user_id,
        "blood_type": blood_type,
        "diet_type": diet_type,
        "age": age,
        "weight": weight,
        "height": height,
        "country": country,
        "food_dislikes": food_dislikes,
        "allergies": allergies,
        "main_goal": main_goal,
        "desired_weight": desired_weight
    }

    nutrition_response = await meal_generator.calculate_daily_nutrition(user_data)
    return nutrition_response


@app.api_route("/generate-meal-plan", methods=["GET", "POST"], response_model=MealPlanResponse)
@limiter.limit("10/minute")   # stricter limit for expensive operation
async def generate_meal_plan(
    request: Request,
    user_id: str = Query(...),
    blood_type: str = Query(...),
    diet_type: str = Query(...),
    age: Optional[int] = Query(None),
    weight: Optional[float] = Query(None),
    height: Optional[float] = Query(None),
    country: str = Query(""),
    food_dislikes: str = Query(""),
    allergies: str = Query(""),
    blood_group_rules: Optional[str] = Query(None),
    total_daily_calories: Optional[int] = Query(None),
    carbs: Optional[float] = Query(None),
    protein: Optional[float] = Query(None),
    fat: Optional[float] = Query(None),
    breakfast_calories: Optional[int] = Query(None),
    lunch_calories: Optional[int] = Query(None),
    dinner_calories: Optional[int] = Query(None),
    generate_images: bool = Query(False),
    main_goal: Optional[str] = Query("Stay Fit")
):
    language = get_language_from_request(request)
    blood_type = normalize_blood_type(blood_type)
    diet_type = diet_type.lower()

    # Prefer query; for POST FormData also accept body field
    if not blood_group_rules:
        if request.method == "POST":
            try:
                form = await request.form()
                blood_group_rules = form.get("blood_group_rules") or None
            except Exception:
                pass

    valid_blood_types = ["O", "A", "B", "AB"]
    valid_diet_types = ["classic", "vegan", "pescatarian", "vegetarian"]

    if blood_type not in valid_blood_types:
        error_msg = "Invalid blood type. Must be O, A, B, or AB."
        if language == 'ko':
            error_msg = "잘못된 혈액형입니다. O, A, B, AB 중 하나여야 합니다."
        raise HTTPException(status_code=400, detail=error_msg)

    if diet_type not in valid_diet_types:
        error_msg = "Invalid diet type. Must be classic, vegan, pescatarian,  or vegetarian."
        if language == 'ko':
            error_msg = "잘못된 식단 유형입니다. 클래식, 비건, 페스카테리안, 카니보어, 비건 중 하나여야 합니다."
        raise HTTPException(status_code=400, detail=error_msg)
    
    if diet_type == "classic":
        diet_type = "standard"

    user_data = {
        "user_id": user_id,
        "blood_type": blood_type,
        "diet_type": diet_type,
        "age": age,
        "weight": weight,
        "height": height,
        "country": country,
        "food_dislikes": food_dislikes,
        "allergies": allergies,
        "blood_group_rules": blood_group_rules,
        "main_goal": main_goal
    }

    daily_nutrition = None
    if total_daily_calories and carbs and protein and fat:
        meal_distribution = {}
        if breakfast_calories:
            meal_distribution["breakfast"] = breakfast_calories
        if lunch_calories:
            meal_distribution["lunch"] = lunch_calories
        if dinner_calories:
            meal_distribution["dinner"] = dinner_calories

        daily_nutrition = DailyNutritionResponse(
            total_daily_calories=total_daily_calories,
            total_daily_macronutrients=Macronutrients(
                carbohydrates=carbs,
                protein=protein,
                fat=fat
            ),
            user_id=user_id,
            blood_type=blood_type,
            diet_type=diet_type,
            calculation_timestamp=datetime.now(),
            meal_calorie_distribution=meal_distribution if meal_distribution else None
        )

    try:
        meal_plan_response = await meal_generator.generate_meal_plan(
            user_data,
            daily_nutrition,
            generate_images=generate_images
        )

        response_dict = meal_plan_response.dict()
        translated_response = translate_response(response_dict, language, 'meal_plan')
        return JSONResponse(content=translated_response)

    except Exception as e:
        error_msg = f"Meal plan generation failed: {str(e)}"
        if language == 'ko':
            error_msg = f"식단 계획 생성 실패: {str(e)}"
        raise HTTPException(status_code=500, detail=error_msg)


@app.get("/swap-meal", response_model=SwapMealResponse)
@limiter.limit("15/minute")
async def swap_meal(
    request: Request,
    user_id: str = Query(...),
    blood_type: str = Query(...),
    diet_type: str = Query(...),
    category: str = Query(...),
    sub_category: str = Query(...),
    current_calories: int = Query(...),
    country: str = Query(""),
    food_dislikes: str = Query(""),
    allergies: str = Query(""),
    generate_images: bool = Query(False)
):
    language = get_language_from_request(request)
    blood_type = normalize_blood_type(blood_type)
    diet_type = diet_type.lower()
    category = category.lower()
    sub_category = normalize_sub_category(sub_category)

    valid_blood_types = ["O", "A", "B", "AB"]
    valid_diet_types = ["classic", "vegan", "pescatarian", "vegetarian"]

    if blood_type not in valid_blood_types:
        error_msg = "Invalid blood type. Must be O, A, B, or AB."
        if language == 'ko':
            error_msg = "잘못된 혈액형입니다. O, A, B, AB 중 하나여야 합니다."
        raise HTTPException(status_code=400, detail=error_msg)

    if diet_type not in valid_diet_types:
        error_msg = "Invalid diet type. Must be classic, vegan, pescatarian,  or vegetarian."
        if language == 'ko':
            error_msg = "잘못된 식단 유형입니다. 클래식, 비건, 페스카테리안, 카니보어, 비건 중 하나여야 합니다."
        raise HTTPException(status_code=400, detail=error_msg)
    
    if diet_type == "classic":
        diet_type = "standard"

    swap_request = SwapMealRequest(
        user_id=user_id,
        blood_type=blood_type,
        diet_type=diet_type,
        category=category,
        sub_category=sub_category,
        current_calories=current_calories,
        country=country,
        food_dislikes=food_dislikes,
        allergies=allergies
    )

    try:
        # Pass generate_images to the generator
        swap_response = await meal_generator.generate_swap_meals(swap_request, generate_images=generate_images)
        response_dict = swap_response.dict()
        translated_response = translate_response(response_dict, language, 'swap_meal')
        return JSONResponse(content=translated_response)

    except Exception as e:
        error_msg = f"Swap meal generation failed: {str(e)}"
        if language == 'ko':
            error_msg = f"대체 식사 생성 실패: {str(e)}"
        raise HTTPException(status_code=500, detail=error_msg)


@app.post("/generate-meal-images", response_model=MealImageResponse)
@limiter.limit("15/minute")
async def generate_meal_images(
    request: Request,
    meal_request: MealImageRequest
):
    try:
        language = get_language_from_request(request)
        ingredients_list = [
            {"name": ing.name, "quantity": ing.quantity or "some"}
            for ing in meal_request.ingredients
        ]

        meal_image_base64 = meal_generator.meal_image_generator.generate_meal_image(
            meal_request.meal_name,
            meal_request.description,
            ingredients_list
        )

        # Return the ID if provided, otherwise None
        return MealImageResponse(
            meal_image_base64=meal_image_base64,
            id=meal_request.id
        )

    except Exception as e:
        error_msg = f"Image generation failed: {str(e)}"
        language = get_language_from_request(request)
        if language == 'ko':
            error_msg = f"이미지 생성 실패: {str(e)}"
        raise HTTPException(status_code=500, detail=error_msg)


@app.post("/scan-food", response_model=FoodScanResponse)
@limiter.limit("10/minute")
async def scan_food(
    request: Request,
    user_id: str = Query(...),
    blood_type: str = Query(...),
    diet_type: str = Query(...),
    country: str = Query(""),
    allergies: str = Query(""),
    food_dislikes: str = Query(""),
    blood_group_rules: Optional[str] = Form(None),
    image: UploadFile = File(...)
):
    language = get_language_from_request(request)
    blood_type = normalize_blood_type(blood_type)
    diet_type = diet_type.lower()

    # FormData preferred; also allow query-string fallback
    if not blood_group_rules:
        blood_group_rules = request.query_params.get("blood_group_rules") or None

    valid_blood_types = ["O", "A", "B", "AB"]
    valid_diet_types = ["classic", "vegan", "pescatarian", "vegetarian"]

    if blood_type not in valid_blood_types:
        error_msg = "Invalid blood type. Must be O, A, B, or AB."
        if language == 'ko':
            error_msg = "잘못된 혈액형입니다. O, A, B, AB 중 하나여야 합니다."
        raise HTTPException(status_code=400, detail=error_msg)

    if diet_type not in valid_diet_types:
        error_msg = "Invalid diet type. Must be classic, vegan, pescatarian,  or vegetarian."
        if language == 'ko':
            error_msg = "잘못된 식단 유형입니다. 클래식, 비건, 페스카테리안, 카니보어, 비건 중 하나여야 합니다."
        raise HTTPException(status_code=400, detail=error_msg)
    
    if diet_type == "classic":
        diet_type = "standard"

    if not image.content_type.startswith('image/'):
        error_msg = "File must be an image"
        if language == 'ko':
            error_msg = "파일은 이미지여야 합니다"
        raise HTTPException(status_code=400, detail=error_msg)

    try:
        image_data = await image.read()

        # Validate image
        try:
            img = Image.open(io.BytesIO(image_data))
            img.verify()
        except Exception:
            error_msg = "Invalid image file"
            if language == 'ko':
                error_msg = "잘못된 이미지 파일입니다"
            raise HTTPException(status_code=400, detail=error_msg)

        img = Image.open(io.BytesIO(image_data))
        if img.width < 100 or img.height < 100:
            error_msg = "Image too small. Minimum 100x100 pixels required."
            if language == 'ko':
                error_msg = "이미지가 너무 작습니다. 최소 100x100 픽셀이 필요합니다."
            raise HTTPException(status_code=400, detail=error_msg)

        if img.mode != 'RGB':
            img = img.convert('RGB')

        img.close()

    except HTTPException:
        raise
    except Exception as e:
        error_msg = f"Error processing image: {str(e)}"
        if language == 'ko':
            error_msg = f"이미지 처리 오류: {str(e)}"
        raise HTTPException(status_code=400, detail=error_msg)

    user_data = {
        "user_id": user_id,
        "blood_type": blood_type,
        "diet_type": diet_type,
        "country": country,
        "food_dislikes": food_dislikes,
        "allergies": allergies,
        "blood_group_rules": blood_group_rules,
    }

    try:
        scan_response = await food_scanner.scan_food_image(image_data, user_data)
        response_dict = scan_response.dict()
        translated_response = translate_response(response_dict, language, 'food_scan')
        return JSONResponse(content=translated_response)

    except Exception as e:
        error_msg = f"Food scanning failed: {str(e)}"
        if language == 'ko':
            error_msg = f"음식 스캔 실패: {str(e)}"
        raise HTTPException(status_code=500, detail=error_msg)


@app.get("/health")
async def health_check(request: Request):
    """Health check endpoint – now uses vector store stats."""
    language = get_language_from_request(request)

    # Get basic stats from vector store
    index_loaded = meal_store.vectorstore is not None
    doc_count = len(meal_store.vectorstore.index_to_docstore_id) if index_loaded else 0

    status_message = "healthy" if index_loaded else "degraded"
    if language == 'ko':
        status_message = "정상" if index_loaded else "저하됨"

    return {
        "status": status_message,
        "service": "BloodFIT Meal Plan API",
        "language": language,
        "vector_store_loaded": index_loaded,
        "total_document_chunks": doc_count,
        "new_structure": "9 meals per category (3 per sub‑category × 3 sub‑categories)",
        "sub_categories": ["Protein-Packed", "Light & Fresh", "Healthy & Comforting"],
        "total_meals_per_plan": 27,
        "endpoints": {
            "/meal/calculate-daily-nutrition": "Calculate daily nutrition with meal distribution",
            "/meal/generate-meal-plan": "Generate 27‑meal plan with new structure",
            "/meal/swap-meal": "Swap meals with sub‑category support",
            "/meal/scan-food": "Scan food images",
            "/meal/generate-meal-images": "Generate meal images"
        },
        "translation_enabled": translation_service.is_translation_enabled()
    }


@app.get("/")
async def home(request: Request):
    """API Documentation"""
    language = get_language_from_request(request)

    if language == 'ko':
        return {
            "message": "BloodFIT 식단 계획 API",
            "version": "3.0 (Vector Store)",
            "구조": "각 카테고리당 9개 식사 옵션 (하위 카테고리당 3개 × 3개 하위 카테고리)",
            "하위_카테고리": ["단백질 풍부", "가볍고 신선함", "건강하고 편안함"],
            "총_식사_옵션": 27,
            "엔드포인트": {
                "GET /meal/calculate-daily-nutrition": "일일 영양소 목표 계산",
                "GET /meal/generate-meal-plan": "새로운 구조로 식단 계획 생성",
                "GET /meal/swap-meal": "동일한 하위 카테고리에서 대체 식사 생성",
                "POST /meal/scan-food": "음식 이미지 스캔",
                "POST /meal/generate-meal-images": "식사 이미지 생성"
            },
            "사용법": [
                "1. /meal/calculate-daily-nutrition 호출하여 일일 영양소 목표 계산",
                "2. 결과를 /meal/generate-meal-plan에 쿼리 매개변수로 전달",
                "3. 또는 /meal/generate-meal-plan 직접 호출하여 자동 계산"
            ]
        }
    else:
        return {
            "message": "BloodFIT Meal Plan API",
            "version": "3.0 (Vector Store)",
            "structure": "9 meals per category (3 per sub‑category × 3 sub‑categories)",
            "sub_categories": ["Protein-Packed", "Light & Fresh", "Healthy & Comforting"],
            "total_meal_options": 27,
            "endpoints": {
                "GET /meal/calculate-daily-nutrition": "Calculate daily nutrition targets",
                "GET /meal/generate-meal-plan": "Generate meal plan with new structure",
                "GET /meal/swap-meal": "Generate alternative meals within same sub‑category",
                "POST /meal/scan-food": "Scan food image for harmful ingredients",
                "POST /meal/generate-meal-images": "Generate meal images"
            },
            "usage_flow": [
                "1. Call /meal/calculate-daily-nutrition to get daily nutrition targets",
                "2. Pass the results to /meal/generate-meal-plan using query parameters",
                "3. Or call /meal/generate-meal-plan directly for automatic calculation"
            ]
        }