# shared/clients/forecast_client.py
"""
Forecast Service Client - Updated for refactored backend structure
Handles all API calls to the forecasting service

Backend structure:
- ATOMIC: /forecasting/forecasts (CRUD)
- BUSINESS: /forecasting/operations/* (single, multi-day, batch, etc.)
- ANALYTICS: /forecasting/analytics/* (predictions-performance)
"""

from typing import Dict, Any, Optional, List
from datetime import date
from .base_service_client import BaseServiceClient
from shared.config.base import BaseServiceSettings


class ForecastServiceClient(BaseServiceClient):
    """Client for communicating with the forecasting service"""

    def __init__(self, config: BaseServiceSettings, calling_service_name: str = "unknown"):
        super().__init__(calling_service_name, config)

    def get_service_base_path(self) -> str:
        return "/api/v1"

    # ================================================================
    # ATOMIC: Forecast CRUD Operations
    # ================================================================

    async def get_forecast(self, tenant_id: str, forecast_id: str) -> Optional[Dict[str, Any]]:
        """Get forecast details by ID"""
        return await self.get(f"forecasting/forecasts/{forecast_id}", tenant_id=tenant_id)

    async def list_forecasts(
        self,
        tenant_id: str,
        inventory_product_id: Optional[str] = None,
        start_date: Optional[date] = None,
        end_date: Optional[date] = None,
        limit: int = 50,
        offset: int = 0
    ) -> Optional[List[Dict[str, Any]]]:
        """List forecasts for a tenant with optional filters"""
        params = {"limit": limit, "offset": offset}
        if inventory_product_id:
            params["inventory_product_id"] = inventory_product_id
        if start_date:
            params["start_date"] = start_date.isoformat()
        if end_date:
            params["end_date"] = end_date.isoformat()

        return await self.get("forecasting/forecasts", tenant_id=tenant_id, params=params)

    async def delete_forecast(self, tenant_id: str, forecast_id: str) -> Optional[Dict[str, Any]]:
        """Delete a forecast"""
        return await self.delete(f"forecasting/forecasts/{forecast_id}", tenant_id=tenant_id)

    # ================================================================
    # BUSINESS: Forecasting Operations
    # ================================================================

    async def generate_single_forecast(
        self,
        tenant_id: str,
        inventory_product_id: str,
        forecast_date: date,
        include_recommendations: bool = False
    ) -> Optional[Dict[str, Any]]:
        """Generate a single product forecast"""
        data = {
            "inventory_product_id": inventory_product_id,
            "forecast_date": forecast_date.isoformat(),
            "include_recommendations": include_recommendations
        }
        return await self.post("forecasting/operations/single", data=data, tenant_id=tenant_id)

    async def generate_multi_day_forecast(
        self,
        tenant_id: str,
        inventory_product_id: str,
        forecast_date: date,
        forecast_days: int = 7,
        include_recommendations: bool = False
    ) -> Optional[Dict[str, Any]]:
        """Generate multiple daily forecasts for the specified period"""
        data = {
            "inventory_product_id": inventory_product_id,
            "forecast_date": forecast_date.isoformat(),
            "forecast_days": forecast_days,
            "include_recommendations": include_recommendations
        }
        return await self.post("forecasting/operations/multi-day", data=data, tenant_id=tenant_id)

    async def generate_batch_forecast(
        self,
        tenant_id: str,
        inventory_product_ids: List[str],
        forecast_date: date,
        forecast_days: int = 1
    ) -> Optional[Dict[str, Any]]:
        """Generate forecasts for multiple products in batch"""
        data = {
            "inventory_product_ids": inventory_product_ids,
            "forecast_date": forecast_date.isoformat(),
            "forecast_days": forecast_days
        }
        return await self.post("forecasting/operations/batch", data=data, tenant_id=tenant_id)

    async def generate_realtime_prediction(
        self,
        tenant_id: str,
        inventory_product_id: str,
        model_id: str,
        features: Dict[str, Any],
        model_path: Optional[str] = None,
        confidence_level: float = 0.8
    ) -> Optional[Dict[str, Any]]:
        """Generate real-time prediction"""
        data = {
            "inventory_product_id": inventory_product_id,
            "model_id": model_id,
            "features": features,
            "confidence_level": confidence_level
        }
        if model_path:
            data["model_path"] = model_path

        return await self.post("forecasting/operations/realtime", data=data, tenant_id=tenant_id)

    async def validate_predictions(
        self,
        tenant_id: str,
        start_date: date,
        end_date: date
    ) -> Optional[Dict[str, Any]]:
        """Validate predictions against actual sales data"""
        params = {
            "start_date": start_date.isoformat(),
            "end_date": end_date.isoformat()
        }
        return await self.post("forecasting/operations/validate-predictions", params=params, tenant_id=tenant_id)

    async def validate_forecasts(
        self,
        tenant_id: str,
        date: date
    ) -> Optional[Dict[str, Any]]:
        """
        Validate forecasts for a specific date against actual sales.
        Calculates MAPE, RMSE, MAE and identifies products with poor accuracy.

        Args:
            tenant_id: Tenant UUID
            date: Date to validate

        Returns:
            Dict with overall metrics and poor accuracy products list
        """
        params = {
            "validation_date": date.isoformat()
        }
        return await self.post("forecasting/operations/validate-forecasts", params=params, tenant_id=tenant_id)

    async def get_forecast_statistics(
        self,
        tenant_id: str,
        start_date: Optional[date] = None,
        end_date: Optional[date] = None
    ) -> Optional[Dict[str, Any]]:
        """Get forecast statistics"""
        params = {}
        if start_date:
            params["start_date"] = start_date.isoformat()
        if end_date:
            params["end_date"] = end_date.isoformat()

        return await self.get("forecasting/operations/statistics", tenant_id=tenant_id, params=params)

    async def clear_prediction_cache(self, tenant_id: str) -> Optional[Dict[str, Any]]:
        """Clear prediction cache"""
        return await self.delete("forecasting/operations/cache", tenant_id=tenant_id)

    # ================================================================
    # ANALYTICS: Forecasting Analytics
    # ================================================================

    async def get_predictions_performance(
        self,
        tenant_id: str,
        start_date: Optional[date] = None,
        end_date: Optional[date] = None
    ) -> Optional[Dict[str, Any]]:
        """Get predictions performance analytics"""
        params = {}
        if start_date:
            params["start_date"] = start_date.isoformat()
        if end_date:
            params["end_date"] = end_date.isoformat()

        return await self.get("forecasting/analytics/predictions-performance", tenant_id=tenant_id, params=params)

    # ================================================================
    # ML INSIGHTS: Dynamic Rules Generation
    # ================================================================

    async def trigger_rules_generation(
        self,
        tenant_id: str,
        product_ids: Optional[List[str]] = None,
        lookback_days: int = 90,
        min_samples: int = 10
    ) -> Optional[Dict[str, Any]]:
        """
        Trigger dynamic business rules learning for demand forecasting.

        Args:
            tenant_id: Tenant UUID
            product_ids: Specific product IDs to analyze. If None, analyzes all products
            lookback_days: Days of historical data to analyze (30-365)
            min_samples: Minimum samples required for rule learning (5-100)

        Returns:
            Dict with rules generation results including insights posted
        """
        data = {
            "product_ids": product_ids,
            "lookback_days": lookback_days,
            "min_samples": min_samples
        }
        return await self.post("forecasting/ml/insights/generate-rules", data=data, tenant_id=tenant_id)

    # ================================================================
    # Legacy/Compatibility Methods (deprecated)
    # ================================================================

    async def generate_forecasts(
        self,
        tenant_id: str,
        forecast_days: int = 7,
        inventory_product_ids: Optional[List[str]] = None
    ) -> Optional[Dict[str, Any]]:
        """
        COMPATIBILITY: Orchestrator-friendly method to generate forecasts

        This method is called by the orchestrator service and generates batch forecasts
        for either specified products or all products.

        Args:
            tenant_id: Tenant UUID
            forecast_days: Number of days to forecast (default 7)
            inventory_product_ids: Optional list of product IDs. If None, forecasts all products.

        Returns:
            Dict with forecast results
        """
        from datetime import datetime

        # If no product IDs specified, let the backend handle it
        if not inventory_product_ids:
            # Call the batch operation endpoint to forecast all products
            # The forecasting service will handle fetching all products internally
            data = {
                "batch_name": f"orchestrator-batch-{datetime.now().strftime('%Y%m%d')}",
                "inventory_product_ids": [],  # Empty list will trigger fetching all products
                "forecast_days": forecast_days
            }
            return await self.post("forecasting/operations/batch", data=data, tenant_id=tenant_id)

        # Otherwise use the standard batch forecast
        return await self.generate_batch_forecast(
            tenant_id=tenant_id,
            inventory_product_ids=inventory_product_ids,
            forecast_date=datetime.now().date(),
            forecast_days=forecast_days
        )

    async def create_forecast(
        self,
        tenant_id: str,
        model_id: str,
        start_date: str,
        end_date: str,
        product_ids: Optional[List[str]] = None,
        include_confidence_intervals: bool = True,
        **kwargs
    ) -> Optional[Dict[str, Any]]:
        """
        DEPRECATED: Use generate_single_forecast or generate_batch_forecast instead
        Legacy method for backward compatibility
        """
        # Map to new batch forecast operation
        if product_ids:
            return await self.generate_batch_forecast(
                tenant_id=tenant_id,
                inventory_product_ids=product_ids,
                forecast_date=date.fromisoformat(start_date),
                forecast_days=1
            )
        return None
