"""
AEGIS CYBER — Letterhead Word Document Generator
Converts the four deliverable markdown files to formatted .docx
Navy #0B2545 / Gold #C9A24B, Georgia headers / Calibri body, A4
"""

import os, re, sys, io
from docx import Document
from docx.shared import Inches, Pt, RGBColor, Cm, Emu
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_ALIGN_VERTICAL
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
from copy import deepcopy

# ── Palette ──────────────────────────────────────────────────────────────────
NAVY   = RGBColor(0x0B, 0x25, 0x45)
GOLD   = RGBColor(0xC9, 0xA2, 0x4B)
WHITE  = RGBColor(0xFF, 0xFF, 0xFF)
BLACK  = RGBColor(0x00, 0x00, 0x00)
D_GRAY = RGBColor(0x33, 0x33, 0x33)
M_GRAY = RGBColor(0x55, 0x55, 0x55)
L_GRAY = RGBColor(0xF4, 0xF4, 0xF4)
TBL_HD = RGBColor(0x0B, 0x25, 0x45)  # navy table header bg
WARN_BG= RGBColor(0xFF, 0xF8, 0xE7)  # pale amber for ⚠ callouts

LOGO_PATH = "client/public/images/aegis-cyber-logo.png"
LOGO_H_INCHES = 0.45          # height in header

# ── Page setup ───────────────────────────────────────────────────────────────
def set_a4(doc):
    for section in doc.sections:
        section.page_width  = Cm(21)
        section.page_height = Cm(29.7)
        section.top_margin    = Cm(2.5)
        section.bottom_margin = Cm(2.2)
        section.left_margin   = Cm(2.5)
        section.right_margin  = Cm(2.5)
        section.header_distance = Cm(1.2)
        section.footer_distance = Cm(1.2)

# ── XML helpers ──────────────────────────────────────────────────────────────
def set_cell_bg(cell, hex_color):
    tc   = cell._tc
    tcPr = tc.get_or_add_tcPr()
    shd  = OxmlElement('w:shd')
    shd.set(qn('w:val'),   'clear')
    shd.set(qn('w:color'), 'auto')
    shd.set(qn('w:fill'),  hex_color)
    tcPr.append(shd)

def set_cell_border(cell, **kwargs):
    """kwargs: top/bottom/left/right = 'single'|'nil'"""
    tc   = cell._tc
    tcPr = tc.get_or_add_tcPr()
    tcBorders = OxmlElement('w:tcBorders')
    for side, style in kwargs.items():
        el = OxmlElement(f'w:{side}')
        el.set(qn('w:val'),   style)
        el.set(qn('w:sz'),    '4')
        el.set(qn('w:space'), '0')
        el.set(qn('w:color'), 'DDDDDD')
        tcBorders.append(el)
    tcPr.append(tcBorders)

def add_horiz_rule(doc, color_hex="C9A24B"):
    p = doc.add_paragraph()
    pPr = p._p.get_or_add_pPr()
    pb  = OxmlElement('w:pBdr')
    bot = OxmlElement('w:bottom')
    bot.set(qn('w:val'),   'single')
    bot.set(qn('w:sz'),    '6')
    bot.set(qn('w:space'), '1')
    bot.set(qn('w:color'), color_hex)
    pb.append(bot)
    pPr.append(pb)
    p.paragraph_format.space_before = Pt(4)
    p.paragraph_format.space_after  = Pt(4)
    return p

def set_para_spacing(p, before=0, after=6, line=None):
    pf = p.paragraph_format
    pf.space_before = Pt(before)
    pf.space_after  = Pt(after)
    if line:
        pf.line_spacing_rule = WD_LINE_SPACING.EXACTLY
        pf.line_spacing = Pt(line)

def add_run_formatted(para, text, bold=False, italic=False,
                      font_name="Calibri", size=10.5, color=None):
    run = para.add_run(text)
    run.bold   = bold
    run.italic = italic
    run.font.name = font_name
    run.font.size = Pt(size)
    if color:
        run.font.color.rgb = color
    return run

# ── Header / Footer ──────────────────────────────────────────────────────────
def build_header(doc, ref_number, logo_ok):
    section = doc.sections[0]
    header  = section.header
    header.is_linked_to_previous = False
    # clear default paragraph
    for p in header.paragraphs:
        p.clear()

    # one-row table: logo left | ref right
    tbl = header.add_table(1, 2, width=Inches(6.3))
    tbl.alignment = WD_TABLE_ALIGNMENT.LEFT
    tbl.style     = 'Table Grid'
    # remove all borders
    for row in tbl.rows:
        for cell in row.cells:
            set_cell_border(cell, top='nil', bottom='nil', left='nil', right='nil')

    left_cell  = tbl.cell(0, 0)
    right_cell = tbl.cell(0, 1)
    left_cell.width  = Inches(4.0)
    right_cell.width = Inches(2.3)

    # LEFT: logo or text wordmark
    lp = left_cell.paragraphs[0]
    lp.alignment = WD_ALIGN_PARAGRAPH.LEFT
    if logo_ok and os.path.exists(LOGO_PATH):
        try:
            run = lp.add_run()
            run.add_picture(LOGO_PATH, height=Inches(LOGO_H_INCHES))
        except Exception:
            logo_ok = False

    if not logo_ok:
        run = lp.add_run("AEGIS CYBER")
        run.bold = True
        run.font.name  = "Georgia"
        run.font.size  = Pt(14)
        run.font.color.rgb = NAVY

    # RIGHT: ref + divider line at bottom
    rp = right_cell.paragraphs[0]
    rp.alignment = WD_ALIGN_PARAGRAPH.RIGHT
    r = rp.add_run(ref_number)
    r.font.name  = "Calibri"
    r.font.size  = Pt(8)
    r.font.color.rgb = M_GRAY

    # gold rule under header
    rule_p = header.add_paragraph()
    pPr = rule_p._p.get_or_add_pPr()
    pb  = OxmlElement('w:pBdr')
    bot = OxmlElement('w:bottom')
    bot.set(qn('w:val'),   'single')
    bot.set(qn('w:sz'),    '8')
    bot.set(qn('w:space'), '1')
    bot.set(qn('w:color'), 'C9A24B')
    pb.append(bot)
    pPr.append(pb)
    rule_p.paragraph_format.space_before = Pt(2)
    rule_p.paragraph_format.space_after  = Pt(0)
    return logo_ok

def build_footer(doc, ref_number):
    section = doc.sections[0]
    footer  = section.footer
    footer.is_linked_to_previous = False
    for p in footer.paragraphs:
        p.clear()

    # gold top-rule
    rule_p = footer.paragraphs[0]
    pPr = rule_p._p.get_or_add_pPr()
    pb  = OxmlElement('w:pBdr')
    top = OxmlElement('w:top')
    top.set(qn('w:val'),   'single')
    top.set(qn('w:sz'),    '6')
    top.set(qn('w:space'), '1')
    top.set(qn('w:color'), 'C9A24B')
    pb.append(top)
    pPr.append(pb)
    rule_p.paragraph_format.space_before = Pt(0)
    rule_p.paragraph_format.space_after  = Pt(2)

    fp = footer.add_paragraph()
    fp.alignment = WD_ALIGN_PARAGRAPH.CENTER
    # confidentiality tag | ref | page
    r1 = fp.add_run("AEGIS CYBER  |  ")
    r1.font.name = "Calibri"; r1.font.size = Pt(8); r1.font.color.rgb = M_GRAY
    r2 = fp.add_run(ref_number + "  |  ")
    r2.font.name = "Calibri"; r2.font.size = Pt(8); r2.font.color.rgb = M_GRAY; r2.bold = True
    r3 = fp.add_run("Page ")
    r3.font.name = "Calibri"; r3.font.size = Pt(8); r3.font.color.rgb = M_GRAY
    # page number field
    fld = OxmlElement('w:fldChar')
    fld.set(qn('w:fldCharType'), 'begin')
    fp.runs[-1]._r.append(fld)
    instr = OxmlElement('w:instrText')
    instr.text = 'PAGE'
    run_instr = OxmlElement('w:r')
    run_instr.append(instr)
    fp._p.append(run_instr)
    fld2 = OxmlElement('w:fldChar')
    fld2.set(qn('w:fldCharType'), 'end')
    run_end = OxmlElement('w:r')
    run_end.append(fld2)
    fp._p.append(run_end)

# ── Inline markup parser ──────────────────────────────────────────────────────
def apply_inline(para, text, font="Calibri", size=10.5, color=None, base_bold=False):
    """Apply **bold**, *italic*, `code` inline spans to a paragraph via finditer."""
    if not text:
        return
    # spans: **bold**, *italic*, `code`
    TOKEN = re.compile(r'\*\*(.+?)\*\*|\*(.+?)\*|`(.+?)`')
    pos = 0
    for m in TOKEN.finditer(text):
        # literal text before this token
        if m.start() > pos:
            seg = text[pos:m.start()]
            run = para.add_run(seg)
            run.bold = base_bold; run.font.name = font; run.font.size = Pt(size)
            if color: run.font.color.rgb = color
        pos = m.end()
        if m.group(1) is not None:          # **bold**
            run = para.add_run(m.group(1))
            run.bold = True; run.font.name = font; run.font.size = Pt(size)
            if color: run.font.color.rgb = color
        elif m.group(2) is not None:        # *italic*
            run = para.add_run(m.group(2))
            run.italic = True; run.bold = base_bold
            run.font.name = font; run.font.size = Pt(size)
            if color: run.font.color.rgb = color
        elif m.group(3) is not None:        # `code`
            run = para.add_run(m.group(3))
            run.font.name = "Courier New"; run.font.size = Pt(9)
            run.font.color.rgb = RGBColor(0xC0, 0x39, 0x2B)
            if base_bold: run.bold = True
    # trailing literal text
    if pos < len(text):
        seg = text[pos:]
        run = para.add_run(seg)
        run.bold = base_bold; run.font.name = font; run.font.size = Pt(size)
        if color: run.font.color.rgb = color

def strip_inline(text):
    """Strip markdown inline markers for plain text."""
    text = re.sub(r'\*\*(.+?)\*\*', r'\1', text)
    text = re.sub(r'\*(.+?)\*',     r'\1', text)
    text = re.sub(r'`(.+?)`',       r'\1', text)
    return text

# ── Markdown → docx converter ────────────────────────────────────────────────
def md_to_docx(md_text, doc, logo_ok, ref_number):
    set_a4(doc)
    logo_ok = build_header(doc, ref_number, logo_ok)
    build_footer(doc, ref_number)

    lines = md_text.split('\n')
    i = 0
    in_code   = False
    code_buf  = []
    in_table  = False
    tbl_rows  = []
    tbl_hdr   = []

    def flush_table():
        nonlocal tbl_rows, tbl_hdr, in_table
        if not tbl_rows:
            in_table = False; tbl_hdr = []; tbl_rows = []
            return
        ncols = len(tbl_hdr) if tbl_hdr else len(tbl_rows[0])
        # column widths: equal split of 6 inches
        col_w = Inches(6.2 / ncols)
        tbl = doc.add_table(rows=0, cols=ncols)
        tbl.style = 'Table Grid'
        tbl.alignment = WD_TABLE_ALIGNMENT.LEFT
        # header row
        if tbl_hdr:
            hrow = tbl.add_row()
            for j, cell_text in enumerate(tbl_hdr):
                c = hrow.cells[j]
                c.width = col_w
                set_cell_bg(c, '0B2545')
                p = c.paragraphs[0]
                p.alignment = WD_ALIGN_PARAGRAPH.LEFT
                r = p.add_run(strip_inline(cell_text.strip()))
                r.bold = True; r.font.name = "Georgia"; r.font.size = Pt(9.5)
                r.font.color.rgb = WHITE
        # data rows
        for ri, row in enumerate(tbl_rows):
            dr = tbl.add_row()
            for j, cell_text in enumerate(row):
                if j >= ncols: break
                c = dr.cells[j]
                c.width = col_w
                bg = 'F7F9FC' if ri % 2 == 0 else 'FFFFFF'
                set_cell_bg(c, bg)
                p = c.paragraphs[0]
                p.alignment = WD_ALIGN_PARAGRAPH.LEFT
                ct = cell_text.strip()
                # warn cells
                if '⚠' in ct or 'NOT YET' in ct or 'OPEN' in ct:
                    p.paragraph_format.space_before = Pt(2)
                    p.paragraph_format.space_after  = Pt(2)
                apply_inline(p, ct, size=9.5)
        # spacing after table
        sp = doc.add_paragraph()
        sp.paragraph_format.space_before = Pt(0)
        sp.paragraph_format.space_after  = Pt(6)
        in_table = False; tbl_hdr = []; tbl_rows = []

    def flush_code(lines_buf):
        if not lines_buf:
            return
        # single-cell table styled as code block
        tbl = doc.add_table(rows=1, cols=1)
        tbl.style = 'Table Grid'
        c = tbl.cell(0, 0)
        set_cell_bg(c, '1E2A3A')
        p = c.paragraphs[0]
        p.paragraph_format.space_before = Pt(4)
        p.paragraph_format.space_after  = Pt(4)
        code_text = '\n'.join(lines_buf)
        r = p.add_run(code_text)
        r.font.name = "Courier New"; r.font.size = Pt(8.5)
        r.font.color.rgb = RGBColor(0xCC, 0xE5, 0xFF)
        sp = doc.add_paragraph()
        sp.paragraph_format.space_before = Pt(0)
        sp.paragraph_format.space_after  = Pt(6)

    while i < len(lines):
        line = lines[i]

        # ── code block ──
        if line.strip().startswith('```'):
            if not in_code:
                in_code = True; code_buf = []
            else:
                flush_code(code_buf)
                in_code = False; code_buf = []
            i += 1; continue

        if in_code:
            code_buf.append(line)
            i += 1; continue

        # ── table ──
        if line.strip().startswith('|') and '|' in line:
            # separator row?
            if re.match(r'^\s*\|[\s\-:|]+\|\s*$', line):
                i += 1; continue
            cells = [c for c in line.strip().split('|') if c != '']
            if not in_table:
                in_table = True
                tbl_hdr  = cells
            else:
                tbl_rows.append(cells)
            i += 1; continue
        else:
            if in_table:
                flush_table()

        stripped = line.rstrip()

        # ── blank line ──
        if not stripped:
            i += 1; continue

        # ── horizontal rule ---
        if re.match(r'^-{3,}$', stripped) or re.match(r'^\*{3,}$', stripped):
            add_horiz_rule(doc)
            i += 1; continue

        # ── headings ──
        h1 = re.match(r'^# (.+)$', stripped)
        h2 = re.match(r'^## (.+)$', stripped)
        h3 = re.match(r'^### (.+)$', stripped)
        h4 = re.match(r'^#### (.+)$', stripped)

        if h1:
            text = strip_inline(h1.group(1))
            p = doc.add_paragraph()
            p.paragraph_format.space_before = Pt(18)
            p.paragraph_format.space_after  = Pt(6)
            r = p.add_run(text)
            r.bold = True; r.font.name = "Georgia"; r.font.size = Pt(18)
            r.font.color.rgb = NAVY
            # gold underline rule
            add_horiz_rule(doc)
            i += 1; continue

        if h2:
            text = strip_inline(h2.group(1))
            p = doc.add_paragraph()
            p.paragraph_format.space_before = Pt(14)
            p.paragraph_format.space_after  = Pt(4)
            r = p.add_run(text)
            r.bold = True; r.font.name = "Georgia"; r.font.size = Pt(14)
            r.font.color.rgb = NAVY
            i += 1; continue

        if h3:
            text = strip_inline(h3.group(1))
            p = doc.add_paragraph()
            p.paragraph_format.space_before = Pt(10)
            p.paragraph_format.space_after  = Pt(3)
            r = p.add_run(text)
            r.bold = True; r.font.name = "Georgia"; r.font.size = Pt(11.5)
            r.font.color.rgb = NAVY
            i += 1; continue

        if h4:
            text = strip_inline(h4.group(1))
            p = doc.add_paragraph()
            p.paragraph_format.space_before = Pt(8)
            p.paragraph_format.space_after  = Pt(2)
            r = p.add_run(text)
            r.bold = True; r.font.name = "Calibri"; r.font.size = Pt(10.5)
            r.font.color.rgb = NAVY
            i += 1; continue

        # ── blockquote / callout ──
        bq = re.match(r'^> (.+)$', stripped)
        if bq:
            text = bq.group(1)
            is_warn = '⚠' in text or 'NOT YET' in text
            p = doc.add_paragraph()
            p.paragraph_format.left_indent  = Cm(0.8)
            p.paragraph_format.space_before = Pt(4)
            p.paragraph_format.space_after  = Pt(4)
            pPr = p._p.get_or_add_pPr()
            shd = OxmlElement('w:shd')
            shd.set(qn('w:val'),   'clear')
            shd.set(qn('w:color'), 'auto')
            shd.set(qn('w:fill'),  'FFF8E7' if is_warn else 'EEF3F9')
            pPr.append(shd)
            # left border
            pb2 = OxmlElement('w:pBdr')
            lft = OxmlElement('w:left')
            lft.set(qn('w:val'),   'single')
            lft.set(qn('w:sz'),    '16')
            lft.set(qn('w:space'), '4')
            lft.set(qn('w:color'), 'C9A24B' if is_warn else '0B2545')
            pb2.append(lft)
            pPr.append(pb2)
            apply_inline(p, text, size=10, color=RGBColor(0x44,0x44,0x44))
            i += 1; continue

        # ── bullet list ──
        bullet = re.match(r'^(\s*)[-*] (.+)$', stripped)
        if bullet:
            indent_lvl = len(bullet.group(1)) // 2
            text = bullet.group(2)
            p = doc.add_paragraph(style='List Bullet')
            p.paragraph_format.left_indent  = Cm(0.5 + indent_lvl * 0.5)
            p.paragraph_format.space_before = Pt(1)
            p.paragraph_format.space_after  = Pt(1)
            # replace default bullet run with our formatted one
            for run in p.runs:
                run.clear()
            apply_inline(p, text, size=10.5)
            i += 1; continue

        # ── numbered list ──
        num = re.match(r'^(\s*)\d+\. (.+)$', stripped)
        if num:
            text = num.group(2)
            p = doc.add_paragraph(style='List Number')
            p.paragraph_format.space_before = Pt(1)
            p.paragraph_format.space_after  = Pt(1)
            for run in p.runs:
                run.clear()
            apply_inline(p, text, size=10.5)
            i += 1; continue

        # ── normal paragraph ──
        p = doc.add_paragraph()
        p.paragraph_format.space_before = Pt(0)
        p.paragraph_format.space_after  = Pt(6)
        apply_inline(p, stripped, size=10.5)
        i += 1

    # flush any open state
    if in_table:
        flush_table()
    if in_code and code_buf:
        flush_code(code_buf)

    return logo_ok

# ── Document manifest ─────────────────────────────────────────────────────────
DOCS = [
    {
        "src":  "docs/regulatory/PTR-002_LOAD_TEST_BASELINE_REPORT.md",
        "out":  "exports/AEGIS_PTR-002_Load_Test_Baseline_Report.docx",
        "ref":  "AS/PTR/2026/002",
        "classification": "CONFIDENTIAL",
    },
    {
        "src":  "docs/regulatory/BANK_OF_UGANDA_ONE_PAGER.md",
        "out":  "exports/AEGIS_BOU_One_Pager.docx",
        "ref":  "AS/BOU/2026/001",
        "classification": "HIGHLY CONFIDENTIAL",
    },
    {
        "src":  "docs/regulatory/LETTER_OF_TECHNICAL_INTENT.md",
        "out":  "exports/AEGIS_BOU_Letter_of_Technical_Intent.docx",
        "ref":  "AS/BOU/2026/002",
        "classification": "HIGHLY CONFIDENTIAL",
    },
    {
        "src":  "docs/regulatory/AEGIS_CYBER_TIER3_REFERENCE_ARCHITECTURE.md",
        "out":  "exports/AEGIS_BOU_Tier3_Reference_Architecture.docx",
        "ref":  "AS/BOU/2026/003",
        "classification": "HIGHLY CONFIDENTIAL",
    },
    {
        "src":  "docs/AEGIS_IT_HANDOVER_SUITE.md",
        "out":  "exports/AEGIS_IT_Handover_Suite_v4.2.2.docx",
        "ref":  "AS/IT/2026/001",
        "classification": "HIGHLY CONFIDENTIAL",
    },
    {
        "src":  "docs/AEGIS_CYBER_OPS_TRAINING_MANUAL.md",
        "out":  "exports/AEGIS_Internal_Ops_Training_Manual_v1.0.docx",
        "ref":  "AS/OPS/2026/001",
        "classification": "INTERNAL — NOT FOR DISTRIBUTION",
    },
]

# ── Main ──────────────────────────────────────────────────────────────────────
def main():
    os.makedirs("exports", exist_ok=True)

    # test logo placement on first doc
    logo_ok = os.path.exists(LOGO_PATH)
    if logo_ok:
        print(f"Logo found at {LOGO_PATH} — will attempt header placement")
    else:
        print(f"Logo NOT found at {LOGO_PATH} — using text wordmark fallback")

    results = []
    for spec in DOCS:
        src = spec["src"]
        if not os.path.exists(src):
            print(f"  SKIP (not found): {src}")
            results.append((src, "SKIPPED"))
            continue

        with open(src, encoding='utf-8') as f:
            md = f.read()

        doc = Document()
        # remove default empty paragraph
        for p in doc.paragraphs:
            p._element.getparent().remove(p._element)
        # remove default styles noise
        doc.styles['Normal'].font.name = "Calibri"
        doc.styles['Normal'].font.size = Pt(10.5)

        logo_ok = md_to_docx(md, doc, logo_ok, spec["ref"])

        out = spec["out"]
        doc.save(out)
        size_kb = os.path.getsize(out) // 1024
        status = "LOGO" if logo_ok else "TEXT-WORDMARK"
        print(f"  ✓ {os.path.basename(out)}  [{status}]  {size_kb} KB")
        results.append((os.path.basename(out), "OK", size_kb, status))

    print("\n── Summary ──────────────────────────────────────────────")
    ok  = sum(1 for r in results if len(r) > 1 and r[1] == "OK")
    skp = sum(1 for r in results if len(r) > 1 and r[1] == "SKIPPED")
    print(f"Generated: {ok}  Skipped: {skp}  Total attempted: {len(results)}")
    if any(r[3] == "TEXT-WORDMARK" for r in results if len(r) > 3):
        print("NOTE: Logo placement failed — text wordmark used in headers.")
    else:
        print("Logo placement: CLEAN on all documents.")

if __name__ == "__main__":
    main()
