from typing import Optional, Union, Literal
import os
import aiohttp
import cups
from pathlib import Path
from pdf2image import convert_from_path
from brother_ql.conversion import convert
from brother_ql.raster import BrotherQLRaster
import logging
import asyncio
import time
from datetime import datetime, timedelta

logger = logging.getLogger(__name__)

class PrintService:
    def __init__(self, printer_name: Optional[str] = None, printer_api_url: str = "http://localhost:8000/print", 
                 min_print_interval: int = 30):
        """Initialize the print service.
        
        Args:
            printer_name: Name of the printer to use. If None, will use default printer.
            printer_api_url: URL of the printer API endpoint
            min_print_interval: Minimum time in seconds between print requests for label printer
        """
        self.printer_name = printer_name
        self.printer_api_url = printer_api_url
        self.status_url = printer_api_url.replace('/print', '/status')
        self.conn = cups.Connection()
        self.cache_dir = Path("app/data/cache/prints")
        self.cache_dir.mkdir(parents=True, exist_ok=True)
        
        # Rate limiting and coordination
        self.min_print_interval = min_print_interval
        self._last_print_time = None
        self._print_lock = asyncio.Lock()
        
    async def _wait_for_printer_ready(self, max_wait: int = 300) -> bool:
        """Wait for the printer to be ready.
        
        Args:
            max_wait: Maximum time to wait in seconds
            
        Returns:
            bool: True if printer is ready, False if timeout
        """
        start_time = time.time()
        while time.time() - start_time < max_wait:
            try:
                async with aiohttp.ClientSession() as session:
                    async with session.get(self.status_url) as response:
                        if response.status == 200:
                            data = await response.json()
                            if data.get('status') == 'ready':
                                return True
            except Exception as e:
                logger.warning(f"Error checking printer status: {e}")
            
            await asyncio.sleep(1)
            
        logger.error("Timeout waiting for printer to be ready")
        return False
        
    async def print_file(self, file_path: Union[str, Path], printer_type: Literal["regular", "label"] = "regular", label_type: Optional[Literal["dk1201", "dk1241"]] = None) -> bool:
        """Print a PDF or PNG file.
        
        Args:
            file_path: Path to the PDF or PNG file
            printer_type: Type of printer ("regular" or "label")
            label_type: Type of label to use ("dk1201" or "dk1241"). Only used when printer_type is "label".
            
        Returns:
            bool: True if print was successful, False otherwise
        """
        try:
            file_path = Path(file_path)
            if not file_path.exists():
                logger.error(f"File not found: {file_path}")
                return False

            if printer_type == "regular":
                # For regular printers, use CUPS
                printer = self.printer_name or self.conn.getDefault()
                if not printer:
                    logger.error("No default printer found")
                    return False
                    
                job_id = self.conn.printFile(
                    printer,
                    str(file_path),
                    f"{file_path.suffix.upper()} Print",
                    {}
                )
                logger.info(f"Print job {job_id} submitted to printer {printer}")
                return True
            else:
                # For label printers, we need to coordinate requests
                if label_type is None:
                    logger.error("label_type must be specified when printing to label printer")
                    return False

                # Wait for printer to be ready
                if not await self._wait_for_printer_ready():
                    logger.error("Printer not ready after waiting")
                    return False

                if file_path.suffix.lower() == '.pdf':
                    # Convert PDF to image first
                    images = convert_from_path(file_path)
                    if not images:
                        logger.error(f"No images could be extracted from {file_path}")
                        return False
                    image = images[0]  # Only use first page
                else:
                    # For PNG files, we can use them directly
                    from PIL import Image
                    image = Image.open(file_path)
                
                # Convert to label format
                qlr = BrotherQLRaster("QL-1100")
                qlr.exception_on_warning = True
                
                # Get label size based on type
                label_size = "29x90" if label_type == "dk1201" else "102x152"
                
                converted_image = convert(
                    qlr=qlr,
                    images=[image],
                    label=label_size,
                    rotate="0",
                    threshold=70.0,
                    dither=False,
                    compress=False,
                    red=False,
                    dpi_600=False,
                    hq=True,
                    cut=True
                )
                
                # Cache the converted binary data
                cache_path = self.cache_dir / f"{file_path.stem}_{label_type}_converted.bin"
                with open(cache_path, "wb") as f:
                    f.write(converted_image)
                
                # Send to API
                return await self._send_print_request(cache_path)
                
        except Exception as e:
            logger.error(f"Error printing file {file_path}: {str(e)}")
            return False
            
    async def _send_print_request(self, file_path: Union[str, Path]) -> bool:
        """Send print data to printer API.
        
        Args:
            file_path: Path to the binary data file to send to the printer
            
        Returns:
            bool: True if request was successful, False otherwise
        """
        try:
            # Read the binary data from the cache file
            with open(file_path, "rb") as f:
                print_data = f.read()
            
            # Send the request to the printer API using aiohttp
            async with aiohttp.ClientSession() as session:
                async with session.post(
                    self.printer_api_url,
                    data=print_data,
                    timeout=30
                ) as response:
                    if response.status == 200:
                        return True
                    else:
                        response_text = await response.text()
                        logger.error(f"Print request failed with status {response.status}: {response_text}")
                        return False
                
        except Exception as e:
            logger.error(f"Error sending print request: {str(e)}")
            return False
