import hashlib
import html
import io
import logging
import re
from datetime import datetime, timezone
from pathlib import Path

from docx import Document
from docx.shared import Inches, Pt, RGBColor
from reportlab.lib.pagesizes import LETTER
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer
from reportlab.lib.utils import ImageReader
from sqlalchemy import text

from app.core.config import get_settings
from app.core.security import decrypt_text, format_rut
from app.core.timezone import SANTIAGO
from app.services.pdf_branding import draw_pdf_footer
from app.services.document_formatting import format_document_value
from app.services.rich_text import append_docx_html, reportlab_story, sanitize_template_html
from app.services.file_security import read_private_file
from app.services.service_configuration import effective_entity_id
from app.services.template_variables import VARIABLE_REFERENCE_PATTERN, extract_template_variables, field_code_candidates


ILLEGAL_XML = re.compile(r"[\x00-\x08\x0B\x0C\x0E-\x1F\uFFFE\uFFFF]")
MONTHS_ES = ("enero", "febrero", "marzo", "abril", "mayo", "junio",
             "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre")


def _plain(value):
    return "" if value is None else str(value)


def render_template_content(content: str, variables: dict[str, object],
                            field_values: dict[str, object] | None = None,
                            field_types: dict[str, str] | None = None) -> str:
    """Render template variables with the same rules used by final documents."""
    values = field_values or {}
    types = field_types or {}

    def replace_variable(match):
        variable = match.group(1) or match.group(2)
        if variable in variables:
            return html.escape(format_document_value(variable, variables[variable]))
        try:
            candidates = field_code_candidates(variable)
        except ValueError:
            return match.group(0)
        for code in candidates:
            if code in values:
                return html.escape(format_document_value(
                    variable,
                    values[code],
                    field_code=code,
                    field_type=types.get(code),
                ))
        return match.group(0)

    return VARIABLE_REFERENCE_PATTERN.sub(
        replace_variable,
        sanitize_template_html(content),
    )


def render_sale_document(db, sale_id: int):
    sale = db.execute(text("""
      SELECT v.id,v.numero_publico,v.creada_en,v.notaria_id,c.nombres_cifrado,c.apellidos_cifrado,c.rut_cifrado,
             c.email_cifrado,c.telefono_cifrado,m.id materia_id,m.nombre materia,n.nombre_fantasia notaria,
             n.direccion notaria_direccion,COALESCE(NULLIF(n.ciudad,''),co.nombre) notaria_ciudad,
             co.nombre notaria_comuna,r.nombre notaria_region,
             n.logo_objeto_storage,n.logo_mime_type,n.logo_sha256,
             pm.id plantilla_id,pm.contenido
      FROM ventas v JOIN clientes c ON c.id=v.cliente_id JOIN materias m ON m.id=v.materia_id
      JOIN notarias n ON n.id=v.notaria_id
      JOIN comunas co ON co.id=n.comuna_id JOIN regiones r ON r.id=co.region_id
      LEFT JOIN configuraciones_servicio_entidad cse
        ON cse.materia_id=m.id AND cse.notaria_id=n.id
      JOIN plantillas_materias pm ON pm.materia_id=m.id AND pm.activa=TRUE
        AND pm.notaria_id <=> IF(COALESCE(cse.campos_plantilla_personalizados,FALSE),n.id,NULL)
      WHERE v.id=:id ORDER BY pm.version DESC LIMIT 1
    """), {"id": sale_id}).mappings().first()
    if not sale:
        raise ValueError("El documento todavía no tiene una plantilla activa")
    created_at = sale["creada_en"]
    if created_at and created_at.tzinfo is None:
        created_at = created_at.replace(tzinfo=timezone.utc)
    created_at = created_at.astimezone(SANTIAGO) if created_at else datetime.now(SANTIAGO)
    today = datetime.now(SANTIAGO)
    long_date = f"{created_at.day} de {MONTHS_ES[created_at.month - 1]} de {created_at.year}"
    variables = {
        "folio": sale["numero_publico"], "materia.nombre": sale["materia"],
        "notaria.nombre": sale["notaria"],
        "notaria.direccion": sale["notaria_direccion"],
        "notaria.comuna": sale["notaria_comuna"],
        "notaria.ciudad": sale["notaria_ciudad"],
        "notaria.region": sale["notaria_region"],
        "entidad.nombre": sale["notaria"],
        "entidad.direccion": sale["notaria_direccion"],
        "entidad.comuna": sale["notaria_comuna"],
        "entidad.ciudad": sale["notaria_ciudad"],
        "entidad.region": sale["notaria_region"],
        "cliente.nombres": decrypt_text(sale["nombres_cifrado"]),
        "cliente.apellidos": decrypt_text(sale["apellidos_cifrado"]),
        "cliente.rut": format_rut(decrypt_text(sale["rut_cifrado"])),
        "cliente.email": decrypt_text(sale["email_cifrado"]),
        "cliente.telefono": decrypt_text(sale["telefono_cifrado"]) if sale["telefono_cifrado"] else "",
        "documento.fecha": created_at.strftime("%d-%m-%Y"),
        "documento.fecha.larga": long_date,
        "fecha.hoy": today.strftime("%d-%m-%Y"),
        "ot.fecha": created_at.strftime("%d-%m-%Y"),
        "ot.fecha.larga": long_date,
    }
    variables["cliente.nombre_completo"] = f"{variables['cliente.nombres']} {variables['cliente.apellidos']}".strip()
    variables.update({
        "campo.cliente_nombre_completo": variables["cliente.nombre_completo"],
        "campo.cliente_email": variables["cliente.email"],
        "campo.cliente_rut": variables["cliente.rut"],
        "campo.cliente_telefono": variables["cliente.telefono"],
        "compareciente.1.nombre": variables["cliente.nombre_completo"],
        "compareciente.1.nombreInformal": variables["cliente.nombre_completo"],
        "compareciente.1.nombres": variables["cliente.nombres"],
        "compareciente.1.apellidos": variables["cliente.apellidos"],
        "compareciente.1.rut": variables["cliente.rut"],
        "compareciente.1.email": variables["cliente.email"],
        "compareciente.1.telefono": variables["cliente.telefono"],
    })
    fields = db.execute(text("""
      SELECT cm.codigo,td.codigo tipo_dato,rc.valor_cifrado FROM respuestas_campos rc
      JOIN campos_materias cm ON cm.id=rc.campo_materia_id
      JOIN tipo_de_dato td ON td.id=cm.tipo_de_dato_id
      WHERE rc.venta_id=:id AND rc.compareciente_id IS NULL
    """), {"id": sale_id}).mappings().all()
    field_values = {}
    field_types = {}
    for field in fields:
        value = decrypt_text(field["valor_cifrado"]) if field["valor_cifrado"] else ""
        if field["tipo_dato"] == "COMUNA" and value.isdigit():
            commune_name = db.execute(text("""
              SELECT nombre FROM comunas WHERE codigo_gesnot=:codigo AND activo=TRUE LIMIT 1
            """), {"codigo": int(value)}).scalar()
            value = commune_name or value
        field_values[field["codigo"]] = value
        field_types[field["codigo"]] = field["tipo_dato"]
        variables[f"campo.{field['codigo']}"] = value

    field_entity=effective_entity_id(db,sale["materia_id"],sale["notaria_id"],"fields")
    active_codes=db.execute(text("""
      SELECT codigo FROM campos_materias WHERE materia_id=:matter AND notaria_id <=> :entity AND activo=TRUE
    """),{"matter":sale["materia_id"],"entity":field_entity}).scalars().all()
    for code in active_codes:
        field_values.setdefault(code,"")
    unresolved=[]
    for variable in extract_template_variables(sale["contenido"]):
        if variable in variables: continue
        try: candidates=field_code_candidates(variable)
        except ValueError: candidates=[]
        if not any(candidate in field_values for candidate in candidates): unresolved.append(variable)
    if unresolved:
        raise ValueError("La plantilla contiene variables sin respuesta: "+", ".join(unresolved))
    rendered = render_template_content(sale["contenido"], variables, field_values, field_types)
    logo = None
    if sale["logo_objeto_storage"]:
        try:
            candidate = read_private_file(sale["logo_objeto_storage"])
            if sale["logo_sha256"] and hashlib.sha256(candidate).digest() != sale["logo_sha256"]:
                raise ValueError("La huella del logo no coincide")
            logo = candidate
        except Exception:
            logging.getLogger(__name__).exception("entity_document_logo_unavailable sale_id=%s", sale_id)
    branding = {"logo": logo, "mime_type": sale["logo_mime_type"] if logo else None}
    return rendered, sale["plantilla_id"], sale["numero_publico"], branding


def document_verification_code(folio: str, file_format: str) -> str:
    return f"DOC-{folio.strip().upper()}-{file_format.strip().upper()}"


def build_docx(content: str, verification_code: str | None = None,
               branding: dict | None = None) -> bytes:
    content = ILLEGAL_XML.sub("", content)
    document = Document()
    logo = (branding or {}).get("logo")
    if logo:
        try:
            header = document.sections[0].header
            header_paragraph = header.paragraphs[0]
            header_paragraph.alignment = 0
            header_paragraph.add_run().add_picture(io.BytesIO(logo), width=Inches(1.15))
        except Exception:
            logging.getLogger(__name__).exception("entity_logo_docx_render_failed")
    append_docx_html(document, content)
    footer = document.sections[0].footer
    paragraph = footer.paragraphs[0]
    paragraph.alignment = 1
    mark = paragraph.add_run("D  ")
    mark.bold = True
    mark.font.size = Pt(9)
    mark.font.color.rgb = RGBColor(230, 0, 0)
    footer_text = "Documenta.  ·  https://documenta.cl"
    if verification_code:
        footer_text += f"  ·  Verificación: {verification_code}"
    name = paragraph.add_run(footer_text)
    name.bold = True
    name.font.size = Pt(8)
    name.font.color.rgb = RGBColor(104, 112, 133)
    output = io.BytesIO()
    document.save(output)
    return output.getvalue()


def _draw_pdf_entity_logo(pdf, logo: bytes | None) -> None:
    if not logo:
        return
    try:
        reader = ImageReader(io.BytesIO(logo))
        source_width, source_height = reader.getSize()
        max_width, max_height = 34 * mm, 15 * mm
        scale = min(max_width / source_width, max_height / source_height)
        width, height = source_width * scale, source_height * scale
        pdf.drawImage(reader, 22 * mm, LETTER[1] - 18 * mm - height,
                      width=width, height=height, preserveAspectRatio=True, mask="auto")
    except Exception:
        logging.getLogger(__name__).exception("entity_logo_pdf_render_failed")


def build_pdf(content: str, verification_code: str | None = None,
              branding: dict | None = None) -> bytes:
    content = ILLEGAL_XML.sub("", content)
    output = io.BytesIO()
    logo = (branding or {}).get("logo")
    document = SimpleDocTemplate(output, pagesize=LETTER, rightMargin=22*mm, leftMargin=22*mm,
                                 topMargin=(36 if logo else 20)*mm, bottomMargin=25*mm,
                                 title="Documento Documenta")
    styles = getSampleStyleSheet()
    story = reportlab_story(content)
    def branded_footer(pdf, doc):
        _draw_pdf_entity_logo(pdf, logo)
        draw_pdf_footer(pdf, doc, verification_code=verification_code)
    document.build(story, onFirstPage=branded_footer, onLaterPages=branded_footer)
    return output.getvalue()


def persist_generated(db, sale_id: int, template_id: int, folio: str, file_format: str, content: bytes):
    extension = "pdf" if file_format == "PDF" else "docx"
    relative = f"generated/{sale_id}/{folio}.{extension}"
    path = Path(get_settings().storage_path) / relative
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_bytes(content)
    path.chmod(0o600)
    db.execute(text("""
      INSERT INTO documentos_generados (venta_id,plantilla_materia_id,formato,objeto_storage,sha256)
      VALUES (:venta,:plantilla,:formato,:objeto,:sha)
      ON DUPLICATE KEY UPDATE plantilla_materia_id=VALUES(plantilla_materia_id),
        objeto_storage=VALUES(objeto_storage),sha256=VALUES(sha256),creado_en=UTC_TIMESTAMP(6)
    """), {"venta":sale_id,"plantilla":template_id,"formato":file_format,
            "objeto":relative,"sha":hashlib.sha256(content).digest()})
    return path
