import sys
import types
import base64
import textwrap
import io
import os
import json
import subprocess
import shutil
import openpyxl
from PIL import Image, ImageDraw, ImageFont, ImageOps

# ================= ПАТЧ ДЛЯ PYTHON 3.12 (БЕЗ DISTUTILS) =================
if 'distutils' not in sys.modules:
    _distutils = types.ModuleType('distutils')
    _distutils_version = types.ModuleType('distutils.version')
    
    class LooseVersion:
        def __init__(self, vstring):
            self.vstring = str(vstring)
            self.version = tuple(
                int(x) if x.isdigit() else x 
                for x in self.vstring.replace('-', '.').split('.')
            )
        def __lt__(self, other):
            if isinstance(other, str): other = LooseVersion(other)
            return self.version < other.version
        def __ge__(self, other):
            if isinstance(other, str): other = LooseVersion(other)
            return self.version >= other.version
        def __eq__(self, other):
            if isinstance(other, str): other = LooseVersion(other)
            return self.version == other.version

    _distutils_version.LooseVersion = LooseVersion
    _distutils.version = _distutils_version
    sys.modules['distutils'] = _distutils
    sys.modules['distutils.version'] = _distutils_version

from pylibdmtx.pylibdmtx import encode

# ================= НАСТРОЙКИ ПЕЧАТИ =================
DPI = 203
LABEL_SIZE_MM = 20
TOTAL_DOTS = int((LABEL_SIZE_MM / 25.4) * DPI)
PRINTER_NAME = "XP-TT426B"
HISTORY_FILE = "/tmp/comments_history.json"

def get_font(is_bold, size):
    fonts = [
        "arialbd.ttf" if is_bold else "arial.ttf",
        "LiberationSans-Bold.ttf" if is_bold else "LiberationSans-Regular.ttf"
    ]
    for font_name in fonts:
        try:
            return ImageFont.truetype(font_name, size)
        except IOError:
            continue
    return ImageFont.load_default()

def draw_text_with_halo(draw, x, y, text, font, halo_color=(255, 255, 255), text_color=(0, 0, 0), thickness=3):
    for dx in range(-thickness, thickness + 1):
        for dy in range(-thickness, thickness + 1):
            if dx * dx + dy * dy <= thickness * thickness:
                draw.text((x + dx, y + dy), text, font=font, fill=halo_color)
    draw.text((x, y), text, font=font, fill=text_color)

def create_separator_label(text, direction):
    img = Image.new('1', (TOTAL_DOTS, TOTAL_DOTS), 1)
    draw = ImageDraw.Draw(img)
    
    pixels = img.load()
    for y in range(TOTAL_DOTS):
        for x in range(TOTAL_DOTS):
            if (x // 3) % 2 == 0 and (y // 3) % 2 == 0:
                pixels[x, y] = 0
            
    draw.rectangle([0, 0, TOTAL_DOTS-1, TOTAL_DOTS-1], outline=0, width=4)
    
    arrows = "↑ ↑ ↑" if direction == 'up' else "↓ ↓ ↓"
    margin = 8 
    half_h = TOTAL_DOTS // 2
    
    font_size_arr = 80
    font_arr = get_font(is_bold=True, size=font_size_arr)
    while font_size_arr > 8:
        bbox_arr = draw.textbbox((0, 0), arrows, font=font_arr)
        if (bbox_arr[2] - bbox_arr[0]) <= (TOTAL_DOTS - margin * 2) and (bbox_arr[3] - bbox_arr[1]) <= (half_h - margin * 2):
            break
        font_size_arr -= 2
        font_arr = get_font(is_bold=True, size=font_size_arr)
        
    font_size_txt = 80
    font_txt = get_font(is_bold=True, size=font_size_txt)
    while font_size_txt > 8:
        bbox_txt = draw.textbbox((0, 0), text, font=font_txt)
        if (bbox_txt[2] - bbox_txt[0]) <= (TOTAL_DOTS - margin * 2) and (bbox_txt[3] - bbox_txt[1]) <= (half_h - margin * 2):
            break
        font_size_txt -= 2
        font_txt = get_font(is_bold=True, size=font_size_txt)
        
    w_arr, h_arr = bbox_arr[2] - bbox_arr[0], bbox_arr[3] - bbox_arr[1]
    draw_text_with_halo(draw, (TOTAL_DOTS - w_arr) // 2, (half_h - h_arr) // 2 - bbox_arr[1], arrows, font_arr, 1, 0)
    
    w_txt, h_txt = bbox_txt[2] - bbox_txt[0], bbox_txt[3] - bbox_txt[1]
    draw_text_with_halo(draw, (TOTAL_DOTS - w_txt) // 2, half_h + (half_h - h_txt) // 2 - bbox_txt[1], text, font_txt, 1, 0)
    
    return img

def create_label(b64_dm, article_text, name_text):
    img = Image.new('1', (TOTAL_DOTS, TOTAL_DOTS), 1)
    draw = ImageDraw.Draw(img)
    PAD = 6 

    dm_bytes = base64.b64decode(b64_dm)
    encoded = encode(dm_bytes)
    dm_img = Image.frombytes('RGB', (encoded.width, encoded.height), encoded.pixels)
    
    gray = dm_img.convert('L')
    inverted = ImageOps.invert(gray)
    bbox = inverted.getbbox()
    if bbox: dm_img = dm_img.crop(bbox)
        
    pixels = dm_img.convert('L').load()
    module_size = next((x for x in range(1, dm_img.width) if pixels[x, 0] != pixels[0, 0]), 1)
            
    modules_x = dm_img.width // module_size
    modules_y = dm_img.height // module_size
    small_dm = dm_img.resize((modules_x, modules_y), Image.NEAREST)
    
    max_dm_space = 128 
    scale = max_dm_space // modules_x
    if scale < 1: scale = 1
    
    final_dm_size = modules_x * scale
    final_dm = small_dm.resize((final_dm_size, final_dm_size), Image.NEAREST)
    img.paste(final_dm, (PAD, PAD))

    GAP = 2 
    art_w = TOTAL_DOTS - final_dm_size - PAD - GAP  
    art_h = final_dm_size                   
    
    if art_w > 10:
        font_size = 100
        font_article = get_font(is_bold=True, size=font_size)
        
        dummy_img = Image.new('1', (1, 1))
        dummy_draw = ImageDraw.Draw(dummy_img)
        while font_size > 8:
            bbox = dummy_draw.textbbox((0,0), article_text, font=font_article)
            text_w, text_h = bbox[2] - bbox[0], bbox[3] - bbox[1]
            if text_w <= art_h and text_h <= art_w:
                break
            font_size -= 1
            font_article = get_font(is_bold=True, size=font_size)
        
        art_img = Image.new('1', (art_h, art_w), 1)
        art_draw = ImageDraw.Draw(art_img)
        
        x_pos = (art_h - text_w) // 2
        y_pos = (art_w - text_h) // 2 - bbox[1]
        
        art_draw.text((x_pos, y_pos), article_text, font=font_article, fill=0)
        art_img = art_img.rotate(90, expand=True)
        img.paste(art_img, (PAD + final_dm_size + GAP, PAD))

    font_name = get_font(is_bold=False, size=11)
    y_text = PAD + final_dm_size + GAP + 2
    chars_per_line = 24
    wrapped_name = textwrap.wrap(name_text, width=chars_per_line)
    
    for line in wrapped_name:
        if y_text + 11 > TOTAL_DOTS - PAD: 
            break 
        draw.text((PAD, y_text), line, font=font_name, fill=0)
        y_text += 12

    return img

def print_batch_cups(printer_name, images):
    if not images:
        return False, "Нет изображений для печати."
    
    if not shutil.which("lp"):
        return False, "Клиент CUPS (команда lp) не найден."

    pdf_bytes = io.BytesIO()
    images[0].save(
        pdf_bytes, 
        format="PDF", 
        resolution=203.0, 
        save_all=True, 
        append_images=images[1:]
    )
    pdf_data = pdf_bytes.getvalue()
    
    try:
        process = subprocess.run(
            [
                "lp", 
                "-d", printer_name, 
                "-o", f"media=Custom.{LABEL_SIZE_MM}x{LABEL_SIZE_MM}mm", 
                "-o", "fit-to-page", 
                "-"
            ], 
            input=pdf_data,
            check=True,
            capture_output=True
        )
        return True, process.stdout.decode('utf-8', errors='ignore').strip()
    except subprocess.CalledProcessError as e:
        err_msg = e.stderr.decode('utf-8', errors='ignore').strip() if e.stderr else str(e)
        return False, f"Ошибка печати CUPS: {err_msg}"

def load_history():
    if os.path.exists(HISTORY_FILE):
        try:
            with open(HISTORY_FILE, 'r', encoding='utf-8') as f:
                return json.load(f)
        except: pass
    return []

def save_history(comment):
    history = load_history()
    if comment in history:
        history.remove(comment)
    history.insert(0, comment)
    history = history[:10]
    try:
        with open(HISTORY_FILE, 'w', encoding='utf-8') as f:
            json.dump(history, f, ensure_ascii=False)
    except: pass

# ================= ТОЧКА ВХОДА СТРИМЛИТ =================
def run(plugin_info):
    import streamlit as st
    
    st.markdown(plugin_info.get("full_description", ""))
    
    with st.container(border=True):
        st.subheader("Настройки печати")
        col1, col2 = st.columns(2)
        
        with col1:
            print_separators = st.checkbox("Печатать разделители артикулов", value=True)
            
        with col2:
            print_comment = st.checkbox("Печатать комментарий", value=False)
            comment_text = ""
            if print_comment:
                history = load_history()
                options = ["Новый комментарий..."] + history
                selected_option = st.selectbox("Выберите или введите:", options)
                
                if selected_option == "Новый комментарий...":
                    comment_text = st.text_input("Введите текст", placeholder="Например: Сборка №1")
                else:
                    comment_text = selected_option

    with st.container(border=True):
        st.subheader("Данные для печати")
        st.info(
            "Программа собирает данные по точным названиям заголовков (порядок не важен):\n"
            " • **Код маркировки в base64**\n"
            " • **Номенклатура.Артикул**\n"
            " • **Номенклатура.Наименование**\n"
            "Остальные колонки игнорируются."
        )
        
        uploaded_file = st.file_uploader("Загрузите файл Excel", type=["xlsx"])
        
        if st.button("НАЧАТЬ ПЕЧАТЬ", type="primary", use_container_width=True):
            if not uploaded_file:
                st.error("Пожалуйста, загрузите файл Excel перед началом печати.")
                return
                
            with st.spinner("Сборка этикеток, пожалуйста подождите..."):
                try:
                    wb = openpyxl.load_workbook(uploaded_file, data_only=True)
                    sheet = wb.active
                    headers = [str(cell.value).strip() if cell.value else "" for cell in sheet[1]]
                    
                    try:
                        b64_idx = headers.index('Код маркировки в base64')
                        art_idx = headers.index('Номенклатура.Артикул')
                        name_idx = headers.index('Номенклатура.Наименование')
                    except ValueError:
                        st.error("Ошибка: Не найдены обязательные заголовки в таблице. Проверьте соответствие с памяткой.")
                        return

                    articles_data = {}
                    for row in sheet.iter_rows(min_row=2, values_only=True):
                        b64_dm = row[b64_idx]
                        if not b64_dm: 
                            continue
                        
                        b64_dm = str(b64_dm).strip()
                        article = str(row[art_idx] if row[art_idx] is not None else '').strip()
                        if article.endswith('.0'): 
                            article = article[:-2]
                        name = str(row[name_idx] if row[name_idx] is not None else '').strip()
                        
                        if article not in articles_data:
                            articles_data[article] = []
                        articles_data[article].append((b64_dm, name))

                    if not articles_data:
                        st.error("В файле нет данных для печати.")
                        return

                    unique_articles_count = len(articles_data)
                    images_to_print = [] 

                    if print_comment and comment_text.strip():
                        images_to_print.append(create_separator_label(comment_text.strip(), 'up'))

                    for article, items in articles_data.items():
                        if print_separators and unique_articles_count > 1:
                            images_to_print.append(create_separator_label(article, 'up'))

                        for b64_dm, name in items:
                            images_to_print.append(create_label(b64_dm, article, name))

                        if print_separators and unique_articles_count > 1:
                            images_to_print.append(create_separator_label(article, 'down'))

                    if print_comment and comment_text.strip():
                        images_to_print.append(create_separator_label(comment_text.strip(), 'down'))
                    
                    st.success(f"Сформировано {len(images_to_print)} этикеток. Отправка на принтер...")

                    success, msg = print_batch_cups(PRINTER_NAME, images_to_print)
                    
                    if success:
                        if print_comment and comment_text.strip():
                            save_history(comment_text.strip())
                        st.success("Готово! Задание успешно отправлено на принтер.")
                    else:
                        st.error(msg)
                        
                except Exception as e:
                    st.error(f"Произошла ошибка при обработке файла: {str(e)}")