# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError
import requests
import base64
import logging

_logger = logging.getLogger(__name__)


class ProductTemplate(models.Model):
    _inherit = 'product.template'

    @api.constrains('shopify_published', 'categ_id')
    def _check_shopify_published_category(self):
        for record in self:
            if record.shopify_published and record.categ_id:
                if 'Custom' in record.categ_id.name or 'Insumo' in record.categ_id.name:
                    raise UserError("Los productos de la categoría Custom o Insumos no pueden ser publicados en Shopify.")

    # ── IDs de Shopify (ya existían) ─────────────────────────
    shopify_id = fields.Char(
        string='ID de Shopify (Template)',
        copy=False, index=True, readonly=True,
    )

    # ── Campos nuevos: control de publicación ─────────────────
    shopify_published = fields.Boolean(
        string='Publicado en Shopify',
        default=False,
        copy=False,
        help="Si está activo, este producto existe en Shopify y se mantiene sincronizado automáticamente.",
    )
    shopify_published_online = fields.Boolean(
        string='Disponible en Venta en Línea',
        default=True,
        copy=True,
        help="Si está marcado, el producto se publicará en la Tienda Online de Shopify.",
    )
    shopify_published_pos = fields.Boolean(
        string='Disponible en Punto de Venta (POS)',
        default=True,
        copy=True,
        help="Si está marcado, el producto se publicará en el Point of Sale (POS) de Shopify.",
    )
    shopify_config_id = fields.Many2one(
        'shopify.config',
        string='Configuración Shopify',
        copy=False,
        help="Tienda de Shopify donde se publica este producto. Si se deja vacío, se usa la primera configuración activa.",
    )
    shopify_status = fields.Selection(
        selection=[
            ('draft',  'Borrador (draft)'),
            ('active', 'Activo (active)'),
        ],
        string='Estado en Shopify',
        default='draft',
        copy=False,
        help="Estado de publicación de este producto en Shopify.",
    )
    shopify_synced_at = fields.Datetime(
        string='Primera sincronización',
        copy=False, readonly=True,
        help="Fecha en que el producto fue importado/sincronizado por primera vez desde Shopify.",
    )
    shopify_last_pushed_at = fields.Datetime(
        string='Última actualización a Shopify',
        copy=False, readonly=True,
        help="Fecha de la última vez que se envió una actualización de este producto a Shopify.",
    )
    shopify_sync_status = fields.Selection(
        selection=[
            ('pending',  '⏳ Pendiente'),
            ('synced',   '✅ Sincronizado'),
            ('error',    '❌ Error'),
        ],
        string='Estado Shopify',
        default='pending',
        copy=False,
        readonly=True,
    )
    shopify_sync_error = fields.Char(
        string='Último error Shopify',
        copy=False, readonly=True,
    )
    shopify_publication_ids = fields.One2many(
        'shopify.product.publication',
        'product_tmpl_id',
        string='Canales de Venta Shopify',
        copy=False,
    )

    # ── Auditoría: tracking de cambios (ya existían) ─────────
    list_price = fields.Float(tracking=True)
    standard_price = fields.Float(tracking=True)
    default_code = fields.Char(tracking=True)
    barcode = fields.Char(tracking=True)

    # ──────────────────────────────────────────────────────────
    # HELPER: obtener la config de Shopify activa
    # ──────────────────────────────────────────────────────────
    def _get_shopify_config(self):
        """
        Devuelve la shopify.config a usar para este producto.
        Prioridad: campo shopify_config_id → primera config confirmada del sistema.
        """
        self.ensure_one()
        config = self.shopify_config_id
        if not config:
            config = self.env['shopify.config'].search(
                [('state', '=', 'confirmed')], limit=1
            )
        if not config:
            raise UserError(
                "No hay una configuración de Shopify activa.\n"
                "Ve a Shopify Connect → Configuración y confirma una tienda."
            )
        return config

    # ──────────────────────────────────────────────────────────
    # ACCIÓN PRINCIPAL: Publicar / Actualizar en Shopify
    # ──────────────────────────────────────────────────────────
    def action_push_to_shopify(self):
        """
        Botón manual: crea o actualiza el producto en Shopify.
        - Si shopify_id está vacío → POST (crear nuevo producto en Shopify).
        - Si shopify_id tiene valor → PUT (actualizar el existente).
        Maneja variantes e imagen principal.
        """
        self.ensure_one()
        
        if self.categ_id and ('Custom' in self.categ_id.name or 'Insumo' in self.categ_id.name):
            raise UserError("No se permite exportar a Shopify productos de la categoría Custom o Insumo.")

        config = self._get_shopify_config()
        token = config._ensure_access_token()

        shop_domain = (config.shop_url or '').strip().lower()
        if '://' in shop_domain:
            shop_domain = shop_domain.split('://')[1].split('/')[0]

        headers = {
            'X-Shopify-Access-Token': token,
            'Content-Type': 'application/json',
        }
        api_base = f"https://{shop_domain}/admin/api/2024-01"

        # ── Respaldo de Barcodes desde Shopify ──────────────────
        # Si ya existe en Shopify, consultamos el producto actual para recuperar barcodes.
        # Esto evita que Odoo borre barcodes en Shopify si localmente estaban vacíos.
        if self.shopify_id:
            try:
                check_url = f"{api_base}/products/{self.shopify_id}.json"
                check_resp = requests.get(check_url, headers=headers, timeout=15)
                if check_resp.status_code == 200:
                    prod_data = check_resp.json().get('product', {})
                    shopify_barcodes = {}
                    for var in prod_data.get('variants', []):
                        v_id = str(var.get('id'))
                        barcode = var.get('barcode')
                        if barcode:
                            shopify_barcodes[v_id] = barcode
                    
                    # Actualizar variantes de Odoo con el barcode de Shopify si en Odoo está vacío
                    for v in self.product_variant_ids:
                        s_var_id = v.shopify_variant_id
                        if s_var_id and s_var_id in shopify_barcodes and not v.barcode:
                            v.sudo().write({'barcode': shopify_barcodes[s_var_id]})
            except Exception as e_bc:
                _logger.warning("[Shopify Push] No se pudieron resguardar los barcodes de Shopify: %s", str(e_bc))

        # ── Construir payload ─────────────────────────────────
        is_create = not bool(self.shopify_id)
        payload = self._build_shopify_product_payload(is_create=is_create)
        warning_messages = []

        try:
            if self.shopify_id:
                # ── UPDATE ────────────────────────────────────
                # CANDADO PRO: Para evitar errores de Shopify 422 con opciones ligadas a Metafields,
                # extraemos las variantes del payload del producto y las actualizamos individualmente.
                variants_payload = payload.pop('variants', [])
                url = f"{api_base}/products/{self.shopify_id}.json"
                resp = requests.put(url, headers=headers, json={'product': payload}, timeout=25)
                
                # Actualizar variantes individualmente
                has_new_variants = False
                if resp.status_code in (200, 201):
                    for var_data in variants_payload:
                        v_id = var_data.get('id')
                        if v_id:
                            v_url = f"{api_base}/variants/{v_id}.json"
                            v_resp = requests.put(v_url, headers=headers, json={'variant': var_data}, timeout=15)
                            if v_resp.status_code not in (200, 201):
                                _logger.warning("[Shopify Push] Error actualizando variante %s: %s", v_id, v_resp.text[:200])
                                warning_messages.append(f"Variante {v_id} no actualizada.")
                        else:
                            # Si no tiene ID, es una nueva variante, la agregamos al producto
                            v_url = f"{api_base}/products/{self.shopify_id}/variants.json"
                            v_resp = requests.post(v_url, headers=headers, json={'variant': var_data}, timeout=15)
                            if v_resp.status_code not in (200, 201):
                                try:
                                    actual_error = v_resp.json().get('errors', v_resp.text[:200])
                                except Exception:
                                    actual_error = v_resp.text[:200]
                                _logger.warning("[Shopify Push] Error creando variante: %s", actual_error)
                                warning_messages.append(f"No se pudo crear la variante (SKU: {var_data.get('sku')}). Shopify respondió: {actual_error}")
                            else:
                                has_new_variants = True
                                
                # Siempre volver a obtener el producto para sincronizar IDs de variantes
                # por si falló la creación porque la variante ya existía en Shopify
                if True:
                    # Refetch product data to get the new variant IDs
                    fetch_url = f"{api_base}/products/{self.shopify_id}.json"
                    fetch_resp = requests.get(fetch_url, headers=headers, timeout=15)
                    if fetch_resp.status_code == 200:
                        product_data = fetch_resp.json().get('product', {})
                    else:
                        product_data = resp.json().get('product', {})
                else:
                    product_data = resp.json().get('product', {})

            else:
                # ── CREATE ────────────────────────────────────
                url = f"{api_base}/products.json"
                resp = requests.post(url, headers=headers, json={'product': payload}, timeout=25)
                product_data = resp.json().get('product', {})

            if resp.status_code not in (200, 201):
                error_msg = f"HTTP {resp.status_code}: {resp.text[:300]}"
                self.write({
                    'shopify_sync_status': 'error',
                    'shopify_sync_error': error_msg,
                })
                raise UserError(
                    f"Error al publicar '{self.name}' en Shopify:\n{error_msg}"
                )

            new_shopify_id = str(product_data.get('id', ''))

            # ── Guardar IDs de variantes en Odoo ─────────────
            self._sync_variant_ids_from_shopify(product_data)

            # ── Sincronizar stock inicial a Shopify ───────────
            if config:
                try:
                    config._sync_variants_stock_to_shopify(self.product_variant_ids)
                except Exception as e:
                    _logger.warning("[Shopify Push] Excepción sincronizando stock de variantes: %s", str(e))

            # ── Subir imagen si tiene (SOLO AL CREAR) ─────────
            # CANDADO PRO: No sobreescribir imágenes de Shopify en actualizaciones
            if self.image_1920 and new_shopify_id and is_create:
                self._push_image_to_shopify(api_base, new_shopify_id, headers)

            # ── Sincronizar canales de venta (Online vs POS) ──
            if new_shopify_id:
                self._sync_shopify_channels(api_base, headers, new_shopify_id)

            now = fields.Datetime.now()
            write_vals = {
                'shopify_id':              new_shopify_id,
                'shopify_published':       True,
                'shopify_config_id':       config.id,
                'shopify_last_pushed_at':  now,
                'shopify_sync_status':     'synced',
                'shopify_sync_error':      False,
            }
            # Solo escribir shopify_synced_at la primera vez (cuando se crea)
            if is_create:
                write_vals['shopify_synced_at'] = now
            self.write(write_vals)

            action_label = "creado" if not self.shopify_id else "actualizado"
            _logger.info("[Shopify Push] Producto '%s' %s. ID Shopify: %s",
                         self.name, action_label, new_shopify_id)

            final_message = f"'{self.name}' fue {action_label} correctamente en Shopify."
            if warning_messages:
                final_message += "\n\n⚠️ ADVERTENCIAS:\n" + "\n".join(warning_messages)

            return {
                'type': 'ir.actions.client',
                'tag': 'display_notification',
                'params': {
                    'title': '✅ Publicado' if not warning_messages else '⚠️ Publicado con advertencias',
                    'message': final_message,
                    'type': 'success' if not warning_messages else 'warning',
                    'sticky': bool(warning_messages),
                }
            }

        except requests.exceptions.Timeout:
            self.write({'shopify_sync_status': 'error',
                        'shopify_sync_error': 'Timeout al conectar con Shopify'})
            raise UserError("Timeout: Shopify tardó demasiado en responder. Intenta de nuevo.")
        except UserError:
            raise
        except Exception as e:
            self.write({'shopify_sync_status': 'error',
                        'shopify_sync_error': str(e)[:200]})
            raise UserError(f"Error inesperado: {str(e)}")

    # ──────────────────────────────────────────────────────────
    # HELPER: Construir el payload para Shopify
    # ──────────────────────────────────────────────────────────
    def _build_shopify_product_payload(self, is_create=False):
        """
        Construye el dict de producto para la API de Shopify.
        Maneja productos simples y productos con variantes (atributos).
        """
        self.ensure_one()

        payload = {
            'title':     self.name,
            'vendor':    self.categ_id.name if self.categ_id else 'Hazard',
            'status':    self.shopify_status or 'draft',
        }

        # CANDADO PRO: Solo mandar descripción cuando se CREA el producto por primera vez.
        # Si es actualización (is_create=False), omitimos body_html para no borrar el trabajo en Shopify.
        if is_create:
            description = self.description_sale or self.description or ''
            payload['body_html'] = description

        # ── Variantes ─────────────────────────────────────────
        # Odoo puede tener variantes via product.attribute.
        # Si solo tiene 1 variante → payload simple con precio/SKU directo.
        # Si tiene múltiples → construimos el array de variantes.
        variants = self.product_variant_ids

        if len(variants) == 1:
            v = variants[0]
            v_payload = {
                'sku':   v.default_code or self.default_code or '',
                'price': str(self.list_price),
                'inventory_management': 'shopify',
                'inventory_policy':     'deny',
            }
            if v.barcode:  # Solo enviar barcode si Odoo tiene uno
                v_payload['barcode'] = v.barcode
            if v.shopify_variant_id:
                v_payload['id'] = v.shopify_variant_id
            payload['variants'] = [v_payload]

        else:
            # Múltiples variantes
            options = []
            seen_attrs = []
            for v in variants:
                for val in v.product_template_attribute_value_ids:
                    attr_name = val.attribute_id.name
                    if attr_name not in seen_attrs:
                        seen_attrs.append(attr_name)

            # CANDADO PRO: No enviar 'options' en actualizaciones para evitar 
            # error de Shopify con opciones vinculadas a metafields (Standard Taxonomy).
            if is_create:
                # Máximo 3 options en Shopify
                for i, attr_name in enumerate(seen_attrs[:3]):
                    options.append({'name': attr_name})
                payload['options'] = options

            shopify_variants = []
            for v in variants:
                attr_vals = v.product_template_attribute_value_ids
                variant_data = {
                    'sku':   v.default_code or '',
                    'price': str(v.lst_price),
                    'inventory_management': 'shopify',
                    'inventory_policy':     'deny',
                }
                if v.barcode:  # Solo enviar barcode si Odoo tiene uno
                    variant_data['barcode'] = v.barcode
                    
                if v.shopify_variant_id:
                    variant_data['id'] = v.shopify_variant_id

                # CANDADO PRO: Si es actualización y la variante ya existe, 
                # omitimos asignar los valores de opción (option1, etc.) 
                # para evitar sobrescribir metafields.
                if is_create or not v.shopify_variant_id:
                    # Asignar option1, option2, option3 según posición
                    for i, val in enumerate(attr_vals[:3]):
                        variant_data[f'option{i+1}'] = val.name

                shopify_variants.append(variant_data)
            payload['variants'] = shopify_variants

        return payload

    # ──────────────────────────────────────────────────────────
    # HELPER: Subir imagen
    # ──────────────────────────────────────────────────────────
    def _push_image_to_shopify(self, api_base, shopify_product_id, headers):
        """
        Sube la imagen principal del producto a Shopify.
        Evita duplicados consultando si ya existen imágenes asociadas.
        """
        try:
            img_b64 = self.image_1920
            if not img_b64:
                return

            # Evitar duplicados: consultar imágenes existentes en Shopify
            check_url = f"{api_base}/products/{shopify_product_id}/images.json"
            check_resp = requests.get(check_url, headers=headers, timeout=15)
            if check_resp.status_code == 200:
                existing_images = check_resp.json().get('images', [])
                if existing_images:
                    _logger.info("[Shopify Push] El producto '%s' ya tiene imágenes en Shopify. Se omite subida duplicada.", self.name)
                    return

            # Detectar si ya es bytes o string
            if isinstance(img_b64, bytes):
                img_str = img_b64.decode('utf-8')
            else:
                img_str = img_b64

            url = f"{api_base}/products/{shopify_product_id}/images.json"
            payload = {
                'image': {
                    'attachment': img_str,
                    'filename':   f"{self.name.replace(' ', '_')}.jpg",
                    'alt':        self.name,
                }
            }
            resp = requests.post(url, headers=headers, json=payload, timeout=30)
            if resp.status_code not in (200, 201):
                _logger.warning("[Shopify Push] Imagen no subida para '%s': HTTP %s — %s",
                                self.name, resp.status_code, resp.text[:200])
            else:
                _logger.info("[Shopify Push] Imagen subida para '%s'", self.name)
        except Exception as e:
            _logger.warning("[Shopify Push] Error subiendo imagen para '%s': %s", self.name, str(e))

    # ──────────────────────────────────────────────────────────
    # HELPER: Guardar IDs de variantes devueltos por Shopify
    # ──────────────────────────────────────────────────────────
    def _sync_variant_ids_from_shopify(self, product_data):
        """
        Después de crear/actualizar el producto en Shopify,
        guarda los IDs de variante e inventory_item_id en cada product.product de Odoo.
        El mapeo se hace por SKU (default_code) y como respaldo por Opciones.
        """
        shopify_variants = product_data.get('variants', [])
        sku_map = {}
        option_map = {}
        for sv in shopify_variants:
            sku = (sv.get('sku') or '').strip()
            opt1 = (sv.get('option1') or '').strip().upper()
            opt2 = (sv.get('option2') or '').strip().upper()
            opt3 = (sv.get('option3') or '').strip().upper()
            opts_tuple = (opt1, opt2, opt3)
            
            data = {
                'variant_id':        str(sv.get('id', '')),
                'inventory_item_id': str(sv.get('inventory_item_id', '')),
            }
            if sku:
                sku_map[sku] = data
            option_map[opts_tuple] = data

        for variant in self.product_variant_ids:
            sku = (variant.default_code or '').strip()
            matched_data = None
            if sku and sku in sku_map:
                matched_data = sku_map[sku]
            else:
                # Fallback to matching by options
                attr_vals = variant.product_template_attribute_value_ids
                opt1 = attr_vals[0].name.strip().upper() if len(attr_vals) > 0 else ''
                opt2 = attr_vals[1].name.strip().upper() if len(attr_vals) > 1 else ''
                opt3 = attr_vals[2].name.strip().upper() if len(attr_vals) > 2 else ''
                opts_tuple = (opt1, opt2, opt3)
                if opts_tuple in option_map:
                    matched_data = option_map[opts_tuple]
            
            if matched_data:
                variant.write({
                    'shopify_variant_id':        matched_data['variant_id'],
                    'shopify_inventory_item_id': matched_data['inventory_item_id'],
                })

    # ──────────────────────────────────────────────────────────
    # AUTO-SYNC: cuando se edita un producto ya publicado
    # ──────────────────────────────────────────────────────────
    SHOPIFY_SYNC_FIELDS = {
        'name', 'description_sale', 'description',
        'list_price', 'categ_id',
        # NOTA: image_1920 excluida intencionalmente para evitar push automático
        # al descargar imágenes desde Shopify (causaría sobreescribir barcodes vacíos).
        # Las imágenes se suben a Shopify solo cuando el usuario hace push manual.
    }

    def write(self, vals):
        res = super().write(vals)
        # Solo sincronizar si el producto ya está publicado en Shopify
        # y si cambió algún campo relevante
        changed = set(vals.keys()) & self.SHOPIFY_SYNC_FIELDS
        if changed:
            for record in self:
                if record.shopify_published and record.shopify_id:
                    try:
                        config = record._get_shopify_config()
                        if config and config.auto_sync_products:
                            record.action_push_to_shopify()
                    except Exception as e:
                        # No bloqueamos el guardado — solo logueamos
                        _logger.error(
                            "[Shopify Auto-sync] Error actualizando '%s' en Shopify: %s",
                            record.name, str(e)
                        )
        return res

    # ──────────────────────────────────────────────────────────
    # ACCIÓN: Actualizar canales desde Shopify (solo lectura)
    # ──────────────────────────────────────────────────────────
    def action_refresh_shopify_channels(self):
        """
        Botón manual: lee los canales de venta de Shopify y actualiza
        la tabla local sin hacer ningún cambio en Shopify.
        Útil para cargar el estado actual de canales de productos ya publicados.
        """
        self.ensure_one()
        if not self.shopify_id:
            raise UserError("Este producto no está publicado en Shopify.")

        config = self._get_shopify_config()
        token = config._ensure_access_token()

        shop_domain = (config.shop_url or '').strip().lower()
        if '://' in shop_domain:
            shop_domain = shop_domain.split('://')[1].split('/')[0]

        headers = {
            'X-Shopify-Access-Token': token,
            'Content-Type': 'application/json',
        }
        api_base = f"https://{shop_domain}/admin/api/2024-01"

        self._read_shopify_channels(api_base, headers, self.shopify_id)

        return {
            'type': 'ir.actions.client',
            'tag': 'display_notification',
            'params': {
                'title': '✅ Canales actualizados',
                'message': f"Se actualizaron los canales de venta para '{self.name}'.",
                'type': 'success',
                'sticky': False,
            }
        }

    # ──────────────────────────────────────────────────────────
    # HELPER: Leer canales de Shopify (solo lectura, sin mutations)
    # ──────────────────────────────────────────────────────────
    def _read_shopify_channels(self, api_base, headers, shopify_product_id):
        """
        Lee los canales de venta de Shopify y el estado de publicación
        del producto. Crea/actualiza los registros locales SIN hacer
        ningún cambio en Shopify (solo lectura).
        """
        self.ensure_one()
        graphql_url = f"{api_base}/graphql.json"
        config = self._get_shopify_config()

        # ── Paso 1: Obtener TODOS los canales de la tienda ──────────
        query_publications = """
        query {
            publications(first: 30) {
                edges {
                    node {
                        id
                        name
                    }
                }
            }
        }
        """
        try:
            resp = requests.post(graphql_url, headers=headers, json={'query': query_publications}, timeout=15)
            if resp.status_code != 200:
                _logger.warning("[Shopify Channels Read] Error consultando publicaciones: HTTP %s", resp.status_code)
                return

            data = resp.json() or {}
            if 'errors' in data:
                _logger.warning("[Shopify Channels Read] GraphQL error: %s", data.get('errors'))
                return

            edges = (data.get('data') or {}).get('publications', {}).get('edges', [])
            if not edges:
                _logger.warning("[Shopify Channels Read] No se encontraron canales.")
                return

            # ── Paso 2: Crear/actualizar registros shopify.publication ──
            ShopifyPub = self.env['shopify.publication'].sudo()
            all_pub_records = {}
            valid_pub_gids = []

            for edge in edges:
                node = edge.get('node', {})
                pub_gid = node.get('id')
                pub_name = (node.get('name') or '').strip()
                if not pub_gid:
                    continue

                valid_pub_gids.append(pub_gid)

                pub_rec = ShopifyPub.search([
                    ('shopify_publication_id', '=', pub_gid),
                    ('config_id', '=', config.id),
                ], limit=1)
                if not pub_rec:
                    pub_rec = ShopifyPub.create({
                        'name': pub_name,
                        'shopify_publication_id': pub_gid,
                        'config_id': config.id,
                    })
                elif pub_rec.name != pub_name:
                    pub_rec.write({'name': pub_name})

                all_pub_records[pub_gid] = pub_rec

            # Eliminar canales locales que ya no existen en Shopify
            obsolete_pubs = ShopifyPub.search([
                ('config_id', '=', config.id),
                ('shopify_publication_id', 'not in', valid_pub_gids)
            ])
            if obsolete_pubs:
                obsolete_pubs.unlink()

            # ── Paso 3: Leer estado actual de publicación del producto ──
            graphql_product_id = f"gid://shopify/Product/{shopify_product_id}"
            query_product_pubs = """
            query getProductPublications($productId: ID!) {
                product(id: $productId) {
                    resourcePublicationsV2(first: 30) {
                        edges {
                            node {
                                isPublished
                                publication {
                                    id
                                    name
                                }
                            }
                        }
                    }
                }
            }
            """
            resp2 = requests.post(
                graphql_url, headers=headers,
                json={'query': query_product_pubs, 'variables': {'productId': graphql_product_id}},
                timeout=15
            )

            currently_published_gids = set()
            if resp2.status_code == 200:
                data2 = resp2.json() or {}
                product_data = (data2.get('data') or {}).get('product') or {}
                pub_edges = (product_data.get('resourcePublicationsV2') or {}).get('edges', [])
                for pe in pub_edges:
                    pn = pe.get('node', {})
                    if pn.get('isPublished'):
                        pub_info = pn.get('publication') or {}
                        pub_gid_val = pub_info.get('id')
                        if pub_gid_val:
                            currently_published_gids.add(pub_gid_val)

            # ── Paso 4: Crear/actualizar registros (SOLO LECTURA) ──
            ProdPub = self.env['shopify.product.publication'].sudo()

            for pub_gid, pub_rec in all_pub_records.items():
                is_published = pub_gid in currently_published_gids

                existing = ProdPub.search([
                    ('product_tmpl_id', '=', self.id),
                    ('publication_id', '=', pub_rec.id),
                ], limit=1)

                if existing:
                    # Actualizar al estado real de Shopify
                    if existing.is_published != is_published:
                        existing.write({'is_published': is_published})
                else:
                    ProdPub.create({
                        'product_tmpl_id': self.id,
                        'publication_id': pub_rec.id,
                        'is_published': is_published,
                    })

            _logger.info("[Shopify Channels Read] %s canales leídos para '%s'", len(all_pub_records), self.name)

        except Exception as e:
            _logger.warning("[Shopify Channels Read] Error: %s", str(e))

    # ──────────────────────────────────────────────────────────
    # HELPER: Sincronizar canales de venta vía GraphQL
    # ──────────────────────────────────────────────────────────
    def _sync_shopify_channels(self, api_base, headers, shopify_product_id):
        """
        Sincroniza la publicación del producto en TODOS los canales de Shopify.
        1. Obtiene la lista completa de publicaciones (canales) de la tienda.
        2. Crea/actualiza registros shopify.publication para cada canal.
        3. Lee el estado actual de publicación del producto en cada canal.
        4. Crea/actualiza registros shopify.product.publication.
        5. Aplica cambios locales (publicar/despublicar) según los checkboxes de Odoo.
        """
        self.ensure_one()
        graphql_url = f"{api_base}/graphql.json"
        config = self._get_shopify_config()

        # ── Paso 1: Obtener TODOS los canales de la tienda ──────────
        query_publications = """
        query {
            publications(first: 30) {
                edges {
                    node {
                        id
                        name
                    }
                }
            }
        }
        """
        try:
            resp = requests.post(graphql_url, headers=headers, json={'query': query_publications}, timeout=15)
            if resp.status_code != 200:
                _logger.warning("[Shopify Channels] Error al consultar publicaciones: HTTP %s", resp.status_code)
                return

            data = resp.json() or {}
            if 'errors' in data:
                _logger.warning(
                    "[Shopify Channels] La consulta GraphQL devolvió un error (¿falta de permisos?): %s. "
                    "Asegúrate de activar los permisos de 'Publications' en tu Custom App en Shopify.",
                    data.get('errors')
                )
                return

            data_content = data.get('data') or {}
            publications_content = data_content.get('publications') or {}
            edges = publications_content.get('edges', [])

            if not edges:
                _logger.warning("[Shopify Channels] No se encontraron canales de venta en Shopify.")
                return

            # ── Paso 2: Asegurar que existan registros shopify.publication ──
            ShopifyPub = self.env['shopify.publication'].sudo()
            all_pub_records = {}  # {gid: shopify.publication record}
            valid_pub_gids = []

            for edge in edges:
                node = edge.get('node', {})
                pub_gid = node.get('id')
                pub_name = (node.get('name') or '').strip()
                if not pub_gid:
                    continue

                valid_pub_gids.append(pub_gid)

                pub_rec = ShopifyPub.search([
                    ('shopify_publication_id', '=', pub_gid),
                    ('config_id', '=', config.id),
                ], limit=1)
                if not pub_rec:
                    pub_rec = ShopifyPub.create({
                        'name': pub_name,
                        'shopify_publication_id': pub_gid,
                        'config_id': config.id,
                    })
                elif pub_rec.name != pub_name:
                    pub_rec.write({'name': pub_name})

                all_pub_records[pub_gid] = pub_rec

            # Eliminar canales locales que ya no existen en Shopify
            obsolete_pubs = ShopifyPub.search([
                ('config_id', '=', config.id),
                ('shopify_publication_id', 'not in', valid_pub_gids)
            ])
            if obsolete_pubs:
                obsolete_pubs.unlink()

            _logger.info("[Shopify Channels] %s canales de venta sincronizados.", len(all_pub_records))

            # ── Paso 3: Leer estado actual de publicación del producto ──
            graphql_product_id = f"gid://shopify/Product/{shopify_product_id}"

            query_product_pubs = """
            query getProductPublications($productId: ID!) {
                product(id: $productId) {
                    resourcePublicationsV2(first: 30) {
                        edges {
                            node {
                                isPublished
                                publication {
                                    id
                                    name
                                }
                            }
                        }
                    }
                }
            }
            """
            resp2 = requests.post(
                graphql_url, headers=headers,
                json={'query': query_product_pubs, 'variables': {'productId': graphql_product_id}},
                timeout=15
            )

            # Set de GIDs donde el producto está actualmente publicado en Shopify
            currently_published_gids = set()
            if resp2.status_code == 200:
                data2 = resp2.json() or {}
                product_data = (data2.get('data') or {}).get('product') or {}
                pub_edges = (product_data.get('resourcePublicationsV2') or {}).get('edges', [])
                for pe in pub_edges:
                    pn = pe.get('node', {})
                    if pn.get('isPublished'):
                        pub_info = pn.get('publication') or {}
                        pub_gid_val = pub_info.get('id')
                        if pub_gid_val:
                            currently_published_gids.add(pub_gid_val)
            else:
                _logger.warning("[Shopify Channels] No se pudo consultar el estado de publicación del producto: HTTP %s", resp2.status_code)

            # ── Paso 4: Crear/actualizar registros shopify.product.publication ──
            ProdPub = self.env['shopify.product.publication'].sudo()

            for pub_gid, pub_rec in all_pub_records.items():
                is_currently_published = pub_gid in currently_published_gids

                existing = ProdPub.search([
                    ('product_tmpl_id', '=', self.id),
                    ('publication_id', '=', pub_rec.id),
                ], limit=1)

                if existing:
                    # Leer el valor local que el usuario podría haber cambiado
                    local_desired = existing.is_published

                    if local_desired != is_currently_published:
                        # El usuario cambió algo en Odoo → aplicar cambio en Shopify
                        self._set_shopify_channel_publication(
                            graphql_url, headers, graphql_product_id, pub_gid, local_desired
                        )
                    # Si no hay discrepancia, no hacer nada (el estado ya coincide)
                else:
                    # Primera vez: crear registro con el estado real de Shopify
                    ProdPub.create({
                        'product_tmpl_id': self.id,
                        'publication_id': pub_rec.id,
                        'is_published': is_currently_published,
                    })

        except Exception as e:
            _logger.warning("[Shopify Channels] Error inesperado en la sincronización de canales: %s", str(e))

    def _set_shopify_channel_publication(self, graphql_url, headers, graphql_product_id, pub_gid, should_publish):
        """
        Publica o despublica un producto en un canal específico de Shopify vía GraphQL.
        """
        mutation_name = "publishablePublish" if should_publish else "publishableUnpublish"
        mutation_query = f"""
        mutation {mutation_name}($id: ID!, $input: [PublicationInput!]!) {{
            {mutation_name}(id: $id, input: $input) {{
                userErrors {{
                    field
                    message
                }}
            }}
        }}
        """
        variables = {
            "id": graphql_product_id,
            "input": [{"publicationId": pub_gid}]
        }
        try:
            m_resp = requests.post(graphql_url, headers=headers, json={'query': mutation_query, 'variables': variables}, timeout=15)
            if m_resp.status_code == 200:
                errors = m_resp.json().get('data', {}).get(mutation_name, {}).get('userErrors', [])
                if errors:
                    _logger.warning("[Shopify Channels] Error en mutation %s para %s: %s", mutation_name, pub_gid, errors)
                else:
                    action_verb = "publicado" if should_publish else "despublicado"
                    _logger.info("[Shopify Channels] Producto %s exitosamente en %s", action_verb, pub_gid)
            else:
                _logger.warning("[Shopify Channels] HTTP %s al ejecutar mutation %s para %s", m_resp.status_code, mutation_name, pub_gid)
        except Exception as e:
            _logger.warning("[Shopify Channels] Error al ejecutar mutation %s: %s", mutation_name, str(e))


    # ──────────────────────────────────────────────────────────
    # ACCIÓN: Despublicar de Shopify
    # ──────────────────────────────────────────────────────────
    def action_unpublish_from_shopify(self):
        """
        Cambia el status del producto a 'draft' en Shopify
        (no lo elimina, solo lo oculta de la tienda).
        """
        self.ensure_one()
        if not self.shopify_id:
            raise UserError("Este producto no está publicado en Shopify.")

        config = self._get_shopify_config()
        token = config._ensure_access_token()
        shop_domain = (config.shop_url or '').strip().lower()
        if '://' in shop_domain:
            shop_domain = shop_domain.split('://')[1].split('/')[0]

        headers = {
            'X-Shopify-Access-Token': token,
            'Content-Type': 'application/json',
        }
        url = f"https://{shop_domain}/admin/api/2024-01/products/{self.shopify_id}.json"
        resp = requests.put(url, headers=headers,
                            json={'product': {'id': self.shopify_id, 'status': 'draft'}},
                            timeout=15)

        if resp.status_code == 200:
            self.write({
                'shopify_published': False, 
                'shopify_sync_status': 'pending',
                'shopify_status': 'draft',
            })
            return {
                'type': 'ir.actions.client',
                'tag': 'display_notification',
                'params': {
                    'title': 'Producto despublicado',
                    'message': f"'{self.name}' fue movido a borrador en Shopify.",
                    'type': 'warning',
                    'sticky': False,
                }
            }
        else:
            raise UserError(f"Error al despublicar en Shopify: HTTP {resp.status_code}")

    def action_generate_barcode(self):
        """ Botón manual para generar código de barras EAN-13 desde la ficha del Template """
        for template in self:
            # Propagar a todas las variantes que no tengan barcode
            for variant in template.product_variant_ids:
                if not variant.barcode:
                    new_barcode = variant._generate_ean13_for_record()
                    if new_barcode:
                        variant.write({'barcode': new_barcode})
                        _logger.info("Barcode generado manualmente (vía Template) para %s: %s", variant.name, new_barcode)




class ProductProduct(models.Model):
    _inherit = 'product.product'

    shopify_variant_id = fields.Char(
        string='ID Variante Shopify', copy=False, index=True, readonly=True,
    )
    shopify_inventory_item_id = fields.Char(
        string='ID Inventario Shopify', copy=False, index=True, readonly=True,
    )

    @api.model
    def _compute_ean13_checksum(self, barcode_str):
        """ Calcula el dígito verificador para EAN-13 usando el Módulo 10 """
        if len(barcode_str) != 12 or not barcode_str.isdigit():
            return "0"
        
        sum_odd = sum(int(barcode_str[i]) for i in range(0, 12, 2))
        sum_even = sum(int(barcode_str[i]) * 3 for i in range(1, 12, 2))
        total_sum = sum_odd + sum_even
        
        remainder = total_sum % 10
        checksum = 10 - remainder if remainder != 0 else 0
        return str(checksum)

    def _generate_ean13_for_record(self):
        """ Obtiene el siguiente EAN-13 de la secuencia oficial """
        # Pedimos el siguiente número a la secuencia de Odoo (ej: 750000000001)
        sequence_val = self.env['ir.sequence'].next_by_code('product.barcode.hazard')
        if not sequence_val:
            _logger.warning("No se pudo generar código de barras: Falta la secuencia 'product.barcode.hazard'")
            return False

        # Si el valor devuelto tiene menos de 12 dígitos por algún error de configuración, lo forzamos
        if len(sequence_val) < 12:
            sequence_val = sequence_val.zfill(12)
        elif len(sequence_val) > 12:
            sequence_val = sequence_val[:12]

        checksum = self._compute_ean13_checksum(sequence_val)
        return sequence_val + checksum

    @api.model_create_multi
    def create(self, vals_list):
        """ Intercepta la creación de productos nuevos para asignar Barcode automático si viene vacío """
        for vals in vals_list:
            if not vals.get('barcode'):
                # Solo asignamos código si no trae uno manual y si estamos generando
                new_barcode = self._generate_ean13_for_record()
                if new_barcode:
                    vals['barcode'] = new_barcode
        return super(ProductProduct, self).create(vals_list)

    def action_generate_barcode(self):
        """ Botón manual para generar código de barras desde la ficha del producto """
        for record in self:
            if not record.barcode:
                new_barcode = record._generate_ean13_for_record()
                if new_barcode:
                    record.write({'barcode': new_barcode})
                    _logger.info("Barcode generado manualmente para %s: %s", record.name, new_barcode)
