"""
Simple PDF Convert API
POST /api/pdf/convert

Behavior:
- If a PDF file is uploaded: save it AS-IS under ./public/pdfs/<same_name>.pdf (overwrites if exists).
- If a TXT file is uploaded: read its text, create PDF with SAME base name, save to ./public/pdfs/.
- If only 'text' is provided (JSON or form): create PDF named text-<uuid8>.pdf under ./public/pdfs/.
- Response includes FULL absolute URL to access the saved file on the server.
"""

from __future__ import annotations
import io
import os

from datetime import datetime

from flask import Blueprint, request, jsonify, current_app
from werkzeug.utils import secure_filename

pdf_convert_bp = Blueprint("pdf_convert_bp", __name__, url_prefix="/api/pdf")

ALLOWED_EXTENSIONS = {".pdf", ".txt"}
MAX_UPLOAD_MB = 25
PUBLIC_SUBDIR = "pdfs"  # -> ./public/pdfs/<filename>.pdf


# ----------------------------
# Helpers
# ----------------------------
def _bytes_to_mb(n: int) -> float:
    return round((n or 0) / (1024 * 1024), 2)

def _allowed_file(filename: str) -> bool:
    return os.path.splitext(filename.lower())[1] in ALLOWED_EXTENSIONS

def _public_root() -> str:
    root = os.path.join(current_app.root_path, "public")
    os.makedirs(os.path.join(root, PUBLIC_SUBDIR), exist_ok=True)
    return root

def _public_dir() -> str:
    d = os.path.join(_public_root(), PUBLIC_SUBDIR)
    os.makedirs(d, exist_ok=True)
    return d

def _full_url_for(filename: str) -> str:
    base = request.url_root.rstrip("/")  # e.g. http://localhost:5000
    return f"{base}/public/{PUBLIC_SUBDIR}/{filename}"

def _read_text_from_txt(fs) -> str:
    raw = fs.read()
    if not raw:
        fs.stream.seek(0)
        raw = fs.stream.read()
    try:
        return raw.decode("utf-8")
    except UnicodeDecodeError:
        return raw.decode("latin-1", errors="ignore")


# ----------------------------
# WRAPPED PDF WRITERS (no text loss)
# ----------------------------
def _write_pdf_reportlab_verbatim(text: str) -> bytes:
    from reportlab.lib.pagesizes import A4
    from reportlab.pdfgen import canvas
    from reportlab.lib.units import cm
    from reportlab.pdfbase import pdfmetrics

    font_path = current_app.config.get("VERBATIM_FONT_PATH")
    if not font_path:
        candidate = os.path.join(current_app.root_path, "assets", "fonts", "DejaVuSansMono.ttf")
        if os.path.exists(candidate):
            font_path = candidate

    buf = io.BytesIO()
    c = canvas.Canvas(buf, pagesize=A4)
    width, height = A4

    left_margin = 1.8 * cm
    right_margin = 1.8 * cm
    top_margin = 2.0 * cm
    bottom_margin = 2.0 * cm

    font_name = "Helvetica"
    font_size = 11
    try:
        if font_path and os.path.exists(font_path):
            from reportlab.pdfbase.ttfonts import TTFont
            pdfmetrics.registerFont(TTFont("DejaVuSansMono", font_path))
            font_name = "DejaVuSansMono"
    except Exception as e:
        current_app.logger.warning("TTF registration failed, using Helvetica: %s", e)

    usable_width = width - left_margin - right_margin
    leading = font_size * 1.35

    def wrap_line(line: str) -> list[str]:
        if line is None:
            return [""]
        if line == "":
            return [""]

        line = line.replace("\t", "    ")
        words = line.split(" ")
        out_lines, cur = [], ""

        def sw(s: str) -> float:
            return pdfmetrics.stringWidth(s, font_name, font_size)

        for w in words:
            cur_try = w if cur == "" else (cur + " " + w)
            if sw(cur_try) <= usable_width:
                cur = cur_try
            else:
                if cur:
                    out_lines.append(cur)
                    cur = w
                else:
                    token = w
                    while token:
                        lo, hi, best = 1, len(token), 1
                        while lo <= hi:
                            mid = (lo + hi) // 2
                            if sw(token[:mid]) <= usable_width:
                                best = mid
                                lo = mid + 1
                            else:
                                hi = mid - 1
                        out_lines.append(token[:best])
                        token = token[best:]
        if cur != "":
            out_lines.append(cur)
        return out_lines

    text_obj = c.beginText()
    text_obj.setTextOrigin(left_margin, height - top_margin)
    text_obj.setFont(font_name, font_size)
    y_limit = bottom_margin

    for logical_line in text.split("\n"):
        for physical in wrap_line(logical_line):
            if text_obj.getY() - leading < y_limit:
                c.drawText(text_obj)
                c.showPage()
                text_obj = c.beginText()
                text_obj.setTextOrigin(left_margin, height - top_margin)
                text_obj.setFont(font_name, font_size)
            text_obj.textLine(physical)

    c.drawText(text_obj)
    c.setFont("Helvetica-Oblique", 8)
    c.drawRightString(width - right_margin, bottom_margin - 0.6 * cm,
                      f"Generated: {datetime.now():%Y-%m-%d %H:%M}")
    c.save()
    buf.seek(0)
    return buf.read()


def _write_pdf_fpdf_verbatim(text: str) -> bytes:
    from fpdf import FPDF
    pdf = FPDF()
    pdf.set_auto_page_break(auto=True, margin=15)
    pdf.add_page()
    pdf.set_font("Courier", size=11)
    for line in text.split("\n"):
        pdf.multi_cell(0, 6, txt=(line or "").replace("\t", "    "))
    out = io.BytesIO()
    pdf.output(out)
    return out.getvalue()


def _write_pdf_minimal_verbatim(text: str) -> bytes:
    from textwrap import wrap as twrap
    def esc(s: str) -> str:
        return s.replace("\\", "\\\\").replace("(", "\\(").replace(")", "\\)")
    max_cols = 90
    lines = []
    for ln in text.split("\n"):
        wrapped = twrap((ln or "").replace("\t", "    "), max_cols) or [""]
        lines.extend(wrapped)

    y = 800
    chunks = []
    chunks.append("%PDF-1.4\n")
    font_obj = "1 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Courier >>\nendobj\n"
    chunks.append(font_obj)
    content_lines = [f"BT\n/F1 11 Tf\n1 0 0 1 50 {y} Tm\n"]
    for line in lines:
        content_lines.append(f"({esc(line)}) Tj\n0 -16 Td\n")
    content_lines.append("ET\n")
    content_stream = "".join(content_lines).encode("latin-1", "ignore")
    content_obj = f"2 0 obj\n<< /Length {len(content_stream)} >>\nstream\n".encode("latin-1") + content_stream + b"endstream\nendobj\n"
    chunks.append(content_obj.decode("latin-1", "ignore"))
    page_obj = "3 0 obj\n<< /Type /Page /Parent 4 0 R /Resources << /Font << /F1 1 0 R >> >> /MediaBox [0 0 595 842] /Contents 2 0 R >>\nendobj\n"
    chunks.append(page_obj)
    pages_obj = "4 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n"
    chunks.append(pages_obj)
    catalog_obj = "5 0 obj\n<< /Type /Catalog /Pages 4 0 R >>\nendobj\n"
    chunks.append(catalog_obj)
    offsets, out = [], b""
    pos = 0
    for ck in chunks:
        bck = ck.encode("latin-1", "ignore")
        offsets.append(pos); out += bck; pos += len(bck)
    xref_pos = len(out)
    xref = "xref\n0 6\n0000000000 65535 f \n" + "".join(f"{off:010} 00000 n \n" for off in offsets)
    out += xref.encode("latin-1")
    trailer = f"trailer\n<< /Size 6 /Root 5 0 R >>\nstartxref\n{xref_pos}\n%%EOF"
    out += trailer.encode("latin-1")
    return out


def _text_to_pdf_bytes_verbatim(text: str) -> bytes:
    try:
        import reportlab
        return _write_pdf_reportlab_verbatim(text)
    except Exception:
        try:
            import fpdf
            return _write_pdf_fpdf_verbatim(text)
        except Exception:
            return _write_pdf_minimal_verbatim(text)