"""
Generic REST API connector for IoT equipment
Supports standard REST endpoints with JSON responses
"""

import httpx
import time
from typing import Dict, Any, Optional
from datetime import datetime, timezone

from .base_connector import (
    BaseIoTConnector,
    SensorReading,
    ConnectionStatus,
    EquipmentCapabilities
)


class GenericRESTAPIConnector(BaseIoTConnector):
    """
    Generic REST API connector for equipment with standard REST interfaces

    Expected configuration:
    {
        "endpoint": "https://api.example.com",
        "port": 443,
        "credentials": {
            "api_key": "your-api-key",
            "token": "bearer-token",  # Optional
            "username": "user",  # Optional
            "password": "pass"  # Optional
        },
        "additional_config": {
            "data_endpoint": "/api/v1/equipment/{equipment_id}/data",
            "status_endpoint": "/api/v1/equipment/{equipment_id}/status",
            "capabilities_endpoint": "/api/v1/equipment/{equipment_id}/capabilities",
            "timeout": 10,
            "verify_ssl": true
        }
    }
    """

    def __init__(self, equipment_id: str, config: Dict[str, Any]):
        super().__init__(equipment_id, config)

        self.timeout = config.get('additional_config', {}).get('timeout', 10)
        self.verify_ssl = config.get('additional_config', {}).get('verify_ssl', True)

        # API endpoints (support templating with {equipment_id})
        self.data_endpoint = config.get('additional_config', {}).get(
            'data_endpoint', '/data'
        ).replace('{equipment_id}', equipment_id)

        self.status_endpoint = config.get('additional_config', {}).get(
            'status_endpoint', '/status'
        ).replace('{equipment_id}', equipment_id)

        self.capabilities_endpoint = config.get('additional_config', {}).get(
            'capabilities_endpoint', '/capabilities'
        ).replace('{equipment_id}', equipment_id)

        # Build full base URL
        port_str = f":{self.port}" if self.port and self.port not in [80, 443] else ""
        self.base_url = f"{self.endpoint}{port_str}"

        # Authentication headers
        self._headers = self._build_auth_headers()

        # HTTP client (will be created on demand)
        self._client: Optional[httpx.AsyncClient] = None

    def _build_auth_headers(self) -> Dict[str, str]:
        """Build authentication headers from credentials"""
        headers = {
            "Content-Type": "application/json",
            "Accept": "application/json"
        }

        # API Key authentication
        if 'api_key' in self.credentials:
            headers['X-API-Key'] = self.credentials['api_key']

        # Bearer token authentication
        if 'token' in self.credentials:
            headers['Authorization'] = f"Bearer {self.credentials['token']}"

        # Basic auth (will be handled by httpx.BasicAuth if needed)

        return headers

    async def _get_client(self) -> httpx.AsyncClient:
        """Get or create HTTP client"""
        if self._client is None:
            auth = None
            if 'username' in self.credentials and 'password' in self.credentials:
                auth = httpx.BasicAuth(
                    username=self.credentials['username'],
                    password=self.credentials['password']
                )

            self._client = httpx.AsyncClient(
                base_url=self.base_url,
                headers=self._headers,
                auth=auth,
                timeout=self.timeout,
                verify=self.verify_ssl
            )

        return self._client

    async def connect(self) -> ConnectionStatus:
        """Establish connection (test connectivity)"""
        try:
            client = await self._get_client()
            start_time = time.time()

            # Try to fetch status to verify connection
            response = await client.get(self.status_endpoint)

            response_time = int((time.time() - start_time) * 1000)

            if response.status_code == 200:
                self._is_connected = True
                self._clear_error()
                return ConnectionStatus(
                    is_connected=True,
                    status="connected",
                    message="Successfully connected to equipment API",
                    response_time_ms=response_time,
                    last_successful_connection=datetime.now(timezone.utc)
                )
            else:
                self._is_connected = False
                error_msg = f"HTTP {response.status_code}: {response.text}"
                self._set_error(error_msg)
                return ConnectionStatus(
                    is_connected=False,
                    status="error",
                    message="Failed to connect to equipment API",
                    response_time_ms=response_time,
                    error_details=error_msg
                )

        except httpx.TimeoutException as e:
            self._is_connected = False
            error_msg = f"Connection timeout: {str(e)}"
            self._set_error(error_msg)
            return ConnectionStatus(
                is_connected=False,
                status="error",
                message="Connection timeout",
                error_details=error_msg
            )

        except Exception as e:
            self._is_connected = False
            error_msg = f"Connection error: {str(e)}"
            self._set_error(error_msg)
            return ConnectionStatus(
                is_connected=False,
                status="error",
                message="Failed to connect",
                error_details=error_msg
            )

    async def disconnect(self) -> bool:
        """Close connection"""
        if self._client:
            await self._client.aclose()
            self._client = None

        self._is_connected = False
        return True

    async def test_connection(self) -> ConnectionStatus:
        """Test connection without persisting client"""
        result = await self.connect()
        await self.disconnect()
        return result

    async def get_current_reading(self) -> Optional[SensorReading]:
        """Get current sensor readings from equipment"""
        try:
            client = await self._get_client()
            response = await client.get(self.data_endpoint)

            if response.status_code != 200:
                self._set_error(f"Failed to fetch data: HTTP {response.status_code}")
                return None

            data = response.json()

            # Parse response into SensorReading
            # This mapping can be customized per manufacturer
            return self._parse_sensor_data(data)

        except Exception as e:
            self._set_error(f"Error fetching sensor data: {str(e)}")
            return None

    def _parse_sensor_data(self, data: Dict[str, Any]) -> SensorReading:
        """
        Parse API response into standardized SensorReading
        Override this method for manufacturer-specific parsing
        """
        # Default parsing - assumes standard field names
        return SensorReading(
            timestamp=self._parse_timestamp(data.get('timestamp')),
            temperature=data.get('temperature'),
            temperature_zones=data.get('temperature_zones'),
            target_temperature=data.get('target_temperature'),
            humidity=data.get('humidity'),
            target_humidity=data.get('target_humidity'),
            energy_consumption_kwh=data.get('energy_consumption_kwh'),
            power_current_kw=data.get('power_current_kw') or data.get('power_kw'),
            operational_status=data.get('operational_status') or data.get('status'),
            cycle_stage=data.get('cycle_stage') or data.get('stage'),
            cycle_progress_percentage=data.get('cycle_progress_percentage') or data.get('progress'),
            time_remaining_minutes=data.get('time_remaining_minutes') or data.get('time_remaining'),
            motor_speed_rpm=data.get('motor_speed_rpm'),
            door_status=data.get('door_status'),
            steam_level=data.get('steam_level'),
            product_weight_kg=data.get('product_weight_kg'),
            moisture_content=data.get('moisture_content'),
            additional_sensors=data.get('additional_sensors') or {}
        )

    def _parse_timestamp(self, timestamp_value: Any) -> datetime:
        """Parse timestamp from various formats"""
        if timestamp_value is None:
            return datetime.now(timezone.utc)

        if isinstance(timestamp_value, datetime):
            return timestamp_value

        if isinstance(timestamp_value, str):
            # Try ISO format
            try:
                return datetime.fromisoformat(timestamp_value.replace('Z', '+00:00'))
            except:
                pass

        if isinstance(timestamp_value, (int, float)):
            # Unix timestamp
            return datetime.fromtimestamp(timestamp_value, tz=timezone.utc)

        return datetime.now(timezone.utc)

    async def get_capabilities(self) -> EquipmentCapabilities:
        """Discover equipment capabilities"""
        try:
            client = await self._get_client()
            response = await client.get(self.capabilities_endpoint)

            if response.status_code == 200:
                data = response.json()
                return EquipmentCapabilities(
                    supports_temperature=data.get('supports_temperature', True),
                    supports_humidity=data.get('supports_humidity', False),
                    supports_energy_monitoring=data.get('supports_energy_monitoring', False),
                    supports_remote_control=data.get('supports_remote_control', False),
                    supports_realtime=data.get('supports_realtime', True),
                    temperature_zones=data.get('temperature_zones', 1),
                    supported_protocols=['rest_api'],
                    manufacturer_specific_features=data.get('additional_features')
                )
            else:
                # Return default capabilities if endpoint not available
                return EquipmentCapabilities(
                    supports_temperature=True,
                    supports_realtime=True,
                    supported_protocols=['rest_api']
                )

        except Exception as e:
            # Return minimal capabilities on error
            self._set_error(f"Error fetching capabilities: {str(e)}")
            return EquipmentCapabilities(
                supports_temperature=True,
                supports_realtime=True,
                supported_protocols=['rest_api']
            )

    async def get_status(self) -> Dict[str, Any]:
        """Get equipment status"""
        try:
            client = await self._get_client()
            response = await client.get(self.status_endpoint)

            if response.status_code == 200:
                return response.json()
            else:
                return {
                    "error": f"HTTP {response.status_code}",
                    "connected": False
                }

        except Exception as e:
            return {
                "error": str(e),
                "connected": False
            }

    async def set_target_temperature(self, temperature: float) -> bool:
        """Set target temperature (if supported)"""
        try:
            client = await self._get_client()

            # POST to control endpoint
            control_endpoint = self.config.get('additional_config', {}).get(
                'control_endpoint', '/control'
            ).replace('{equipment_id}', self.equipment_id)

            response = await client.post(
                control_endpoint,
                json={"target_temperature": temperature}
            )

            return response.status_code in [200, 201, 202]

        except Exception as e:
            self._set_error(f"Error setting temperature: {str(e)}")
            return False


# Register this connector with the factory
from .base_connector import ConnectorFactory
ConnectorFactory.register_connector('rest_api', GenericRESTAPIConnector)
