"""Vendor Cost Tracker Router - Track and analyze AI API spending"""
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel
from typing import Optional
from datetime import datetime, date, timedelta
import uuid
from collections import defaultdict

router = APIRouter()

# In-memory storage for cost entries and alerts
cost_entries: list = []
budget_alerts: dict = {}

# Comprehensive pricing data (per 1M tokens or per 1000 requests)
PROVIDER_PRICING = {
    "openai": {
        "gpt-4o": {"input": 2.50, "output": 10.00, "unit": "1M tokens"},
        "gpt-4o-mini": {"input": 0.15, "output": 0.60, "unit": "1M tokens"},
        "gpt-4-turbo": {"input": 10.00, "output": 30.00, "unit": "1M tokens"},
        "gpt-4": {"input": 30.00, "output": 60.00, "unit": "1M tokens"},
        "gpt-3.5-turbo": {"input": 0.50, "output": 1.50, "unit": "1M tokens"},
        "text-embedding-3-small": {"input": 0.02, "output": 0.0, "unit": "1M tokens"},
        "text-embedding-3-large": {"input": 0.13, "output": 0.0, "unit": "1M tokens"},
        "whisper": {"input": 0.006, "output": 0.0, "unit": "per minute"},
        "dall-e-3": {"input": 0.04, "output": 0.0, "unit": "per image (1024x1024)"},
    },
    "anthropic": {
        "claude-opus-4": {"input": 15.00, "output": 75.00, "unit": "1M tokens"},
        "claude-sonnet-4": {"input": 3.00, "output": 15.00, "unit": "1M tokens"},
        "claude-3.5-sonnet": {"input": 3.00, "output": 15.00, "unit": "1M tokens"},
        "claude-3.5-haiku": {"input": 0.80, "output": 4.00, "unit": "1M tokens"},
        "claude-3-opus": {"input": 15.00, "output": 75.00, "unit": "1M tokens"},
        "claude-3-sonnet": {"input": 3.00, "output": 15.00, "unit": "1M tokens"},
        "claude-3-haiku": {"input": 0.25, "output": 1.25, "unit": "1M tokens"},
    },
    "google": {
        "gemini-2.0-flash": {"input": 0.10, "output": 0.40, "unit": "1M tokens"},
        "gemini-1.5-pro": {"input": 1.25, "output": 5.00, "unit": "1M tokens"},
        "gemini-1.5-flash": {"input": 0.075, "output": 0.30, "unit": "1M tokens"},
        "gemini-1.0-pro": {"input": 0.50, "output": 1.50, "unit": "1M tokens"},
    },
    "aws": {
        "bedrock-claude-3-opus": {"input": 15.00, "output": 75.00, "unit": "1M tokens"},
        "bedrock-claude-3-sonnet": {"input": 3.00, "output": 15.00, "unit": "1M tokens"},
        "bedrock-claude-3-haiku": {"input": 0.25, "output": 1.25, "unit": "1M tokens"},
        "bedrock-titan-text": {"input": 0.80, "output": 1.00, "unit": "1M tokens"},
        "bedrock-titan-embeddings": {"input": 0.10, "output": 0.0, "unit": "1M tokens"},
    },
    "azure": {
        "azure-gpt-4o": {"input": 2.50, "output": 10.00, "unit": "1M tokens"},
        "azure-gpt-4-turbo": {"input": 10.00, "output": 30.00, "unit": "1M tokens"},
        "azure-gpt-4": {"input": 30.00, "output": 60.00, "unit": "1M tokens"},
        "azure-gpt-35-turbo": {"input": 0.50, "output": 1.50, "unit": "1M tokens"},
    },
    "cohere": {
        "command-r-plus": {"input": 2.50, "output": 10.00, "unit": "1M tokens"},
        "command-r": {"input": 0.15, "output": 0.60, "unit": "1M tokens"},
        "embed-english-v3.0": {"input": 0.10, "output": 0.0, "unit": "1M tokens"},
    },
    "mistral": {
        "mistral-large": {"input": 2.00, "output": 6.00, "unit": "1M tokens"},
        "mistral-small": {"input": 0.20, "output": 0.60, "unit": "1M tokens"},
        "mistral-embed": {"input": 0.10, "output": 0.0, "unit": "1M tokens"},
    }
}


class CostEntry(BaseModel):
    provider: str
    model: Optional[str] = None
    amount: float
    input_tokens: Optional[int] = None
    output_tokens: Optional[int] = None
    requests: Optional[int] = None
    project: Optional[str] = "default"
    description: Optional[str] = None
    entry_date: date


class BudgetAlert(BaseModel):
    name: str
    provider: Optional[str] = None
    project: Optional[str] = None
    monthly_limit: float
    alert_threshold: float = 0.8


class CostSummary(BaseModel):
    total: float
    by_provider: dict
    by_project: dict
    by_model: dict
    daily_breakdown: list
    period_start: str
    period_end: str
    entry_count: int


class TokenUsageEstimate(BaseModel):
    provider: str
    model: str
    input_tokens: int
    output_tokens: int


@router.post("/log")
async def log_cost(entry: CostEntry):
    """Log a cost entry"""
    entry_id = str(uuid.uuid4())[:8]

    cost_record = {
        "id": entry_id,
        "provider": entry.provider.lower(),
        "model": entry.model,
        "amount": entry.amount,
        "input_tokens": entry.input_tokens,
        "output_tokens": entry.output_tokens,
        "requests": entry.requests,
        "project": entry.project or "default",
        "description": entry.description,
        "entry_date": entry.entry_date.isoformat(),
        "created_at": datetime.now().isoformat()
    }

    cost_entries.append(cost_record)

    # Check budget alerts
    triggered_alerts = check_budget_alerts(entry.provider, entry.project)

    return {
        "message": "Cost logged successfully",
        "entry_id": entry_id,
        "entry": cost_record,
        "alerts_triggered": triggered_alerts
    }


@router.post("/log-batch")
async def log_costs_batch(entries: list[CostEntry]):
    """Log multiple cost entries at once"""
    results = []
    for entry in entries:
        entry_id = str(uuid.uuid4())[:8]
        cost_record = {
            "id": entry_id,
            "provider": entry.provider.lower(),
            "model": entry.model,
            "amount": entry.amount,
            "input_tokens": entry.input_tokens,
            "output_tokens": entry.output_tokens,
            "requests": entry.requests,
            "project": entry.project or "default",
            "description": entry.description,
            "entry_date": entry.entry_date.isoformat(),
            "created_at": datetime.now().isoformat()
        }
        cost_entries.append(cost_record)
        results.append(cost_record)

    return {
        "message": f"Logged {len(results)} cost entries",
        "entries": results
    }


@router.get("/summary")
async def get_cost_summary(
    start_date: Optional[date] = None,
    end_date: Optional[date] = None,
    provider: Optional[str] = None,
    project: Optional[str] = None
):
    """Get cost summary for a period"""
    # Default to current month
    if not start_date:
        today = date.today()
        start_date = date(today.year, today.month, 1)
    if not end_date:
        end_date = date.today()

    # Filter entries
    filtered = []
    for entry in cost_entries:
        entry_date = date.fromisoformat(entry["entry_date"])
        if start_date <= entry_date <= end_date:
            if provider and entry["provider"] != provider.lower():
                continue
            if project and entry["project"] != project:
                continue
            filtered.append(entry)

    # Aggregate
    total = sum(e["amount"] for e in filtered)
    by_provider = defaultdict(float)
    by_project = defaultdict(float)
    by_model = defaultdict(float)
    daily = defaultdict(float)

    for entry in filtered:
        by_provider[entry["provider"]] += entry["amount"]
        by_project[entry["project"]] += entry["amount"]
        if entry["model"]:
            by_model[f"{entry['provider']}/{entry['model']}"] += entry["amount"]
        daily[entry["entry_date"]] += entry["amount"]

    # Sort daily breakdown
    daily_breakdown = [
        {"date": d, "amount": round(a, 2)}
        for d, a in sorted(daily.items())
    ]

    return {
        "total": round(total, 2),
        "by_provider": {k: round(v, 2) for k, v in sorted(by_provider.items(), key=lambda x: -x[1])},
        "by_project": {k: round(v, 2) for k, v in sorted(by_project.items(), key=lambda x: -x[1])},
        "by_model": {k: round(v, 2) for k, v in sorted(by_model.items(), key=lambda x: -x[1])},
        "daily_breakdown": daily_breakdown,
        "period_start": start_date.isoformat(),
        "period_end": end_date.isoformat(),
        "entry_count": len(filtered)
    }


@router.get("/entries")
async def get_cost_entries(
    limit: int = Query(100, le=1000),
    offset: int = 0,
    provider: Optional[str] = None,
    project: Optional[str] = None
):
    """Get individual cost entries with pagination"""
    filtered = cost_entries

    if provider:
        filtered = [e for e in filtered if e["provider"] == provider.lower()]
    if project:
        filtered = [e for e in filtered if e["project"] == project]

    # Sort by date descending
    filtered = sorted(filtered, key=lambda x: x["entry_date"], reverse=True)

    return {
        "entries": filtered[offset:offset + limit],
        "total": len(filtered),
        "limit": limit,
        "offset": offset
    }


@router.delete("/entries/{entry_id}")
async def delete_cost_entry(entry_id: str):
    """Delete a cost entry"""
    global cost_entries
    original_len = len(cost_entries)
    cost_entries = [e for e in cost_entries if e["id"] != entry_id]

    if len(cost_entries) == original_len:
        raise HTTPException(status_code=404, detail="Entry not found")

    return {"message": "Entry deleted", "entry_id": entry_id}


@router.get("/forecast")
async def forecast_costs(
    months: int = Query(3, ge=1, le=12),
    provider: Optional[str] = None,
    project: Optional[str] = None
):
    """Forecast future costs based on usage patterns"""
    if len(cost_entries) < 7:
        return {
            "message": "Need at least 7 days of data for forecasting",
            "forecast": [],
            "confidence": 0.0
        }

    # Get last 30 days of data
    today = date.today()
    thirty_days_ago = today - timedelta(days=30)

    recent = []
    for entry in cost_entries:
        entry_date = date.fromisoformat(entry["entry_date"])
        if entry_date >= thirty_days_ago:
            if provider and entry["provider"] != provider.lower():
                continue
            if project and entry["project"] != project:
                continue
            recent.append(entry)

    if not recent:
        return {
            "message": "No recent data for forecasting",
            "forecast": [],
            "confidence": 0.0
        }

    # Calculate daily average
    daily_totals = defaultdict(float)
    for entry in recent:
        daily_totals[entry["entry_date"]] += entry["amount"]

    daily_avg = sum(daily_totals.values()) / max(len(daily_totals), 1)

    # Simple linear forecast
    forecast = []
    for m in range(1, months + 1):
        # Days in forecast month
        forecast_date = today + timedelta(days=30 * m)
        days_in_month = 30  # Simplified

        # Add some variance for uncertainty
        base_forecast = daily_avg * days_in_month

        forecast.append({
            "month": forecast_date.strftime("%Y-%m"),
            "predicted_cost": round(base_forecast, 2),
            "lower_bound": round(base_forecast * 0.8, 2),
            "upper_bound": round(base_forecast * 1.2, 2)
        })

    # Confidence based on data points
    confidence = min(0.9, len(daily_totals) / 30)

    return {
        "daily_average": round(daily_avg, 2),
        "forecast": forecast,
        "confidence": round(confidence, 2),
        "based_on_days": len(daily_totals),
        "method": "linear_average"
    }


@router.post("/alerts")
async def set_budget_alert(alert: BudgetAlert):
    """Set budget alert thresholds"""
    alert_id = str(uuid.uuid4())[:8]

    alert_record = {
        "id": alert_id,
        "name": alert.name,
        "provider": alert.provider.lower() if alert.provider else None,
        "project": alert.project,
        "monthly_limit": alert.monthly_limit,
        "alert_threshold": alert.alert_threshold,
        "created_at": datetime.now().isoformat()
    }

    budget_alerts[alert_id] = alert_record

    return {
        "message": "Budget alert configured",
        "alert_id": alert_id,
        "alert": alert_record
    }


@router.get("/alerts")
async def get_budget_alerts():
    """Get all budget alerts with current status"""
    today = date.today()
    month_start = date(today.year, today.month, 1)

    alerts_with_status = []
    for alert in budget_alerts.values():
        # Calculate current spend for this alert's scope
        filtered = cost_entries
        if alert["provider"]:
            filtered = [e for e in filtered if e["provider"] == alert["provider"]]
        if alert["project"]:
            filtered = [e for e in filtered if e["project"] == alert["project"]]

        # Filter to current month
        monthly = [
            e for e in filtered
            if date.fromisoformat(e["entry_date"]) >= month_start
        ]

        current_spend = sum(e["amount"] for e in monthly)
        percent_used = (current_spend / alert["monthly_limit"] * 100) if alert["monthly_limit"] > 0 else 0

        status = "ok"
        if percent_used >= 100:
            status = "exceeded"
        elif percent_used >= alert["alert_threshold"] * 100:
            status = "warning"

        alerts_with_status.append({
            **alert,
            "current_spend": round(current_spend, 2),
            "percent_used": round(percent_used, 1),
            "remaining": round(max(0, alert["monthly_limit"] - current_spend), 2),
            "status": status
        })

    return {"alerts": alerts_with_status}


@router.delete("/alerts/{alert_id}")
async def delete_budget_alert(alert_id: str):
    """Delete a budget alert"""
    if alert_id not in budget_alerts:
        raise HTTPException(status_code=404, detail="Alert not found")

    del budget_alerts[alert_id]
    return {"message": "Alert deleted", "alert_id": alert_id}


def check_budget_alerts(provider: str, project: str) -> list:
    """Check if any budget alerts are triggered"""
    today = date.today()
    month_start = date(today.year, today.month, 1)

    triggered = []
    for alert in budget_alerts.values():
        # Check if alert applies
        if alert["provider"] and alert["provider"] != provider.lower():
            continue
        if alert["project"] and alert["project"] != project:
            continue

        # Calculate current spend
        filtered = cost_entries
        if alert["provider"]:
            filtered = [e for e in filtered if e["provider"] == alert["provider"]]
        if alert["project"]:
            filtered = [e for e in filtered if e["project"] == alert["project"]]

        monthly = [
            e for e in filtered
            if date.fromisoformat(e["entry_date"]) >= month_start
        ]

        current_spend = sum(e["amount"] for e in monthly)
        threshold_amount = alert["monthly_limit"] * alert["alert_threshold"]

        if current_spend >= threshold_amount:
            triggered.append({
                "alert_id": alert["id"],
                "alert_name": alert["name"],
                "current_spend": round(current_spend, 2),
                "limit": alert["monthly_limit"],
                "severity": "exceeded" if current_spend >= alert["monthly_limit"] else "warning"
            })

    return triggered


@router.post("/estimate")
async def estimate_cost(usage: TokenUsageEstimate):
    """Estimate cost for given token usage"""
    provider = usage.provider.lower()
    model = usage.model.lower()

    if provider not in PROVIDER_PRICING:
        raise HTTPException(status_code=400, detail=f"Unknown provider: {provider}")

    provider_models = PROVIDER_PRICING[provider]

    # Find matching model (fuzzy match)
    matched_model = None
    for m in provider_models:
        if m.lower() == model or model in m.lower():
            matched_model = m
            break

    if not matched_model:
        return {
            "error": f"Model '{model}' not found for provider '{provider}'",
            "available_models": list(provider_models.keys())
        }

    pricing = provider_models[matched_model]

    # Calculate cost (pricing is per 1M tokens)
    input_cost = (usage.input_tokens / 1_000_000) * pricing["input"]
    output_cost = (usage.output_tokens / 1_000_000) * pricing["output"]
    total_cost = input_cost + output_cost

    return {
        "provider": provider,
        "model": matched_model,
        "input_tokens": usage.input_tokens,
        "output_tokens": usage.output_tokens,
        "input_cost": round(input_cost, 6),
        "output_cost": round(output_cost, 6),
        "total_cost": round(total_cost, 6),
        "pricing": pricing
    }


@router.get("/providers")
async def list_providers():
    """List supported providers with current pricing"""
    providers = []
    for provider, models in PROVIDER_PRICING.items():
        provider_info = {
            "name": provider,
            "models": []
        }
        for model, pricing in models.items():
            provider_info["models"].append({
                "name": model,
                "input_price": pricing["input"],
                "output_price": pricing["output"],
                "unit": pricing["unit"]
            })
        providers.append(provider_info)

    return {"providers": providers}


@router.get("/compare-providers")
async def compare_providers(
    input_tokens: int = Query(1000000),
    output_tokens: int = Query(500000)
):
    """Compare costs across providers for the same usage"""
    comparisons = []

    for provider, models in PROVIDER_PRICING.items():
        for model, pricing in models.items():
            if pricing["unit"] != "1M tokens":
                continue  # Skip non-token based pricing

            input_cost = (input_tokens / 1_000_000) * pricing["input"]
            output_cost = (output_tokens / 1_000_000) * pricing["output"]
            total = input_cost + output_cost

            comparisons.append({
                "provider": provider,
                "model": model,
                "input_cost": round(input_cost, 4),
                "output_cost": round(output_cost, 4),
                "total_cost": round(total, 4)
            })

    # Sort by total cost
    comparisons.sort(key=lambda x: x["total_cost"])

    cheapest = comparisons[0] if comparisons else None
    most_expensive = comparisons[-1] if comparisons else None

    return {
        "input_tokens": input_tokens,
        "output_tokens": output_tokens,
        "comparisons": comparisons,
        "cheapest": cheapest,
        "most_expensive": most_expensive,
        "savings_potential": round(most_expensive["total_cost"] - cheapest["total_cost"], 4) if cheapest and most_expensive else 0
    }


@router.get("/stats")
async def get_cost_stats():
    """Get overall cost statistics"""
    if not cost_entries:
        return {
            "message": "No cost data available",
            "total_entries": 0
        }

    today = date.today()
    this_month_start = date(today.year, today.month, 1)
    last_month_start = date(today.year, today.month - 1, 1) if today.month > 1 else date(today.year - 1, 12, 1)

    # This month
    this_month = [
        e for e in cost_entries
        if date.fromisoformat(e["entry_date"]) >= this_month_start
    ]
    this_month_total = sum(e["amount"] for e in this_month)

    # Last month
    last_month = [
        e for e in cost_entries
        if last_month_start <= date.fromisoformat(e["entry_date"]) < this_month_start
    ]
    last_month_total = sum(e["amount"] for e in last_month)

    # Calculate change
    if last_month_total > 0:
        month_change = ((this_month_total - last_month_total) / last_month_total) * 100
    else:
        month_change = 100 if this_month_total > 0 else 0

    # All time stats
    all_time_total = sum(e["amount"] for e in cost_entries)
    unique_providers = len(set(e["provider"] for e in cost_entries))
    unique_projects = len(set(e["project"] for e in cost_entries))

    # Date range
    dates = [date.fromisoformat(e["entry_date"]) for e in cost_entries]

    return {
        "this_month_total": round(this_month_total, 2),
        "last_month_total": round(last_month_total, 2),
        "month_over_month_change": round(month_change, 1),
        "all_time_total": round(all_time_total, 2),
        "total_entries": len(cost_entries),
        "unique_providers": unique_providers,
        "unique_projects": unique_projects,
        "date_range": {
            "earliest": min(dates).isoformat() if dates else None,
            "latest": max(dates).isoformat() if dates else None
        }
    }
