from datetime import date, datetime, timedelta
from typing import Dict, List, Optional

from fastapi import Depends, FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import and_, desc, select
from sqlalchemy.ext.asyncio import AsyncSession

from services.calendar_service.models import CalendarEntry, CycleData, HealthInsights
from services.calendar_service.schemas import (CalendarEntryCreate, CalendarEntryResponse, 
                                               CycleDataResponse, CycleOverview, EntryType, 
                                               FlowIntensity, HealthInsightResponse, MoodType)
from shared.auth import get_current_user_from_token as get_current_user
from shared.config import settings
from shared.database import get_db

app = FastAPI(title="Calendar Service", version="1.0.0")

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


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


class CycleDataResponse(BaseModel):
    id: int
    cycle_start_date: date
    cycle_length: Optional[int]
    period_length: Optional[int]
    ovulation_date: Optional[date]
    fertile_window_start: Optional[date]
    fertile_window_end: Optional[date]
    next_period_predicted: Optional[date]
    avg_cycle_length: Optional[int]
    avg_period_length: Optional[int]

    class Config:
        from_attributes = True


class HealthInsightResponse(BaseModel):
    id: int
    insight_type: str
    title: str
    description: str
    recommendation: Optional[str]
    confidence_level: str
    created_at: datetime

    class Config:
        from_attributes = True


class CycleOverview(BaseModel):
    current_cycle_day: Optional[int]
    current_phase: str  # menstrual, follicular, ovulation, luteal
    next_period_date: Optional[date]
    days_until_period: Optional[int]
    cycle_regularity: str  # very_regular, regular, irregular, very_irregular
    avg_cycle_length: Optional[int]


def calculate_cycle_phase(
    cycle_start: date, cycle_length: int, current_date: date
) -> str:
    """Calculate current cycle phase"""
    days_since_start = (current_date - cycle_start).days

    if days_since_start <= 5:
        return "menstrual"
    elif days_since_start <= cycle_length // 2 - 2:
        return "follicular"
    elif cycle_length // 2 - 2 < days_since_start <= cycle_length // 2 + 2:
        return "ovulation"
    else:
        return "luteal"


async def calculate_predictions(user_id: int, db: AsyncSession):
    """Calculate cycle predictions based on historical data"""
    # Get last 6 cycles for calculations
    cycles = await db.execute(
        select(CycleData)
        .filter(CycleData.user_id == user_id)
        .order_by(desc(CycleData.cycle_start_date))
        .limit(6)
    )
    cycle_list = cycles.scalars().all()

    if len(cycle_list) < 2:
        return None

    # Calculate averages
    cycle_lengths = [c.cycle_length for c in cycle_list if c.cycle_length]
    period_lengths = [c.period_length for c in cycle_list if c.period_length]

    if not cycle_lengths:
        return None

    avg_cycle = sum(cycle_lengths) / len(cycle_lengths)
    avg_period = sum(period_lengths) / len(period_lengths) if period_lengths else 5

    # Predict next period
    last_cycle = cycle_list[0]
    next_period_date = last_cycle.cycle_start_date + timedelta(days=int(avg_cycle))

    return {
        "avg_cycle_length": int(avg_cycle),
        "avg_period_length": int(avg_period),
        "next_period_predicted": next_period_date,
        "ovulation_date": last_cycle.cycle_start_date
        + timedelta(days=int(avg_cycle // 2)),
    }


@app.post("/api/v1/entries", response_model=CalendarEntryResponse)
async def create_calendar_entry(
    entry_data: CalendarEntryCreate,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Create new calendar entry"""

    # Check if entry already exists for this date and type
    existing = await db.execute(
        select(CalendarEntry).filter(
            and_(
                CalendarEntry.user_id == current_user.id,
                CalendarEntry.entry_date == entry_data.entry_date,
                CalendarEntry.entry_type == entry_data.entry_type.value,
            )
        )
    )
    if existing.scalars().first():
        raise HTTPException(
            status_code=400, detail="Entry already exists for this date and type"
        )

    db_entry = CalendarEntry(
        user_id=current_user.id,
        entry_date=entry_data.entry_date,
        entry_type=entry_data.entry_type.value,
        flow_intensity=entry_data.flow_intensity.value
        if entry_data.flow_intensity
        else None,
        period_symptoms=entry_data.period_symptoms,
        mood=entry_data.mood.value if entry_data.mood else None,
        energy_level=entry_data.energy_level,
        sleep_hours=entry_data.sleep_hours,
        symptoms=entry_data.symptoms,
        medications=entry_data.medications,
        notes=entry_data.notes,
    )

    db.add(db_entry)
    await db.commit()
    await db.refresh(db_entry)

    # If this is a period entry, update cycle data
    if entry_data.entry_type == EntryType.PERIOD:
        await update_cycle_data(current_user.id, entry_data.entry_date, db)

    return CalendarEntryResponse.model_validate(db_entry)


async def update_cycle_data(user_id: int, period_date: date, db: AsyncSession):
    """Update cycle data when period is logged"""

    # Get last cycle
    last_cycle = await db.execute(
        select(CycleData)
        .filter(CycleData.user_id == user_id)
        .order_by(desc(CycleData.cycle_start_date))
        .limit(1)
    )
    last_cycle_data = last_cycle.scalars().first()

    if last_cycle_data:
        # Calculate cycle length
        cycle_length = (period_date - last_cycle_data.cycle_start_date).days
        last_cycle_data.cycle_length = cycle_length

    # Create new cycle
    predictions = await calculate_predictions(user_id, db)

    new_cycle = CycleData(
        user_id=user_id,
        cycle_start_date=period_date,
        avg_cycle_length=predictions["avg_cycle_length"] if predictions else None,
        next_period_predicted=predictions["next_period_predicted"]
        if predictions
        else None,
        ovulation_date=predictions["ovulation_date"] if predictions else None,
    )

    db.add(new_cycle)
    await db.commit()


@app.get("/api/v1/entries", response_model=List[CalendarEntryResponse])
async def get_calendar_entries(
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
    start_date: Optional[date] = Query(None),
    end_date: Optional[date] = Query(None),
    entry_type: Optional[EntryType] = Query(None),
    limit: int = Query(100, ge=1, le=365),
):
    """Get calendar entries with optional filtering"""

    query = select(CalendarEntry).filter(CalendarEntry.user_id == current_user.id)

    if start_date:
        query = query.filter(CalendarEntry.entry_date >= start_date)
    if end_date:
        query = query.filter(CalendarEntry.entry_date <= end_date)
    if entry_type:
        query = query.filter(CalendarEntry.entry_type == entry_type.value)

    query = query.order_by(desc(CalendarEntry.entry_date)).limit(limit)

    result = await db.execute(query)
    entries = result.scalars().all()

    return [CalendarEntryResponse.model_validate(entry) for entry in entries]


@app.get("/api/v1/cycle-overview", response_model=CycleOverview)
async def get_cycle_overview(
    current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)
):
    """Get current cycle overview and predictions"""

    # Get current cycle
    current_cycle = await db.execute(
        select(CycleData)
        .filter(CycleData.user_id == current_user.id)
        .order_by(desc(CycleData.cycle_start_date))
        .limit(1)
    )
    cycle_data = current_cycle.scalars().first()

    if not cycle_data:
        return CycleOverview(
            current_cycle_day=None,
            current_phase="unknown",
            next_period_date=None,
            days_until_period=None,
            cycle_regularity="unknown",
            avg_cycle_length=None,
        )

    today = date.today()
    current_cycle_day = (today - cycle_data.cycle_start_date).days + 1

    # Calculate current phase
    cycle_length = cycle_data.avg_cycle_length or 28
    current_phase = calculate_cycle_phase(
        cycle_data.cycle_start_date, cycle_length, today
    )

    # Days until next period
    next_period_date = cycle_data.next_period_predicted
    days_until_period = None
    if next_period_date:
        days_until_period = (next_period_date - today).days

    # Calculate regularity
    cycles = await db.execute(
        select(CycleData)
        .filter(CycleData.user_id == current_user.id)
        .order_by(desc(CycleData.cycle_start_date))
        .limit(6)
    )
    cycle_list = cycles.scalars().all()

    regularity = "unknown"
    if len(cycle_list) >= 3:
        lengths = [c.cycle_length for c in cycle_list if c.cycle_length]
        if lengths:
            variance = max(lengths) - min(lengths)
            if variance <= 2:
                regularity = "very_regular"
            elif variance <= 5:
                regularity = "regular"
            elif variance <= 10:
                regularity = "irregular"
            else:
                regularity = "very_irregular"

    return CycleOverview(
        current_cycle_day=current_cycle_day,
        current_phase=current_phase,
        next_period_date=next_period_date,
        days_until_period=days_until_period,
        cycle_regularity=regularity,
        avg_cycle_length=cycle_data.avg_cycle_length,
    )


@app.get("/api/v1/insights", response_model=List[HealthInsightResponse])
async def get_health_insights(
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
    limit: int = Query(10, ge=1, le=50),
):
    """Get personalized health insights"""

    result = await db.execute(
        select(HealthInsights)
        .filter(
            HealthInsights.user_id == current_user.id,
            HealthInsights.is_dismissed == False,
        )
        .order_by(desc(HealthInsights.created_at))
        .limit(limit)
    )
    insights = result.scalars().all()

    return [HealthInsightResponse.model_validate(insight) for insight in insights]


@app.get("/api/v1/calendar/entries", response_model=List[CalendarEntryResponse])
async def get_all_calendar_entries(
    start_date: Optional[date] = None,
    end_date: Optional[date] = None,
    entry_type: Optional[EntryType] = None,
    current_user: Dict = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
    limit: int = Query(100, ge=1, le=500),
):
    """Get all calendar entries for the current user"""
    
    query = select(CalendarEntry).filter(CalendarEntry.user_id == current_user["user_id"])
    
    if start_date:
        query = query.filter(CalendarEntry.entry_date >= start_date)
    if end_date:
        query = query.filter(CalendarEntry.entry_date <= end_date)
    if entry_type:
        query = query.filter(CalendarEntry.entry_type == entry_type)
    
    query = query.order_by(CalendarEntry.entry_date.desc()).limit(limit)
    
    result = await db.execute(query)
    entries = result.scalars().all()
    
    return [CalendarEntryResponse.model_validate(entry) for entry in entries]


@app.post("/api/v1/calendar/entries", response_model=CalendarEntryResponse, status_code=201)
async def create_calendar_entry(
    entry_data: CalendarEntryCreate,
    current_user: Dict = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Create a new calendar entry"""
    
    # Check if entry already exists for this date and type
    existing = await db.execute(
        select(CalendarEntry).filter(
            and_(
                CalendarEntry.user_id == current_user.id,
                CalendarEntry.entry_date == entry_data.entry_date,
                CalendarEntry.entry_type == entry_data.entry_type.value,
            )
        )
    )
    if existing.scalars().first():
        raise HTTPException(
            status_code=400, detail="Entry already exists for this date and type"
        )
    
    # Create new calendar entry
    new_entry = CalendarEntry(
        user_id=current_user.id,
        entry_date=entry_data.entry_date,
        entry_type=entry_data.entry_type.value,
        flow_intensity=entry_data.flow_intensity.value if entry_data.flow_intensity else None,
        period_symptoms=entry_data.period_symptoms,
        mood=entry_data.mood.value if entry_data.mood else None,
        energy_level=entry_data.energy_level,
        sleep_hours=entry_data.sleep_hours,
        symptoms=entry_data.symptoms,
        medications=entry_data.medications,
        notes=entry_data.notes,
    )
    
    db.add(new_entry)
    await db.commit()
    await db.refresh(new_entry)
    
    # If this is a period entry, update cycle data
    if entry_data.entry_type == EntryType.PERIOD:
        await update_cycle_data(current_user.id, entry_data.entry_date, db)
    
    return CalendarEntryResponse.model_validate(new_entry)


@app.delete("/api/v1/entries/{entry_id}")
async def delete_calendar_entry(
    entry_id: int,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Delete calendar entry"""

    result = await db.execute(
        select(CalendarEntry).filter(
            and_(CalendarEntry.id == entry_id, CalendarEntry.user_id == current_user.id)
        )
    )
    entry = result.scalars().first()

    if not entry:
        raise HTTPException(status_code=404, detail="Entry not found")

    await db.delete(entry)
    await db.commit()

    return {"message": "Entry deleted successfully"}


@app.get("/api/v1/health")
async def health_check():
    """Health check endpoint"""
    return {"status": "healthy", "service": "calendar-service"}


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8004)
