"""
Load Calculator Module - Service load calculation for load balancing

Usage:
```python
from gmi_ieops.handler import get_load_calculator, set_load_calculator, LoadCalculator

# Use default calculator (based on active request count)
calc = get_load_calculator()
calc.on_request_start()
# ... handle request ...
calc.on_request_end()
load = calc.calculate()  # Returns 0.0 - 1.0

# Custom calculator
class MyLoadCalculator(LoadCalculator):
    def calculate(self) -> float:
        return my_custom_load_value

set_load_calculator(MyLoadCalculator())
```
"""

import threading
from abc import ABC, abstractmethod
from typing import Optional

from ..config import env


class LoadCalculator(ABC):
    """
    Abstract base class for load calculators.
    
    Subclass this to implement custom load calculation logic.
    """
    
    @abstractmethod
    def calculate(self) -> float:
        """
        Calculate current service load.
        
        Returns:
            Load value between 0.0 (idle) and 1.0 (fully loaded).
        """
        pass
    
    def on_request_start(self) -> None:
        """Called when a request starts. Override if needed."""
        pass
    
    def on_request_end(self) -> None:
        """Called when a request ends. Override if needed."""
        pass


class DefaultLoadCalculator(LoadCalculator):
    """
    Default load calculator: load = active_requests / max_concurrency
    
    Example:
        calc = DefaultLoadCalculator()
        calc.on_request_start()
        load = calc.calculate()  # 0.5 if 1 of 2 max requests
        calc.on_request_end()
    """
    
    def __init__(self, max_concurrency: Optional[int] = None):
        self._lock = threading.Lock()
        self._active = 0
        self._max = max_concurrency or env.model.concurrency or 1
    
    @property
    def active_requests(self) -> int:
        with self._lock:
            return self._active
    
    @property
    def max_concurrency(self) -> int:
        return self._max
    
    @max_concurrency.setter
    def max_concurrency(self, value: int) -> None:
        self._max = max(1, value)
    
    def calculate(self) -> float:
        with self._lock:
            return self._active / self._max if self._max > 0 else 0.0
    
    def on_request_start(self) -> None:
        with self._lock:
            self._active += 1
    
    def on_request_end(self) -> None:
        with self._lock:
            self._active = max(0, self._active - 1)


# Global singleton
_calculator: Optional[LoadCalculator] = None
_lock = threading.Lock()


def get_load_calculator() -> LoadCalculator:
    """Get global load calculator (creates DefaultLoadCalculator if not set)."""
    global _calculator
    if _calculator is None:
        with _lock:
            if _calculator is None:
                _calculator = DefaultLoadCalculator()
    return _calculator


def set_load_calculator(calculator: LoadCalculator) -> None:
    """Set custom global load calculator."""
    global _calculator
    with _lock:
        _calculator = calculator


def calculate_load() -> float:
    """Convenience: get_load_calculator().calculate()"""
    return get_load_calculator().calculate()
