"""
Data logging service for chocolate tempering machine.
Handles high-frequency data collection, storage, and export.
"""

import asyncio
import logging
from typing import Dict, List, Optional, Any
from datetime import datetime, timedelta
from dataclasses import dataclass, field
from pathlib import Path
import json
import csv

from ...shared.config import settings
from ...shared.database import db_manager
from ...shared.models.process import ProcessSession, TemperatureReading, ProcessLog
from ..hardware.hardware_manager import hardware_manager


logger = logging.getLogger(__name__)


@dataclass
class DataPoint:
    """Single data point for logging."""
    timestamp: datetime
    session_id: Optional[str]
    sensor_name: str
    value: float
    units: str = "°C"
    quality: str = "good"  # good, poor, bad
    additional_data: Dict[str, Any] = field(default_factory=dict)


class DataLogger:
    """
    High-frequency data logger for process monitoring and analysis.
    Collects temperature, motor, and process data at regular intervals.
    """
    
    def __init__(self):
        self.is_logging = False
        self.logging_task: Optional[asyncio.Task] = None
        self.data_buffer: List[DataPoint] = []
        self.buffer_size = 1000
        self.log_interval = 1.0  # seconds
        self.current_session_id: Optional[str] = None
        
        # Statistics
        self.stats = {
            "total_points_logged": 0,
            "buffer_flushes": 0,
            "logging_errors": 0,
            "last_log_time": None,
            "start_time": datetime.now(),
        }
        
    async def start_logging(self, session_id: Optional[str] = None) -> None:
        """Start data logging."""
        if self.is_logging:
            return
        
        self.current_session_id = session_id
        self.is_logging = True
        self.logging_task = asyncio.create_task(self._logging_loop())
        
        logger.info(f"Data logging started for session: {session_id}")
    
    async def stop_logging(self) -> None:
        """Stop data logging and flush remaining data."""
        if not self.is_logging:
            return
        
        self.is_logging = False
        
        if self.logging_task:
            self.logging_task.cancel()
            try:
                await self.logging_task
            except asyncio.CancelledError:
                pass
        
        # Flush remaining data
        await self._flush_buffer()
        
        logger.info("Data logging stopped")
    
    async def _logging_loop(self) -> None:
        """Main data logging loop."""
        while self.is_logging:
            try:
                await self._collect_data_points()
                
                # Flush buffer if full
                if len(self.data_buffer) >= self.buffer_size:
                    await self._flush_buffer()
                
                # Update statistics
                self.stats["last_log_time"] = datetime.now()
                
                # Wait for next collection cycle
                await asyncio.sleep(self.log_interval)
                
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Error in data logging loop: {e}")
                self.stats["logging_errors"] += 1
                await asyncio.sleep(1.0)  # Error recovery delay
    
    async def _collect_data_points(self) -> None:
        """Collect current data points from hardware."""
        try:
            timestamp = datetime.now()
            
            # Collect temperature data
            temperatures = await hardware_manager.get_all_temperatures()
            
            for sensor_name, reading in temperatures.items():
                quality = "good" if reading.is_valid else "bad"
                
                data_point = DataPoint(
                    timestamp=timestamp,
                    session_id=self.current_session_id,
                    sensor_name=sensor_name,
                    value=reading.value if reading.is_valid else 0.0,
                    units=reading.units,
                    quality=quality,
                    additional_data={
                        "error_message": reading.error_message if not reading.is_valid else None
                    }
                )
                
                self.data_buffer.append(data_point)
                self.stats["total_points_logged"] += 1
            
            # Collect motor current data (if available)
            hw_status = hardware_manager.get_hardware_status()
            
            for motor_name, motor in hw_status.motors.items():
                if motor.current is not None:
                    data_point = DataPoint(
                        timestamp=timestamp,
                        session_id=self.current_session_id,
                        sensor_name=f"{motor_name}_current",
                        value=motor.current,
                        units="A",
                        quality="good",
                        additional_data={
                            "motor_state": motor.state.value,
                            "is_enabled": motor.is_enabled
                        }
                    )
                    
                    self.data_buffer.append(data_point)
                    self.stats["total_points_logged"] += 1
            
        except Exception as e:
            logger.error(f"Error collecting data points: {e}")
            raise
    
    async def _flush_buffer(self) -> None:
        """Flush data buffer to database."""
        if not self.data_buffer:
            return
        
        try:
            async with db_manager.get_async_session() as session:
                # Convert data points to temperature readings
                temperature_readings = []
                
                for data_point in self.data_buffer:
                    if data_point.units == "°C":  # Temperature readings
                        # Map sensor names to columns
                        temp_reading = TemperatureReading(
                            session_id=data_point.session_id,
                            timestamp=data_point.timestamp,
                            **{self._map_sensor_to_column(data_point.sensor_name): data_point.value}
                        )
                        temperature_readings.append(temp_reading)
                
                # Batch insert temperature readings
                if temperature_readings:
                    session.add_all(temperature_readings)
                    await session.commit()
                
                # Clear buffer
                self.data_buffer.clear()
                self.stats["buffer_flushes"] += 1
                
                logger.debug(f"Flushed {len(temperature_readings)} temperature readings to database")
                
        except Exception as e:
            logger.error(f"Error flushing data buffer: {e}")
            # Don't clear buffer on error to retry later
            raise
    
    def _map_sensor_to_column(self, sensor_name: str) -> str:
        """Map sensor names to database column names."""
        mapping = {
            "tank_bottom": "tank_bottom",
            "tank_wall": "tank_wall",
            "pump": "pump", 
            "fountain": "fountain",
        }
        return mapping.get(sensor_name, "tank_bottom")  # Default fallback
    
    async def export_data(
        self,
        session_id: str,
        format: str = "csv",
        start_time: Optional[datetime] = None,
        end_time: Optional[datetime] = None
    ) -> str:
        """Export logged data for a session."""
        try:
            async with db_manager.get_async_session() as session:
                # Query temperature readings
                query = session.query(TemperatureReading).filter(
                    TemperatureReading.session_id == session_id
                )
                
                if start_time:
                    query = query.filter(TemperatureReading.timestamp >= start_time)
                if end_time:
                    query = query.filter(TemperatureReading.timestamp <= end_time)
                
                query = query.order_by(TemperatureReading.timestamp)
                
                readings = await query.all()
                
                # Export to specified format
                if format.lower() == "csv":
                    return await self._export_to_csv(readings, session_id)
                elif format.lower() == "json":
                    return await self._export_to_json(readings, session_id)
                else:
                    raise ValueError(f"Unsupported export format: {format}")
                    
        except Exception as e:
            logger.error(f"Error exporting data: {e}")
            raise
    
    async def _export_to_csv(self, readings: List[TemperatureReading], session_id: str) -> str:
        """Export readings to CSV format."""
        try:
            export_dir = settings.data_directory / "exports"
            export_dir.mkdir(exist_ok=True)
            
            filename = f"temperature_data_{session_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
            filepath = export_dir / filename
            
            with open(filepath, 'w', newline='') as csvfile:
                fieldnames = [
                    'timestamp', 'phase', 'tank_bottom', 'tank_wall', 
                    'pump', 'fountain', 'target_temperature', 'temperature_error'
                ]
                writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
                
                writer.writeheader()
                for reading in readings:
                    writer.writerow({
                        'timestamp': reading.timestamp.isoformat(),
                        'phase': reading.phase.value if reading.phase else None,
                        'tank_bottom': reading.tank_bottom,
                        'tank_wall': reading.tank_wall,
                        'pump': reading.pump,
                        'fountain': reading.fountain,
                        'target_temperature': reading.target_temperature,
                        'temperature_error': reading.temperature_error,
                    })
            
            logger.info(f"Exported {len(readings)} readings to {filepath}")
            return str(filepath)
            
        except Exception as e:
            logger.error(f"Error exporting to CSV: {e}")
            raise
    
    async def _export_to_json(self, readings: List[TemperatureReading], session_id: str) -> str:
        """Export readings to JSON format."""
        try:
            export_dir = settings.data_directory / "exports" 
            export_dir.mkdir(exist_ok=True)
            
            filename = f"temperature_data_{session_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
            filepath = export_dir / filename
            
            data = {
                "session_id": session_id,
                "export_time": datetime.now().isoformat(),
                "reading_count": len(readings),
                "readings": [
                    {
                        "timestamp": reading.timestamp.isoformat(),
                        "phase": reading.phase.value if reading.phase else None,
                        "temperatures": {
                            "tank_bottom": reading.tank_bottom,
                            "tank_wall": reading.tank_wall,
                            "pump": reading.pump,
                            "fountain": reading.fountain,
                        },
                        "target_temperature": reading.target_temperature,
                        "temperature_error": reading.temperature_error,
                    }
                    for reading in readings
                ]
            }
            
            with open(filepath, 'w') as jsonfile:
                json.dump(data, jsonfile, indent=2)
            
            logger.info(f"Exported {len(readings)} readings to {filepath}")
            return str(filepath)
            
        except Exception as e:
            logger.error(f"Error exporting to JSON: {e}")
            raise
    
    async def get_data_summary(self, session_id: str) -> Dict[str, Any]:
        """Get data summary for a session."""
        try:
            async with db_manager.get_async_session() as session:
                # Query session data
                query = session.query(TemperatureReading).filter(
                    TemperatureReading.session_id == session_id
                )
                
                readings = await query.all()
                
                if not readings:
                    return {"session_id": session_id, "reading_count": 0}
                
                # Calculate statistics
                timestamps = [r.timestamp for r in readings]
                fountain_temps = [r.fountain for r in readings if r.fountain is not None]
                
                summary = {
                    "session_id": session_id,
                    "reading_count": len(readings),
                    "start_time": min(timestamps).isoformat(),
                    "end_time": max(timestamps).isoformat(),
                    "duration_seconds": (max(timestamps) - min(timestamps)).total_seconds(),
                }
                
                if fountain_temps:
                    summary.update({
                        "fountain_temp_min": min(fountain_temps),
                        "fountain_temp_max": max(fountain_temps),
                        "fountain_temp_avg": sum(fountain_temps) / len(fountain_temps),
                    })
                
                return summary
                
        except Exception as e:
            logger.error(f"Error getting data summary: {e}")
            raise
    
    def get_logging_statistics(self) -> Dict[str, Any]:
        """Get logging statistics."""
        uptime = datetime.now() - self.stats["start_time"]
        
        return {
            **self.stats,
            "is_logging": self.is_logging,
            "current_session_id": self.current_session_id,
            "buffer_size": len(self.data_buffer),
            "buffer_capacity": self.buffer_size,
            "log_interval": self.log_interval,
            "uptime_seconds": uptime.total_seconds(),
            "points_per_second": (
                self.stats["total_points_logged"] / max(1, uptime.total_seconds())
            ),
        }


# Global data logger instance
data_logger = DataLogger()