import logging
import json
import re
from typing import Optional
from datetime import datetime

from median.models import Product, Printer, Config
from median.views import PrinterView, PrinterViewException, RawConfig
from peewee import DoesNotExist
from flask import session
from common.templating import render_template

logger = logging.getLogger("median.completion")
loggerPrint = logging.getLogger("median.printer")


class LabelService:
    """Service for handling label printing operations"""

    def print_label_nominative(self, printer: Printer, data: dict, patient_data: dict) -> bool:
        """Print nominative label for patient-specific medication"""
        try:
            with PrinterView(printer, 3, session["user_id"]) as p:
                template, _ = p.printer_template("external_box_nominative_label")
                rendered_label = self._render_label(data, patient_data, template)
                print_result = p.send(rendered_label, data["serial"])
                logger.debug(f"Print result for printer {p.printer.address}: {print_result}")
                if not print_result:
                    error_msg = f"Failed to print label with serial {data['serial']}"
                    logger.error(error_msg)
                    raise Exception(error_msg)
            return True

        except PrinterViewException as e:
            logger.error(f"PrinterView: {e}")
            raise Exception(f"Printer error: {str(e)}")
        except Exception as e:
            logger.error(str(e))
            raise

    def print_label_global(self, printer: Printer, data: dict, ward_data: dict) -> bool:
        """Print global label for ward-level medication"""
        try:
            with PrinterView(printer, 3, session["user_id"]) as p:
                template, _ = p.printer_template("external_box_global_label")
                rendered_label = self._render_label(data, ward_data, template)
                print_result = p.send(rendered_label, data["serial"])
                logger.debug(f"Print result for printer {p.printer.address}: {print_result}")
                if not print_result:
                    error_msg = f"Failed to print label with serial {data['serial']}"
                    logger.error(error_msg)
                    raise Exception(error_msg)
            return True

        except PrinterViewException as e:
            logger.error(f"PrinterView: {e}")
            raise Exception(f"Printer error: {str(e)}")
        except Exception as e:
            logger.error(str(e))
            raise

    def _render_label(self, data: dict, additional_data: dict, template: str) -> str:
        """Render label template with provided data"""
        try:
            product: Product = Product.get_or_none(Product.reference == data["ref"])
            if not product:
                raise Exception(f"Print Label: Product not found for reference {data['ref']}")

            try:
                cfg: Config = RawConfig().read("k_eco_client")

                external_box_dict = {
                    "first_name": additional_data.get("first_name", None),
                    "last_name": additional_data.get("last_name", None),
                    "maiden_name": additional_data.get("maiden_name", None),
                    "birthdate": additional_data.get("birthdate", None),
                    "stay": additional_data.get("stay", None),
                    "ward_code": additional_data.get("ward_code", None),
                    "ward_name": additional_data.get("ward_name", None),
                    "fraction": "100",  # This is a fullpack
                    "product_desig": product.designation or None,
                    "product_desig_bis": product.desig_bis or None,
                    "product_gtin": data["product"].get("gtin", None),
                    "date_perem": self._parse_datetime(data["product"].get("perem", None)),
                    "batch": data["product"].get("lot", None),
                    "quantity": data["product"].get("quantity", None),
                    "serial": data.get("serial", None),
                    "comment": product.com_med or None,
                    "client_header": cfg.value if cfg else None,
                }

                rendered_zpl = render_template(template, {}, external_box_dict)
                return rendered_zpl
            except json.JSONDecodeError as e:
                loggerPrint.error(f"Print Label: JSON parsing error: {str(e)}")
                raise Exception(f"Print Label: Error parsing ZPL dictionary: {str(e)}")
            except Exception as e:
                loggerPrint.error(f"Print Label: Template rendering error: {str(e)}")
                raise Exception(f"Print Label: Error rendering template: {str(e)}")

        except DoesNotExist as e:
            loggerPrint.error(f"Print Label: Database record not found: {str(e)}")
            raise Exception(f"Print Label: Required data not found: {str(e)}")
        except Exception as e:
            loggerPrint.error(f"Print Label: Unexpected error: {str(e)}")
            raise Exception(f"Print Label: {str(e)}")

    def _parse_datetime(self, date_str: Optional[str]) -> Optional[datetime]:
        """Parse expiration date from YYMMDD or YYYY/MM/DD format"""
        date_peremption = None
        if date_str:
            # Try YYMMDD format
            yymmdd_match = re.match(r"^(\d{2})(\d{2})(\d{2})$", date_str)
            if yymmdd_match:
                year, month, day = yymmdd_match.groups()
                try:
                    date_peremption = datetime(2000 + int(year), int(month), int(day))
                except ValueError as e:
                    logger.error("Datamatrix exp. date error : " + str(e))
                    date_peremption = None

            # Try YYYY/MM/DD format
            else:
                yyyy_mm_dd_match = re.match(r"^(\d{4})[/\-](\d{2})[/\-](\d{2})$", date_str)
                if yyyy_mm_dd_match:
                    year, month, day = yyyy_mm_dd_match.groups()
                    try:
                        date_peremption = datetime(int(year), int(month), int(day))
                    except ValueError:
                        date_peremption = None

        return date_peremption
