"""
app/utils/pdf_generator.py – Generación de PDFs con ReportLab (Platypus).

Funciones principales:
  - generar_pdf_cambio_individual(cambio, usuario_actual) -> BytesIO
  - generar_pdf_listado_cambios(cambios, filtros, usuario_actual) -> BytesIO

Diseño: SimpleDocTemplate + Paragraph + Table + Image + Spacer.
No depende de librerías del sistema operativo.
"""
import os
import logging
from io import BytesIO
from datetime import datetime

from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer,
    Table, TableStyle, Image, PageBreak, HRFlowable
)

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------
# Constantes de estilo corporativo
# ---------------------------------------------------------------
COLOR_PRIMARIO    = colors.HexColor('#1565C0')   # Azul corporativo
COLOR_SECUNDARIO  = colors.HexColor('#E3F2FD')   # Azul claro
COLOR_SOLICITADO  = colors.HexColor('#FF9800')   # Naranja
COLOR_PROGRAMADO  = colors.HexColor('#2196F3')   # Azul medio
COLOR_LIBERADO    = colors.HexColor('#4CAF50')   # Verde
COLOR_GRIS_CLARO  = colors.HexColor('#F5F5F5')   # Fondo fila alterna
COLOR_CABECERA    = colors.HexColor('#1565C0')   # Cabecera tabla
COLOR_TEXTO_BLANCO = colors.white
COLOR_BORDE       = colors.HexColor('#BDBDBD')

LOGO_PATH = os.path.join(
    os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
    'static', 'img', 'logo.png'
)

# ---------------------------------------------------------------
# Estilos de párrafo
# ---------------------------------------------------------------
_estilos = getSampleStyleSheet()

ESTILO_TITULO = ParagraphStyle(
    'Titulo',
    parent=_estilos['Title'],
    fontName='Helvetica-Bold',
    fontSize=16,
    leading=20,
    alignment=TA_CENTER,
    textColor=COLOR_PRIMARIO,
    spaceAfter=12,
)
ESTILO_SUBTITULO = ParagraphStyle(
    'Subtitulo',
    parent=_estilos['Heading2'],
    fontName='Helvetica-Bold',
    fontSize=12,
    leading=15,
    textColor=COLOR_PRIMARIO,
    spaceBefore=10,
    spaceAfter=4,
)
ESTILO_NORMAL = ParagraphStyle(
    'Normal_CC',
    parent=_estilos['Normal'],
    fontName='Helvetica',
    fontSize=9,
    leading=12,
)
ESTILO_ETIQUETA = ParagraphStyle(
    'Etiqueta',
    parent=_estilos['Normal'],
    fontName='Helvetica-Bold',
    fontSize=9,
    leading=12,
)
ESTILO_CABECERA_TABLA = ParagraphStyle(
    'CabeceraTbl',
    parent=_estilos['Normal'],
    fontName='Helvetica-Bold',
    fontSize=8,
    textColor=COLOR_TEXTO_BLANCO,
    alignment=TA_CENTER,
)
ESTILO_FILTROS = ParagraphStyle(
    'Filtros',
    parent=_estilos['Normal'],
    fontName='Helvetica-Oblique',
    fontSize=8,
    textColor=colors.HexColor('#555555'),
)


# ---------------------------------------------------------------
# Helpers internos
# ---------------------------------------------------------------
def _fmt_fecha(value) -> str:
    """Formatea un objeto date o datetime a 'dd/mm/yyyy'."""
    if value is None:
        return '-'
    try:
        return value.strftime('%d/%m/%Y')
    except Exception:
        return str(value)


def _logo_o_texto() -> object:
    """Retorna un Image del logo o un Paragraph de texto alternativo."""
    try:
        if os.path.exists(LOGO_PATH):
            return Image(LOGO_PATH, width=2.5 * cm, height=2.5 * cm)
    except Exception as e:
        logger.warning('No se pudo cargar el logo: %s', e)
    return Paragraph('<b>IBSO</b>', ESTILO_ETIQUETA)


def _color_estatus(estatus: str) -> colors.Color:
    """Devuelve el color correspondiente al estatus del cambio."""
    mapa = {
        'Solicitado': COLOR_SOLICITADO,
        'Programado': COLOR_PROGRAMADO,
        'Liberado':   COLOR_LIBERADO,
    }
    return mapa.get(estatus, colors.grey)


def _encabezado_pagina(canvas, doc, titulo: str, usuario_nombre: str):
    """
    Dibuja header y footer en cada página.
    Se usa como callback onFirstPage / onLaterPages.
    """
    canvas.saveState()
    ancho, alto = doc.pagesize

    # --- Header ---
    # Línea azul superior
    canvas.setFillColor(COLOR_PRIMARIO)
    canvas.rect(0, alto - 2 * cm, ancho, 2 * cm, fill=1, stroke=0)

    # Logo
    try:
        if os.path.exists(LOGO_PATH):
            canvas.drawImage(LOGO_PATH, 0.8 * cm, alto - 1.8 * cm,
                             width=1.5 * cm, height=1.5 * cm,
                             preserveAspectRatio=True, mask='auto')
    except Exception:
        canvas.setFillColor(colors.white)
        canvas.setFont('Helvetica-Bold', 10)
        canvas.drawString(0.8 * cm, alto - 1.2 * cm, 'IBSO')

    # Título del documento
    canvas.setFillColor(colors.white)
    canvas.setFont('Helvetica-Bold', 11)
    canvas.drawCentredString(ancho / 2, alto - 1.2 * cm, titulo)

    # Fecha de generación (derecha)
    canvas.setFont('Helvetica', 8)
    canvas.drawRightString(ancho - 0.8 * cm, alto - 1.2 * cm,
                           datetime.now().strftime('%d/%m/%Y %H:%M'))

    # --- Footer ---
    canvas.setFillColor(COLOR_PRIMARIO)
    canvas.rect(0, 0, ancho, 1 * cm, fill=1, stroke=0)

    canvas.setFillColor(colors.white)
    canvas.setFont('Helvetica', 7)
    # Número de página
    canvas.drawString(0.8 * cm, 0.35 * cm,
                      f'Generado por: {usuario_nombre}')
    canvas.drawCentredString(ancho / 2, 0.35 * cm,
                             datetime.now().strftime('Impreso el %d/%m/%Y a las %H:%M'))
    canvas.drawRightString(ancho - 0.8 * cm, 0.35 * cm,
                           f'Página {canvas.getPageNumber()}')

    canvas.restoreState()


def _tabla_detalle(filas: list, col_anchos=None) -> Table:
    """
    Crea una tabla de 2 columnas (etiqueta / valor) con estilo corporativo.
    filas: lista de tuplas (etiqueta, valor).
    """
    datos = []
    for etiqueta, valor in filas:
        datos.append([
            Paragraph(str(etiqueta), ESTILO_ETIQUETA),
            Paragraph(str(valor) if valor is not None else '-', ESTILO_NORMAL),
        ])

    if col_anchos is None:
        col_anchos = [5 * cm, 11.5 * cm]

    tbl = Table(datos, colWidths=col_anchos)
    estilo = TableStyle([
        ('BACKGROUND', (0, 0), (0, -1), COLOR_SECUNDARIO),
        ('GRID',       (0, 0), (-1, -1), 0.5, COLOR_BORDE),
        ('VALIGN',     (0, 0), (-1, -1), 'TOP'),
        ('PADDING',    (0, 0), (-1, -1), 5),
        ('ROWBACKGROUNDS', (1, 0), (1, -1), [colors.white, COLOR_GRIS_CLARO]),
    ])
    tbl.setStyle(estilo)
    return tbl


# ---------------------------------------------------------------
# Función pública: PDF individual de un cambio
# ---------------------------------------------------------------
def generar_pdf_cambio_individual(cambio, usuario_actual) -> BytesIO:
    """
    Genera el PDF completo de un registro de cambio.
    Retorna un BytesIO listo para enviar con send_file().
    """
    buffer = BytesIO()
    titulo = f'Control de Cambios – Ficha #{cambio.id}'
    nombre_usuario = getattr(usuario_actual, 'nombre_completo', 'Sistema')

    doc = SimpleDocTemplate(
        buffer,
        pagesize=A4,
        topMargin=2.5 * cm,
        bottomMargin=1.5 * cm,
        leftMargin=1.5 * cm,
        rightMargin=1.5 * cm,
        title=titulo,
        author=nombre_usuario,
    )

    elementos = []

    # Título principal
    elementos.append(Spacer(1, 0.3 * cm))
    elementos.append(Paragraph(f'Ficha de Control de Cambio #{cambio.id}', ESTILO_TITULO))
    elementos.append(HRFlowable(width='100%', thickness=1, color=COLOR_PRIMARIO))
    elementos.append(Spacer(1, 0.3 * cm))

    # ---- Sección 1: Datos de la Solicitud ----
    elementos.append(Paragraph('1. Datos de la Solicitud', ESTILO_SUBTITULO))
    filas_solicitud = [
        ('Sistema:', cambio.sistema.nombre if cambio.sistema else '-'),
        ('Solicitante:', f'{cambio.solicitante.nombre_completo} ({cambio.solicitante.correo})'
                        if cambio.solicitante else '-'),
        ('Núm. Incidencia:', str(cambio.numero_incidencia)),
        ('Descripción Breve:', cambio.descripcion_breve or '-'),
        ('Fecha de Pruebas:', _fmt_fecha(cambio.fecha_pruebas)),
        ('Fecha de Registro:', _fmt_fecha(cambio.creado_en)),
    ]
    elementos.append(_tabla_detalle(filas_solicitud))
    elementos.append(Spacer(1, 0.4 * cm))

    # ---- Sección 2: Datos Técnicos (si aplica) ----
    if cambio.descripcion_tecnica or cambio.fecha_propuesta_produccion or cambio.desarrollador:
        elementos.append(Paragraph('2. Datos Técnicos', ESTILO_SUBTITULO))
        filas_tecnica = [
            ('Descripción Técnica:', cambio.descripcion_tecnica or '-'),
            ('Fecha Propuesta Producción:', _fmt_fecha(cambio.fecha_propuesta_produccion)),
            ('Desarrollador Asignado:',
             cambio.desarrollador.nombre_completo if cambio.desarrollador else '-'),
        ]
        elementos.append(_tabla_detalle(filas_tecnica))
        elementos.append(Spacer(1, 0.4 * cm))

    # ---- Sección 3: Datos de Liberación (si aplica) ----
    if cambio.estatus == 'Liberado':
        elementos.append(Paragraph('3. Datos de Liberación', ESTILO_SUBTITULO))
        filas_liberacion = [
            ('Fecha Real de Aplicación:', _fmt_fecha(cambio.fecha_real_aplicacion)),
            ('Usuario que Validó:',
             cambio.usuario_valido.nombre_completo if cambio.usuario_valido else '-'),
            ('Estado del Cambio:', cambio.estado_cambio or '-'),
            ('Observaciones:', cambio.observaciones or '-'),
            ('Administrador que Liberó:',
             cambio.admin_libero.nombre_completo if cambio.admin_libero else '-'),
        ]
        elementos.append(_tabla_detalle(filas_liberacion))
        elementos.append(Spacer(1, 0.5 * cm))

    # ---- Recuadro de estatus ----
    color_estatus = _color_estatus(cambio.estatus)
    datos_estatus = [[
        Paragraph(f'ESTATUS ACTUAL: {cambio.estatus.upper()}',
                  ParagraphStyle('Estatus', fontName='Helvetica-Bold',
                                 fontSize=13, textColor=colors.white,
                                 alignment=TA_CENTER))
    ]]
    tbl_estatus = Table(datos_estatus, colWidths=[16.5 * cm])
    tbl_estatus.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, -1), color_estatus),
        ('PADDING',    (0, 0), (-1, -1), 12),
        ('ROUNDEDCORNERS', [5]),
    ]))
    elementos.append(tbl_estatus)

    # Construir con callbacks de header/footer
    def _on_page(canvas, doc):
        _encabezado_pagina(canvas, doc, titulo, nombre_usuario)

    doc.build(elementos, onFirstPage=_on_page, onLaterPages=_on_page)
    buffer.seek(0)
    return buffer


# ---------------------------------------------------------------
# Función pública: PDF listado de cambios con filtros
# ---------------------------------------------------------------
def generar_pdf_listado_cambios(cambios: list, filtros: dict, usuario_actual) -> BytesIO:
    """
    Genera el PDF de listado de cambios con filtros aplicados.
    Orientación horizontal (landscape A4).
    Retorna un BytesIO listo para send_file().
    """
    buffer = BytesIO()
    titulo = 'Reporte de Cambios'
    nombre_usuario = getattr(usuario_actual, 'nombre_completo', 'Sistema')
    tam_pagina = landscape(A4)

    doc = SimpleDocTemplate(
        buffer,
        pagesize=tam_pagina,
        topMargin=2.5 * cm,
        bottomMargin=1.5 * cm,
        leftMargin=1.5 * cm,
        rightMargin=1.5 * cm,
        title=titulo,
        author=nombre_usuario,
    )

    elementos = []
    elementos.append(Spacer(1, 0.3 * cm))

    # ---- Resumen de filtros ----
    partes_filtro = []
    if filtros.get('fecha_desde'):
        partes_filtro.append(f"Desde: {_fmt_fecha(filtros['fecha_desde'])}")
    if filtros.get('fecha_hasta'):
        partes_filtro.append(f"Hasta: {_fmt_fecha(filtros['fecha_hasta'])}")
    if filtros.get('sistema'):
        partes_filtro.append(f"Sistema: {filtros['sistema']}")
    if filtros.get('estatus'):
        partes_filtro.append(f"Estatus: {filtros['estatus']}")
    if filtros.get('solicitante'):
        partes_filtro.append(f"Solicitante: {filtros['solicitante']}")

    texto_filtros = '  |  '.join(partes_filtro) if partes_filtro else 'Sin filtros aplicados'
    elementos.append(Paragraph(f'Filtros: {texto_filtros}', ESTILO_FILTROS))
    elementos.append(Spacer(1, 0.3 * cm))
    elementos.append(HRFlowable(width='100%', thickness=1, color=COLOR_PRIMARIO))
    elementos.append(Spacer(1, 0.3 * cm))

    # ---- Tabla de datos ----
    # Cabecera
    cabeceras = [
        Paragraph('ID',           ESTILO_CABECERA_TABLA),
        Paragraph('Sistema',      ESTILO_CABECERA_TABLA),
        Paragraph('Solicitante',  ESTILO_CABECERA_TABLA),
        Paragraph('Incidencia',   ESTILO_CABECERA_TABLA),
        Paragraph('Descripción',  ESTILO_CABECERA_TABLA),
        Paragraph('F. Pruebas',   ESTILO_CABECERA_TABLA),
        Paragraph('Estatus',      ESTILO_CABECERA_TABLA),
        Paragraph('F. Registro',  ESTILO_CABECERA_TABLA),
    ]

    # Anchos de columna (landscape A4 ≈ 27.7 cm útiles)
    col_anchos = [1.2*cm, 4*cm, 4*cm, 1.8*cm, 7*cm, 2.2*cm, 2.5*cm, 2.5*cm]

    filas = [cabeceras]
    MAX_DESC = 90  # caracteres máximos para descripción en tabla

    for c in cambios:
        desc = (c.descripcion_breve or '')
        if len(desc) > MAX_DESC:
            desc = desc[:MAX_DESC] + '…'

        filas.append([
            Paragraph(str(c.id), ESTILO_NORMAL),
            Paragraph(c.sistema.nombre if c.sistema else '-', ESTILO_NORMAL),
            Paragraph(c.solicitante.nombre_completo if c.solicitante else '-', ESTILO_NORMAL),
            Paragraph(str(c.numero_incidencia), ESTILO_NORMAL),
            Paragraph(desc, ESTILO_NORMAL),
            Paragraph(_fmt_fecha(c.fecha_pruebas), ESTILO_NORMAL),
            Paragraph(c.estatus, ESTILO_NORMAL),
            Paragraph(_fmt_fecha(c.creado_en), ESTILO_NORMAL),
        ])

    tbl = Table(filas, colWidths=col_anchos, repeatRows=1)
    tbl.setStyle(TableStyle([
        # Cabecera
        ('BACKGROUND',  (0, 0), (-1, 0), COLOR_CABECERA),
        ('TEXTCOLOR',   (0, 0), (-1, 0), COLOR_TEXTO_BLANCO),
        ('FONTNAME',    (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE',    (0, 0), (-1, 0), 8),
        # Filas de datos
        ('FONTNAME',    (0, 1), (-1, -1), 'Helvetica'),
        ('FONTSIZE',    (0, 1), (-1, -1), 8),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, COLOR_GRIS_CLARO]),
        # Bordes
        ('GRID',        (0, 0), (-1, -1), 0.5, COLOR_BORDE),
        ('VALIGN',      (0, 0), (-1, -1), 'TOP'),
        ('PADDING',     (0, 0), (-1, -1), 4),
    ]))
    elementos.append(tbl)

    # ---- Total de registros ----
    elementos.append(Spacer(1, 0.4 * cm))
    elementos.append(Paragraph(
        f'<b>Total de registros: {len(cambios)}</b>',
        ESTILO_NORMAL
    ))

    def _on_page(canvas, doc):
        _encabezado_pagina(canvas, doc, titulo, nombre_usuario)

    doc.build(elementos, onFirstPage=_on_page, onLaterPages=_on_page)
    buffer.seek(0)
    return buffer
