# shared/utils/tenant_settings_client.py
"""
Tenant Settings Client
Shared utility for services to fetch tenant-specific settings from Tenant Service
Includes Redis caching for performance
"""

import httpx
import json
from typing import Dict, Any, Optional
from uuid import UUID
import redis.asyncio as aioredis
from datetime import timedelta
import logging

logger = logging.getLogger(__name__)


class TenantSettingsClient:
    """
    Client for fetching tenant settings from Tenant Service

    Features:
    - HTTP client to fetch settings from Tenant Service API
    - Redis caching with configurable TTL (default 5 minutes)
    - Automatic cache invalidation support
    - Fallback to defaults if Tenant Service is unavailable
    """

    def __init__(
        self,
        tenant_service_url: str,
        redis_client: Optional[aioredis.Redis] = None,
        cache_ttl: int = 300,  # 5 minutes default
        http_timeout: int = 10
    ):
        """
        Initialize TenantSettingsClient

        Args:
            tenant_service_url: Base URL of Tenant Service (e.g., "http://tenant-service:8000")
            redis_client: Optional Redis client for caching
            cache_ttl: Cache TTL in seconds (default 300 = 5 minutes)
            http_timeout: HTTP request timeout in seconds
        """
        self.tenant_service_url = tenant_service_url.rstrip('/')
        self.redis = redis_client
        self.cache_ttl = cache_ttl
        self.http_timeout = http_timeout

        # HTTP client with connection pooling
        self.http_client = httpx.AsyncClient(
            timeout=http_timeout,
            limits=httpx.Limits(max_keepalive_connections=20, max_connections=100)
        )

    async def get_procurement_settings(self, tenant_id: UUID) -> Dict[str, Any]:
        """
        Get procurement settings for a tenant

        Args:
            tenant_id: UUID of the tenant

        Returns:
            Dictionary with procurement settings
        """
        return await self._get_category_settings(tenant_id, "procurement")

    async def get_inventory_settings(self, tenant_id: UUID) -> Dict[str, Any]:
        """
        Get inventory settings for a tenant

        Args:
            tenant_id: UUID of the tenant

        Returns:
            Dictionary with inventory settings
        """
        return await self._get_category_settings(tenant_id, "inventory")

    async def get_production_settings(self, tenant_id: UUID) -> Dict[str, Any]:
        """
        Get production settings for a tenant

        Args:
            tenant_id: UUID of the tenant

        Returns:
            Dictionary with production settings
        """
        return await self._get_category_settings(tenant_id, "production")

    async def get_supplier_settings(self, tenant_id: UUID) -> Dict[str, Any]:
        """
        Get supplier settings for a tenant

        Args:
            tenant_id: UUID of the tenant

        Returns:
            Dictionary with supplier settings
        """
        return await self._get_category_settings(tenant_id, "supplier")

    async def get_pos_settings(self, tenant_id: UUID) -> Dict[str, Any]:
        """
        Get POS settings for a tenant

        Args:
            tenant_id: UUID of the tenant

        Returns:
            Dictionary with POS settings
        """
        return await self._get_category_settings(tenant_id, "pos")

    async def get_order_settings(self, tenant_id: UUID) -> Dict[str, Any]:
        """
        Get order settings for a tenant

        Args:
            tenant_id: UUID of the tenant

        Returns:
            Dictionary with order settings
        """
        return await self._get_category_settings(tenant_id, "order")

    async def get_all_settings(self, tenant_id: UUID) -> Dict[str, Any]:
        """
        Get all settings for a tenant

        Args:
            tenant_id: UUID of the tenant

        Returns:
            Dictionary with all setting categories
        """
        cache_key = f"tenant_settings:{tenant_id}:all"

        # Try cache first
        if self.redis:
            cached = await self._get_from_cache(cache_key)
            if cached:
                return cached

        # Fetch from Tenant Service
        try:
            url = f"{self.tenant_service_url}/api/v1/tenants/{tenant_id}/settings"
            response = await self.http_client.get(url)
            response.raise_for_status()

            settings = response.json()

            # Cache the result
            if self.redis:
                await self._set_in_cache(cache_key, settings)

            return settings

        except Exception as e:
            logger.error(f"Failed to fetch all settings for tenant {tenant_id}: {e}")
            return self._get_default_settings()

    async def invalidate_cache(self, tenant_id: UUID, category: Optional[str] = None):
        """
        Invalidate cache for a tenant's settings

        Args:
            tenant_id: UUID of the tenant
            category: Optional category to invalidate. If None, invalidates all categories.
        """
        if not self.redis:
            return

        if category:
            cache_key = f"tenant_settings:{tenant_id}:{category}"
            await self.redis.delete(cache_key)
            logger.info(f"Invalidated cache for tenant {tenant_id}, category {category}")
        else:
            # Invalidate all categories
            pattern = f"tenant_settings:{tenant_id}:*"
            keys = await self.redis.keys(pattern)
            if keys:
                await self.redis.delete(*keys)
            logger.info(f"Invalidated all cached settings for tenant {tenant_id}")

    async def _get_category_settings(self, tenant_id: UUID, category: str) -> Dict[str, Any]:
        """
        Internal method to fetch settings for a specific category

        Args:
            tenant_id: UUID of the tenant
            category: Category name

        Returns:
            Dictionary with category settings
        """
        cache_key = f"tenant_settings:{tenant_id}:{category}"

        # Try cache first
        if self.redis:
            cached = await self._get_from_cache(cache_key)
            if cached:
                return cached

        # Fetch from Tenant Service
        try:
            url = f"{self.tenant_service_url}/api/v1/tenants/{tenant_id}/settings/{category}"
            response = await self.http_client.get(url)
            response.raise_for_status()

            data = response.json()
            settings = data.get("settings", {})

            # Cache the result
            if self.redis:
                await self._set_in_cache(cache_key, settings)

            return settings

        except httpx.HTTPStatusError as e:
            if e.response.status_code == 404:
                logger.warning(f"Settings not found for tenant {tenant_id}, using defaults")
            else:
                logger.error(f"HTTP error fetching {category} settings for tenant {tenant_id}: {e}")
            return self._get_default_category_settings(category)

        except Exception as e:
            logger.error(f"Failed to fetch {category} settings for tenant {tenant_id}: {e}")
            return self._get_default_category_settings(category)

    async def _get_from_cache(self, key: str) -> Optional[Dict[str, Any]]:
        """Get value from Redis cache"""
        try:
            cached = await self.redis.get(key)
            if cached:
                return json.loads(cached)
        except Exception as e:
            logger.warning(f"Redis get error for key {key}: {e}")
        return None

    async def _set_in_cache(self, key: str, value: Dict[str, Any]):
        """Set value in Redis cache with TTL"""
        try:
            await self.redis.setex(
                key,
                timedelta(seconds=self.cache_ttl),
                json.dumps(value)
            )
        except Exception as e:
            logger.warning(f"Redis set error for key {key}: {e}")

    def _get_default_category_settings(self, category: str) -> Dict[str, Any]:
        """Get default settings for a category as fallback"""
        defaults = self._get_default_settings()
        return defaults.get(f"{category}_settings", {})

    def _get_default_settings(self) -> Dict[str, Any]:
        """Get default settings for all categories as fallback"""
        return {
            "procurement_settings": {
                "auto_approve_enabled": True,
                "auto_approve_threshold_eur": 500.0,
                "auto_approve_min_supplier_score": 0.80,
                "require_approval_new_suppliers": True,
                "require_approval_critical_items": True,
                "procurement_lead_time_days": 3,
                "demand_forecast_days": 14,
                "safety_stock_percentage": 20.0,
                "po_approval_reminder_hours": 24,
                "po_critical_escalation_hours": 12
            },
            "inventory_settings": {
                "low_stock_threshold": 10,
                "reorder_point": 20,
                "reorder_quantity": 50,
                "expiring_soon_days": 7,
                "expiration_warning_days": 3,
                "quality_score_threshold": 8.0,
                "temperature_monitoring_enabled": True,
                "refrigeration_temp_min": 1.0,
                "refrigeration_temp_max": 4.0,
                "freezer_temp_min": -20.0,
                "freezer_temp_max": -15.0,
                "room_temp_min": 18.0,
                "room_temp_max": 25.0,
                "temp_deviation_alert_minutes": 15,
                "critical_temp_deviation_minutes": 5
            },
            "production_settings": {
                "planning_horizon_days": 7,
                "minimum_batch_size": 1.0,
                "maximum_batch_size": 100.0,
                "production_buffer_percentage": 10.0,
                "working_hours_per_day": 12,
                "max_overtime_hours": 4,
                "capacity_utilization_target": 0.85,
                "capacity_warning_threshold": 0.95,
                "quality_check_enabled": True,
                "minimum_yield_percentage": 85.0,
                "quality_score_threshold": 8.0,
                "schedule_optimization_enabled": True,
                "prep_time_buffer_minutes": 30,
                "cleanup_time_buffer_minutes": 15,
                "labor_cost_per_hour_eur": 15.0,
                "overhead_cost_percentage": 20.0
            },
            "supplier_settings": {
                "default_payment_terms_days": 30,
                "default_delivery_days": 3,
                "excellent_delivery_rate": 95.0,
                "good_delivery_rate": 90.0,
                "excellent_quality_rate": 98.0,
                "good_quality_rate": 95.0,
                "critical_delivery_delay_hours": 24,
                "critical_quality_rejection_rate": 10.0,
                "high_cost_variance_percentage": 15.0
            },
            "pos_settings": {
                "sync_interval_minutes": 5,
                "auto_sync_products": True,
                "auto_sync_transactions": True
            },
            "order_settings": {
                "max_discount_percentage": 50.0,
                "default_delivery_window_hours": 48,
                "dynamic_pricing_enabled": False,
                "discount_enabled": True,
                "delivery_tracking_enabled": True
            }
        }

    async def close(self):
        """Close HTTP client connections"""
        await self.http_client.aclose()


# Factory function for easy instantiation
def create_tenant_settings_client(
    tenant_service_url: str,
    redis_client: Optional[aioredis.Redis] = None,
    cache_ttl: int = 300
) -> TenantSettingsClient:
    """
    Factory function to create a TenantSettingsClient

    Args:
        tenant_service_url: Base URL of Tenant Service
        redis_client: Optional Redis client for caching
        cache_ttl: Cache TTL in seconds

    Returns:
        TenantSettingsClient instance
    """
    return TenantSettingsClient(
        tenant_service_url=tenant_service_url,
        redis_client=redis_client,
        cache_ttl=cache_ttl
    )
