import sys
import types
import base64
import textwrap
import io
import os
import json
import subprocess
import shutil
import datetime
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"
BASE_DIR = os.path.dirname(__file__)
HISTORY_FILE = os.path.join(BASE_DIR, "comments_history.json")

def check_printer_connection(printer_name):
    if not shutil.which("lpstat"):
        return True, ""
    try:
        res = subprocess.run(["lpstat", "-p", printer_name], capture_output=True, text=True)
        output = res.stdout.strip().lower()
        
        if res.returncode != 0 or "unknown printer" in output:
            return False, f"Принтер '{printer_name}' не найден в системе CUPS."
            
        if "waiting for printer to become available" in output:
            return False, "Принтер физически недоступен (потерян USB-проброс). Проверьте кабель и переподключите принтер в Proxmox."
            
        if "disabled since" in output or "paused" in output:
            return False, "Принтер установлен на паузу в CUPS из-за предыдущих ошибок. Очистите очередь и возобновите печать."
            
        return True, ""
    except Exception as e:
        return False, f"Ошибка проверки состояния принтера: {e}"

def get_font(is_bold, size):
    # Приоритет: DejaVu Sans -> Tahoma/Verdana -> Arial -> Liberation
    fonts = [
        "DejaVuSans-Bold.ttf" if is_bold else "DejaVuSans.ttf",
        "tahoma.ttf",
        "verdana.ttf",
        "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)
    
    # 7 мм при 203 DPI = ~56 пикселей
    border_h = 56
    stripe = 12
    
    # Расставляем стрелки так, как вы показали
    arrows = "↑ ↑ ↑" if direction == 'up' else "↓ ↓ ↓"
    
    pixels = img.load()
    for y in range(TOTAL_DOTS):
        for x in range(TOTAL_DOTS):
            # Определяем, попадает ли пиксель в зону полоски
            in_strip = False
            if direction == 'up' and y < border_h:
                in_strip = True
            elif direction == 'down' and y >= TOTAL_DOTS - border_h:
                in_strip = True
                
            if in_strip:
                # Отрисовка косых линий (\\\\\\\\)
                if (x + y) % (stripe * 2) < stripe:
                    pixels[x, y] = 0
            else:
                pixels[x, y] = 1 # Внутри чисто белое поле
                
    # Добавляем тонкую сплошную линию, чтобы отбить штриховку от текста
    if direction == 'up':
        draw.line([(0, border_h), (TOTAL_DOTS, border_h)], fill=0, width=2)
    else:
        draw.line([(0, TOTAL_DOTS - border_h), (TOTAL_DOTS, TOTAL_DOTS - border_h)], fill=0, width=2)
        
    margin = 4 
    avail_h = TOTAL_DOTS - border_h # Высота под текст и стрелки (около 104 пикселей)
    inner_w = TOTAL_DOTS
    half_h = avail_h // 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]) <= (inner_w - 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]) <= (inner_w - 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]
    w_txt, h_txt = bbox_txt[2] - bbox_txt[0], bbox_txt[3] - bbox_txt[1]
    
    # Вычисляем Y-координаты в зависимости от направления
    if direction == 'up':
        y_arr_start = border_h           # Сразу под полосой
        y_txt_start = border_h + half_h  # В самом низу
    else:
        y_txt_start = 0                  # В самом верху
        y_arr_start = half_h             # Сразу над полосой
        
    x_arr = (inner_w - w_arr) // 2
    y_arr = y_arr_start + (half_h - h_arr) // 2 - bbox_arr[1]
    
    x_txt = (inner_w - w_txt) // 2
    y_txt = y_txt_start + (half_h - h_txt) // 2 - bbox_txt[1]
    
    # Печатаем текст. Ореол сделали тоньше (thickness=1), так как фон теперь чисто белый
    draw_text_with_halo(draw, x_arr, y_arr, arrows, font_arr, 1, 0, thickness=1)
    draw_text_with_halo(draw, x_txt, y_txt, text, font_txt, 1, 0, thickness=1)
    
    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_X = 8  # Отступ слева (~1 мм)
    PAD_Y = 10 # Отступ сверху (~1.2 мм)
    GAP = 2    # Возвращаем минимальный зазор, чтобы артикул был крупнее

    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_X, PAD_Y))

    # Высчитываем место под вертикальный артикул
    art_w = TOTAL_DOTS - final_dm_size - PAD_X - GAP - 2
    art_h = final_dm_size                   
    
    if art_w > 8:
        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_X + final_dm_size + GAP, PAD_Y))

    # Наименование снизу
    font_name = get_font(is_bold=False, size=11)
    y_text = PAD_Y + final_dm_size + GAP
    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 - 2: 
            break 
        draw.text((PAD_X, y_text), line, font=font_name, fill=0)
        y_text += 12

    return img

def generate_pdf(images):
    if not images:
        return None
    pdf_bytes = io.BytesIO()
    images[0].save(
        pdf_bytes, 
        format="PDF", 
        resolution=203.0, 
        save_all=True, 
        append_images=images[1:]
    )
    return pdf_bytes.getvalue()

def print_pdf_cups(printer_name, pdf_data):
    if not shutil.which("lp"):
        return False, "Клиент CUPS (команда lp) не найден."
    
    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):
    if not comment:
        return
    history = load_history()
    if comment in history:
        history.remove(comment)
    history.insert(0, comment)
    history = history[:15]
    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", ""))
    
    # 1. Проверка состояния принтера при открытии страницы
    printer_ok, printer_msg = check_printer_connection(PRINTER_NAME)
    if not printer_ok:
        st.error(f"⚠️ **Внимание: Обнаружена проблема с принтером!**\n\n{printer_msg}")
    
    if "comment_input" not in st.session_state:
        st.session_state.comment_input = ""

    def apply_history():
        if st.session_state.history_picker:
            st.session_state.comment_input = st.session_state.history_picker

    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)
            if print_comment:
                st.text_input("Текст комментария", key="comment_input", placeholder="Введите текст...")
                
                history = load_history()
                if history:
                    st.selectbox(
                        "Или выберите из истории:", 
                        [""] + history, 
                        key="history_picker", 
                        on_change=apply_history,
                        label_visibility="collapsed"
                    )
            
            comment_text = st.session_state.comment_input if print_comment else ""

    with st.container(border=True):
        st.subheader("Данные для печати")
        
        uploaded_file = st.file_uploader("Загрузите файл Excel", type=["xlsx"])
        
        # Если файл загружен, сразу проверяем его содержимое
        if uploaded_file:
            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]]
                
                required_headers = ['Код маркировки в base64', 'Номенклатура.Артикул', 'Номенклатура.Наименование']
                missing_headers = [h for h in required_headers if h not in headers]
                
                if missing_headers:
                    st.error(f"**Ошибка!** В файле отсутствуют обязательные колонки:\n\n" + "\n".join([f"❌ {h}" for h in missing_headers]))
                else:
                    b64_idx = headers.index('Код маркировки в base64')
                    art_idx = headers.index('Номенклатура.Артикул')
                    name_idx = headers.index('Номенклатура.Наименование')

                    articles_data = {}
                    valid_labels_count = 0
                    
                    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))
                        valid_labels_count += 1

                    if valid_labels_count == 0:
                        st.error("В файле нет данных для печати (или обязательные колонки пустые).")
                    else:
                        st.success(f"✅ Файл успешно проверен. Готово к печати этикеток: **{valid_labels_count} шт.**")
                        
                        # Кнопка печати появляется ТОЛЬКО если файл валидный
                        if st.button("НАЧАТЬ ПЕЧАТЬ", type="primary", use_container_width=True):
                            
                            # 2. Повторная проверка прямо перед отправкой
                            printer_ok, printer_msg = check_printer_connection(PRINTER_NAME)
                            if not printer_ok:
                                st.error(f"Печать отменена. Устраните проблему с оборудованием:\n\n{printer_msg}")
                                return
                                
                            with st.spinner("Сборка этикеток, пожалуйста подождите..."):
                                unique_articles_count = len(articles_data)
                                images_to_print = [] 

                                # Стартовый комментарий (теперь 'down', чтобы стрелки указывали на этикетки)
                                if print_comment and comment_text.strip():
                                    images_to_print.append(create_separator_label(comment_text.strip(), 'down'))

                                for article, items in articles_data.items():
                                    # Открывающий разделитель артикула
                                    if print_separators and unique_articles_count > 1:
                                        images_to_print.append(create_separator_label(article, 'down'))

                                    # Сами товарные этикетки
                                    for b64_dm, name in items:
                                        images_to_print.append(create_label(b64_dm, article, name))

                                    # Закрывающий разделитель артикула (теперь 'up', закрывает пачку)
                                    if print_separators and unique_articles_count > 1:
                                        images_to_print.append(create_separator_label(article, 'up'))

                                # Финишный комментарий
                                if print_comment and comment_text.strip():
                                    images_to_print.append(create_separator_label(comment_text.strip(), 'up'))
                                
                                pdf_data = generate_pdf(images_to_print)
                                success, msg = print_pdf_cups(PRINTER_NAME, pdf_data)
                                
                                if success:
                                    if print_comment:
                                        save_history(comment_text.strip())
                                        
                                    try:
                                        date_str = datetime.datetime.now().strftime("%Y-%m-%d")
                                        time_str = datetime.datetime.now().strftime("%H-%M-%S")
                                        
                                        history_dir = os.path.join(BASE_DIR, "History", date_str)
                                        os.makedirs(history_dir, exist_ok=True)
                                        
                                        excel_path = os.path.join(history_dir, f"{time_str}_source_{uploaded_file.name}")
                                        pdf_path = os.path.join(history_dir, f"{time_str}_labels.pdf")
                                        
                                        with open(excel_path, "wb") as f:
                                            f.write(uploaded_file.getvalue())
                                            
                                        with open(pdf_path, "wb") as f:
                                            f.write(pdf_data)
                                            
                                        st.success(f"Готово! Задание успешно отправлено на принтер. Копии сохранены в папку History/{date_str}")
                                    except Exception as e:
                                        st.warning(f"Этикетки отправлены на принтер, но произошла ошибка при сохранении файлов в архив: {e}")
                                else:
                                    st.error(msg)
                                    
            except Exception as e:
                st.error(f"Произошла ошибка при обработке файла: {str(e)}")