"""generate_assessment_pdf -- Combine upgrade assessment reports into a single PDF.

Reads all markdown files from a customer's upgrade assessment (executive
summary, per-package reports, upgrade sequence, pre-upgrade checklist)
and renders them into a single professionally styled PDF.

Uses PyMuPDF (fitz) Story API -- already a project dependency for
extract_doc_text. No additional packages required.
"""

import logging
import re
from datetime import datetime
from pathlib import Path
from typing import Any

from ..utils import state

logger = logging.getLogger(__name__)

TOOL_DEFINITION = {
    "name": "generate_assessment_pdf",
    "description": (
        "Combine all upgrade assessment markdown reports into a single "
        "professionally styled PDF. Includes cover page, executive summary, "
        "per-package reports, upgrade sequence, and pre-upgrade checklist. "
        "Call after upgrade assessment reports have been generated via "
        "save_report."
    ),
    "inputSchema": {
        "type": "object",
        "properties": {
            "working_directory": {
                "type": "string",
                "description": (
                    "Absolute path to the workspace root directory."
                ),
            },
            "account_name": {
                "type": "string",
                "description": (
                    "Customer account name (resolves the customer folder)."
                ),
            },
        },
        "required": ["working_directory", "account_name"],
    },
}

# ── Page geometry (A4 in points) ──────────────────────────────────────

PAGE_W = 595.28
PAGE_H = 841.89
M_TOP = 72
M_BOT = 72
M_LEFT = 60
M_RIGHT = 60

# ── Unicode sanitisation ─────────────────────────────────────────────

_UNICODE_MAP = {
    "\u2192": "->",     # →
    "\u2190": "<-",     # ←
    "\u2194": "<->",    # ↔
    "\u2014": "--",     # —
    "\u2013": "-",      # –
    "\u2018": "'",      # '
    "\u2019": "'",      # '
    "\u201c": '"',      # "
    "\u201d": '"',      # "
    "\u2026": "...",    # …
    "\u26a0": "[!]",    # ⚠
    "\u2713": "[OK]",   # ✓
    "\u2717": "[X]",    # ✗
    "\u2022": "-",      # •
    "\ufe0f": "",       # variation selector
    "\u2757": "[!]",    # ❗
}

_UNICODE_RE = re.compile(
    "[" + re.escape("".join(_UNICODE_MAP.keys())) + "]"
)


def _sanitise(text: str) -> str:
    text = _UNICODE_RE.sub(lambda m: _UNICODE_MAP.get(m.group(), ""), text)
    text = re.sub(r"[\U00010000-\U0010ffff]", "", text)
    return text


# ── Link cleanup ─────────────────────────────────────────────────────

def _clean_links(text: str) -> str:
    text = re.sub(
        r"\[([^\]]+)\]\([^)]*\.md\)", r"**\1**", text
    )
    text = re.sub(
        r"\[([^\]]+)\]\(release-docs/[^)]+\)",
        r"*\1 (available in release-docs folder)*",
        text,
    )
    text = re.sub(
        r"\[`([^`]+)`\]\((/packaging/[^)]+)\)",
        r"`\1` (\2)",
        text,
    )
    return text


# ── Markdown → HTML ──────────────────────────────────────────────────

def _inline(text: str) -> str:
    text = re.sub(r"`([^`]+)`", r"<code>\1</code>", text)
    text = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", text)
    text = re.sub(r"(?<!\*)\*([^*]+)\*(?!\*)", r"<em>\1</em>", text)
    text = re.sub(
        r"\[([^\]]+)\]\((https?://[^)]+)\)",
        r'<a href="\2">\1</a>',
        text,
    )
    text = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text)
    return text


def _md_to_html(md_text: str) -> str:
    lines = md_text.split("\n")
    out: list[str] = []
    in_code = False
    in_table = False
    in_ul = False
    in_ol = False
    in_bq = False
    table_row = 0

    def _close_lists():
        nonlocal in_table, in_ul, in_ol, in_bq, table_row
        if in_table:
            out.append("</tbody></table>")
            in_table = False
            table_row = 0
        if in_ul:
            out.append("</ul>")
            in_ul = False
        if in_ol:
            out.append("</ol>")
            in_ol = False
        if in_bq:
            out.append("</blockquote>")
            in_bq = False

    for line in lines:
        s = line.strip()

        # fenced code blocks
        if s.startswith("```"):
            if in_code:
                out.append("</code></pre>")
                in_code = False
            else:
                _close_lists()
                in_code = True
                out.append("<pre><code>")
            continue
        if in_code:
            out.append(
                line.replace("&", "&amp;")
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                + "\n"
            )
            continue

        # close containers that the current line doesn't continue
        if in_table and not s.startswith("|"):
            out.append("</tbody></table>")
            in_table = False
            table_row = 0
        if in_ul and not s.startswith(("- ", "* ", "- [")) and s:
            out.append("</ul>")
            in_ul = False
        if in_ol and not re.match(r"^\d+\.", s) and s:
            out.append("</ol>")
            in_ol = False

        # blockquotes
        if s.startswith(">"):
            if not in_bq:
                _close_lists()
                out.append("<blockquote>")
                in_bq = True
            content = s.lstrip(">").strip()
            if content:
                out.append(f"<p>{_inline(content)}</p>")
            continue
        elif in_bq:
            out.append("</blockquote>")
            in_bq = False

        if not s:
            continue

        # horizontal rule
        if s in ("---", "***", "___"):
            _close_lists()
            out.append("<hr/>")
            continue

        # headers
        hm = re.match(r"^(#{1,4})\s+(.*)", s)
        if hm:
            _close_lists()
            lvl = len(hm.group(1))
            out.append(f"<h{lvl}>{_inline(hm.group(2))}</h{lvl}>")
            continue

        # tables
        if s.startswith("|"):
            cells = [c.strip() for c in s.split("|")[1:-1]]
            if not cells:
                continue
            if all(re.match(r"^[-:]+$", c) for c in cells):
                continue
            if not in_table:
                in_table = True
                table_row = 0
                out.append('<table><thead><tr>')
                for cell in cells:
                    out.append(f"<th>{_inline(cell)}</th>")
                out.append("</tr></thead><tbody>")
            else:
                table_row += 1
                bg = ' style="background-color:#f6f8fb"' if table_row % 2 == 0 else ""
                out.append(f"<tr{bg}>")
                for cell in cells:
                    out.append(f"<td>{_inline(cell)}</td>")
                out.append("</tr>")
            continue

        # checkbox list items  - [ ] and - [x]
        cb = re.match(r"^- \[([ xX])\]\s+(.*)", s)
        if cb:
            if not in_ul:
                out.append("<ul>")
                in_ul = True
            marker = "[OK]" if cb.group(1).lower() == "x" else "[ ]"
            out.append(f"<li>{marker} {_inline(cb.group(2))}</li>")
            continue

        # unordered list
        if s.startswith("- ") or s.startswith("* "):
            if not in_ul:
                out.append("<ul>")
                in_ul = True
            out.append(f"<li>{_inline(s[2:].strip())}</li>")
            continue

        # ordered list
        olm = re.match(r"^(\d+)\.\s+(.*)", s)
        if olm:
            if not in_ol:
                out.append("<ol>")
                in_ol = True
            out.append(f"<li>{_inline(olm.group(2))}</li>")
            continue

        # paragraph
        out.append(f"<p>{_inline(s)}</p>")

    if in_code:
        out.append("</code></pre>")
    _close_lists()
    return "\n".join(out)


# ── File discovery ───────────────────────────────────────────────────

def _make_slug(name: str) -> str:
    slug = name.lower().strip()
    slug = re.sub(r"[^a-z0-9]+", "-", slug)
    return slug.strip("-") or "unknown"


def _discover_files(customer_dir: Path) -> dict:
    result: dict[str, Any] = {
        "executive_summary": None,
        "package_reports": [],
        "upgrade_sequence": None,
        "pre_upgrade_checklist": None,
    }
    exec_files = sorted(
        customer_dir.glob("upgrade-assessment-*.md"), reverse=True
    )
    if exec_files:
        result["executive_summary"] = exec_files[0]

    analysis_dir = customer_dir / "upgrade-analysis"
    if not analysis_dir.is_dir():
        return result

    for md_file in sorted(analysis_dir.glob("*.md")):
        name = md_file.name
        if name == "upgrade-sequence.md":
            result["upgrade_sequence"] = md_file
        elif name == "pre-upgrade-checklist.md":
            result["pre_upgrade_checklist"] = md_file
        else:
            num = re.match(r"^(\d+)-", name)
            key = int(num.group(1)) if num else 999
            result["package_reports"].append((key, md_file))

    result["package_reports"].sort(key=lambda x: x[0])
    return result


# ── Metadata from executive summary ─────────────────────────────────

def _extract_meta(text: str) -> dict:
    meta: dict[str, str] = {
        "customer": "",
        "target_release": "",
        "current_release": "",
        "risk_level": "",
        "date": datetime.now().strftime("%d %B %Y"),
    }
    first_line = text.split("\n")[0] if text else ""
    tm = re.match(r"^#\s+(.+?)\s*[—–\-]+\s*(.*)", first_line)
    if tm:
        meta["customer"] = tm.group(1).strip()

    for line in text.split("\n")[:25]:
        low = line.lower()
        if "**target release:**" in low or "**target:**" in low:
            meta["target_release"] = re.sub(
                r"\*\*.*?\*\*\s*", "", line
            ).strip()
        elif "**current release:**" in low or "**current:**" in low:
            meta["current_release"] = re.sub(
                r"\*\*.*?\*\*\s*", "", line
            ).strip()
        elif "**date:**" in low:
            meta["date"] = re.sub(r"\*\*.*?\*\*\s*", "", line).strip()
        elif "overall risk:" in low:
            rm = re.search(r"(LOW|MEDIUM|HIGH|CRITICAL)", line, re.I)
            if rm:
                meta["risk_level"] = rm.group(1).upper()
    return meta


# ── CSS stylesheet ───────────────────────────────────────────────────

_CSS = """
body {
    font-family: Helvetica, Arial, sans-serif;
    font-size: 10pt;
    color: #2c3e50;
    line-height: 1.5;
}
h1 {
    font-size: 18pt;
    color: #1a5276;
    border-bottom: 2px solid #1a5276;
    padding-bottom: 6pt;
    margin-top: 24pt;
    margin-bottom: 12pt;
}
h2 {
    font-size: 14pt;
    color: #1a5276;
    border-left: 4px solid #2980b9;
    padding-left: 8pt;
    margin-top: 18pt;
    margin-bottom: 8pt;
}
h3 {
    font-size: 12pt;
    color: #34495e;
    border-left: 3px solid #bdc3c7;
    padding-left: 6pt;
    margin-top: 14pt;
    margin-bottom: 6pt;
}
h4 {
    font-size: 11pt;
    color: #2c3e50;
    font-weight: bold;
    margin-top: 10pt;
    margin-bottom: 4pt;
}
p {
    margin-top: 3pt;
    margin-bottom: 3pt;
}
table {
    border-collapse: collapse;
    width: 100%;
    margin-top: 8pt;
    margin-bottom: 8pt;
    font-size: 8.5pt;
}
th {
    background-color: #eaf2f8;
    color: #1a5276;
    font-weight: bold;
    padding: 5pt 4pt;
    border: 0.5pt solid #d5dbdb;
    text-align: left;
}
td {
    padding: 4pt;
    border: 0.5pt solid #d5dbdb;
}
code {
    font-family: Courier, monospace;
    font-size: 8pt;
    background-color: #f4f6f7;
    padding: 1pt 2pt;
}
pre {
    background-color: #f4f6f7;
    padding: 8pt;
    margin: 6pt 0;
    border: 0.5pt solid #d5dbdb;
    font-size: 7.5pt;
}
pre code {
    background-color: transparent;
    padding: 0;
}
blockquote {
    border-left: 4px solid #f39c12;
    background-color: #fef9e7;
    padding: 6pt 10pt;
    margin: 8pt 0;
}
ul, ol {
    margin-top: 4pt;
    margin-bottom: 4pt;
    padding-left: 18pt;
}
li {
    margin-bottom: 2pt;
}
hr {
    border: none;
    border-top: 0.5pt solid #d5dbdb;
    margin: 10pt 0;
}
a {
    color: #2980b9;
}
strong { font-weight: bold; }
em { font-style: italic; }

.section-break {
    page-break-before: always;
}
.cover {
    text-align: center;
    page-break-after: always;
}
.cover-brand {
    font-size: 10pt;
    color: #95a5a6;
    margin-top: 160pt;
}
.cover-title {
    font-size: 26pt;
    color: #1a5276;
    font-weight: bold;
    margin-top: 30pt;
    margin-bottom: 16pt;
}
.cover-customer {
    font-size: 20pt;
    color: #2c3e50;
    margin-bottom: 30pt;
}
.cover-detail {
    font-size: 12pt;
    color: #7f8c8d;
    margin-bottom: 6pt;
}
.cover-risk-high {
    font-size: 13pt;
    color: #c0392b;
    font-weight: bold;
    margin-top: 10pt;
}
.cover-risk-medium {
    font-size: 13pt;
    color: #e67e22;
    font-weight: bold;
    margin-top: 10pt;
}
.cover-risk-low {
    font-size: 13pt;
    color: #27ae60;
    font-weight: bold;
    margin-top: 10pt;
}
.cover-date {
    font-size: 11pt;
    color: #95a5a6;
    margin-top: 40pt;
}
.cover-footer {
    font-size: 9pt;
    color: #bdc3c7;
    margin-top: 60pt;
}
"""

# ── Cover page builder ───────────────────────────────────────────────


def _build_cover(meta: dict) -> str:
    release_line = ""
    if meta.get("current_release") and meta.get("target_release"):
        release_line = (
            f'<p class="cover-detail">'
            f'{_sanitise(meta["current_release"])} -&gt; '
            f'{_sanitise(meta["target_release"])}</p>'
        )
    elif meta.get("target_release"):
        release_line = (
            f'<p class="cover-detail">'
            f'Target: {_sanitise(meta["target_release"])}</p>'
        )

    risk = meta.get("risk_level", "")
    risk_class = {
        "CRITICAL": "cover-risk-high",
        "HIGH": "cover-risk-high",
        "MEDIUM": "cover-risk-medium",
        "LOW": "cover-risk-low",
    }.get(risk, "cover-detail")
    risk_line = ""
    if risk:
        risk_line = f'<p class="{risk_class}">Overall Risk: {risk}</p>'

    customer = meta.get("customer") or "Customer"

    return (
        f'<div class="cover">'
        f'<p class="cover-brand">CLOUDSENSE</p>'
        f'<hr/>'
        f'<p class="cover-title">Upgrade Assessment</p>'
        f'<p class="cover-customer">{customer}</p>'
        f'{release_line}'
        f'{risk_line}'
        f'<p class="cover-date">{meta.get("date", "")}</p>'
        f'<p class="cover-footer">'
        f'Generated by CloudSense Customer Compass</p>'
        f'</div>'
    )


# ── PDF renderer ─────────────────────────────────────────────────────


def _render_pdf(html: str, css: str, out_path: Path) -> tuple[int, int]:
    """Render HTML+CSS to a PDF file. Returns (page_count, file_size)."""
    import fitz  # noqa: delayed import so module loads even w/o fitz

    mediabox = fitz.Rect(0, 0, PAGE_W, PAGE_H)
    content_rect = fitz.Rect(M_LEFT, M_TOP, PAGE_W - M_RIGHT, PAGE_H - M_BOT)

    story = fitz.Story(html, user_css=css)

    def rectfn(rect_num, filled):
        return mediabox, content_rect, None

    doc = story.write_with_links(rectfn)

    # Post-process: add page numbers (skip cover page)
    total = len(doc)
    for i in range(1, total):
        page = doc[i]
        footer = f"Page {i} of {total - 1}"
        tw = fitz.get_text_length(footer, fontname="helv", fontsize=8)
        x = (PAGE_W - tw) / 2
        y = PAGE_H - M_BOT / 2
        page.insert_text(
            fitz.Point(x, y),
            footer,
            fontname="helv",
            fontsize=8,
            color=(0.6, 0.6, 0.6),
        )

    doc.save(str(out_path), garbage=4, deflate=True)
    size = out_path.stat().st_size
    doc.close()
    return total, size


# ── Main entry point ─────────────────────────────────────────────────


async def run(arguments: dict) -> str:
    account_name = (arguments.get("account_name") or "").strip()
    if not account_name:
        return (
            "STATUS: MISSING_CUSTOMER\n\n"
            "Provide account_name to identify the customer."
        )

    state.set_working_dir(arguments.get("working_directory"))
    working_dir = state.get_working_dir()
    slug = _make_slug(account_name)
    customer_dir = working_dir / "customers" / slug

    if not customer_dir.is_dir():
        return (
            f"STATUS: NO_ASSESSMENT_DATA\n\n"
            f"Customer folder not found: {customer_dir}\n"
            f"Run an upgrade assessment first."
        )

    files = _discover_files(customer_dir)

    if not files["executive_summary"] and not files["package_reports"]:
        return (
            f"STATUS: NO_ASSESSMENT_DATA\n\n"
            f"No assessment markdown files found in {customer_dir}.\n"
            f"Expected:\n"
            f"  - upgrade-assessment-*.md (executive summary)\n"
            f"  - upgrade-analysis/*.md (per-package reports)\n\n"
            f"Run an upgrade assessment first."
        )

    # ── Read and convert all sections ──

    sections: list[str] = []
    meta: dict[str, str] = {
        "customer": account_name,
        "date": datetime.now().strftime("%d %B %Y"),
    }

    if files["executive_summary"]:
        raw = files["executive_summary"].read_text(encoding="utf-8")
        raw = _sanitise(_clean_links(raw))
        meta = _extract_meta(raw)
        if not meta["customer"]:
            meta["customer"] = account_name
        sections.append(_md_to_html(raw))

    for _, pkg_file in files["package_reports"]:
        raw = pkg_file.read_text(encoding="utf-8")
        raw = _sanitise(_clean_links(raw))
        sections.append(_md_to_html(raw))

    if files["upgrade_sequence"]:
        raw = files["upgrade_sequence"].read_text(encoding="utf-8")
        raw = _sanitise(_clean_links(raw))
        sections.append(_md_to_html(raw))

    if files["pre_upgrade_checklist"]:
        raw = files["pre_upgrade_checklist"].read_text(encoding="utf-8")
        raw = _sanitise(_clean_links(raw))
        sections.append(_md_to_html(raw))

    # ── Assemble full HTML ──

    cover = _build_cover(meta)
    body_parts = [cover]
    for html_section in sections:
        body_parts.append(f'<div class="section-break">{html_section}</div>')

    full_html = "\n".join(body_parts)

    # ── Render PDF ──

    safe_name = re.sub(r"[^a-zA-Z0-9]+", "-", account_name).strip("-")
    date_str = datetime.now().strftime("%Y-%m-%d")
    out_filename = f"{safe_name}-Upgrade-Assessment-{date_str}.pdf"
    out_path = customer_dir / out_filename

    try:
        page_count, file_size = _render_pdf(full_html, _CSS, out_path)
    except ImportError:
        return (
            "STATUS: GENERATION_FAILED\n\n"
            "PyMuPDF (fitz) is not installed. Install with:\n"
            "  pip install pymupdf"
        )
    except Exception as e:
        logger.exception("PDF generation failed")
        return (
            f"STATUS: GENERATION_FAILED\n\n"
            f"Error during PDF rendering: {type(e).__name__}: {e}\n\n"
            f"This may be caused by unsupported formatting in the "
            f"assessment files. Check logs for details."
        )

    size_str = (
        f"{file_size / 1024 / 1024:.1f} MB"
        if file_size > 1024 * 1024
        else f"{file_size / 1024:.0f} KB"
    )
    pkg_count = len(files["package_reports"])
    extras = []
    if files["upgrade_sequence"]:
        extras.append("upgrade sequence")
    if files["pre_upgrade_checklist"]:
        extras.append("pre-upgrade checklist")
    extras_str = (" + " + " + ".join(extras)) if extras else ""

    return (
        f"STATUS: READY\n\n"
        f"PDF generated successfully.\n\n"
        f"File: {out_path}\n"
        f"Size: {size_str}\n"
        f"Pages: {page_count}\n"
        f"Sections: Executive summary + {pkg_count} package reports"
        f"{extras_str}\n\n"
        f"The PDF is ready to share with customer stakeholders, TAMs, "
        f"or attach to Jira tickets."
    )
