from pathlib import Path
from PIL import Image
from docx import Document
from docx.shared import Mm, Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_CELL_VERTICAL_ALIGNMENT
from docx.oxml import OxmlElement
from docx.oxml.ns import qn

OUT = Path('/home/agent/exports/260820_Gasser_Abmahnung_an_Besteller_CI_optimiert.docx')
SOURCE_LOGO = Path('/home/agent/projects/GasserWebsite/public/images/gasser-logo.png')
CROPPED_LOGO = Path('/home/agent/exports/.gasser_logo_letterhead.png')
OUT.parent.mkdir(parents=True, exist_ok=True)

BORDEAUX = '7A1F2B'
ANTHRACITE = '1F2326'
STEEL = '596066'
CONCRETE_LIGHT = 'E7E3DA'


def twips(mm):
    return int(mm * 56.692913)


def set_cell_margins(cell, top=0, start=0, bottom=0, end=0):
    tcPr = cell._tc.get_or_add_tcPr()
    tcMar = tcPr.first_child_found_in('w:tcMar')
    if tcMar is None:
        tcMar = OxmlElement('w:tcMar')
        tcPr.append(tcMar)
    for name, value in [('top', top), ('start', start), ('bottom', bottom), ('end', end)]:
        node = tcMar.find(qn(f'w:{name}'))
        if node is None:
            node = OxmlElement(f'w:{name}')
            tcMar.append(node)
        node.set(qn('w:w'), str(value))
        node.set(qn('w:type'), 'dxa')


def set_table_widths(table, widths_mm):
    table.autofit = False
    table.alignment = WD_TABLE_ALIGNMENT.LEFT
    total = twips(sum(widths_mm))
    tblPr = table._tbl.tblPr
    tblW = tblPr.find(qn('w:tblW'))
    if tblW is None:
        tblW = OxmlElement('w:tblW')
        tblPr.append(tblW)
    tblW.set(qn('w:w'), str(total))
    tblW.set(qn('w:type'), 'dxa')

    grid = table._tbl.tblGrid
    for child in list(grid):
        grid.remove(child)
    for mm in widths_mm:
        col = OxmlElement('w:gridCol')
        col.set(qn('w:w'), str(twips(mm)))
        grid.append(col)

    for row in table.rows:
        for idx, mm in enumerate(widths_mm):
            cell = row.cells[idx]
            cell.width = Mm(mm)
            tcPr = cell._tc.get_or_add_tcPr()
            tcW = tcPr.find(qn('w:tcW'))
            if tcW is None:
                tcW = OxmlElement('w:tcW')
                tcPr.append(tcW)
            tcW.set(qn('w:w'), str(twips(mm)))
            tcW.set(qn('w:type'), 'dxa')


def remove_table_borders(table):
    tblPr = table._tbl.tblPr
    borders = tblPr.first_child_found_in('w:tblBorders')
    if borders is None:
        borders = OxmlElement('w:tblBorders')
        tblPr.append(borders)
    for edge in ('top', 'left', 'bottom', 'right', 'insideH', 'insideV'):
        el = borders.find(qn(f'w:{edge}'))
        if el is None:
            el = OxmlElement(f'w:{edge}')
            borders.append(el)
        el.set(qn('w:val'), 'nil')


def set_run_font(run, size=10.5, bold=None, color=ANTHRACITE, italic=None):
    run.font.name = 'Arial'
    run._element.get_or_add_rPr().rFonts.set(qn('w:eastAsia'), 'Arial')
    run.font.size = Pt(size)
    run.font.color.rgb = RGBColor.from_string(color)
    if bold is not None:
        run.bold = bold
    if italic is not None:
        run.italic = italic


def set_paragraph(p, before=0, after=0, line=1.12, keep_next=False, alignment=None):
    pf = p.paragraph_format
    pf.space_before = Pt(before)
    pf.space_after = Pt(after)
    pf.line_spacing = line
    pf.keep_with_next = keep_next
    if alignment is not None:
        p.alignment = alignment


def add_text(p, text, **kwargs):
    run = p.add_run(text)
    set_run_font(run, **kwargs)
    return run


def add_rule(paragraph, color=BORDEAUX, size='12', space='5'):
    pPr = paragraph._p.get_or_add_pPr()
    pBdr = pPr.find(qn('w:pBdr'))
    if pBdr is None:
        pBdr = OxmlElement('w:pBdr')
        pPr.append(pBdr)
    bottom = OxmlElement('w:bottom')
    bottom.set(qn('w:val'), 'single')
    bottom.set(qn('w:sz'), size)
    bottom.set(qn('w:space'), space)
    bottom.set(qn('w:color'), color)
    pBdr.append(bottom)


def style_placeholder(run):
    set_run_font(run, size=10.3, color=BORDEAUX)
    shd = OxmlElement('w:shd')
    shd.set(qn('w:fill'), CONCRETE_LIGHT)
    run._r.get_or_add_rPr().append(shd)


def add_field(paragraph, instruction, fallback='1'):
    run = paragraph.add_run()
    begin = OxmlElement('w:fldChar'); begin.set(qn('w:fldCharType'), 'begin')
    instr = OxmlElement('w:instrText'); instr.set(qn('xml:space'), 'preserve'); instr.text = instruction
    sep = OxmlElement('w:fldChar'); sep.set(qn('w:fldCharType'), 'separate')
    text = OxmlElement('w:t'); text.text = fallback
    end = OxmlElement('w:fldChar'); end.set(qn('w:fldCharType'), 'end')
    for node in (begin, instr, sep, text, end):
        run._r.append(node)
    set_run_font(run, size=7.7, color=STEEL)


# Crop transparent logo margins so the visible mark aligns with the text grid.
im = Image.open(SOURCE_LOGO).convert('RGBA')
alpha_bbox = im.getchannel('A').getbbox()
if alpha_bbox:
    x1, y1, x2, y2 = alpha_bbox
    pad = 4
    crop = im.crop((max(0, x1-pad), max(0, y1-pad), min(im.width, x2+pad), min(im.height, y2+pad)))
else:
    crop = im
crop.save(CROPPED_LOGO)

doc = Document()
sec = doc.sections[0]
sec.page_width = Mm(210)
sec.page_height = Mm(297)
sec.left_margin = Mm(25)
sec.right_margin = Mm(25)
sec.top_margin = Mm(18)
sec.bottom_margin = Mm(20)
sec.header_distance = Mm(7)
sec.footer_distance = Mm(9)

normal = doc.styles['Normal']
normal.font.name = 'Arial'
normal._element.get_or_add_rPr().rFonts.set(qn('w:eastAsia'), 'Arial')
normal.font.size = Pt(10.5)
normal.font.color.rgb = RGBColor.from_string(ANTHRACITE)
normal.paragraph_format.space_after = Pt(0)
normal.paragraph_format.line_spacing = 1.12

# Letterhead
header = sec.header
hp = header.paragraphs[0]
set_paragraph(hp, after=5, line=1.0, alignment=WD_ALIGN_PARAGRAPH.LEFT)
hp.paragraph_format.left_indent = Mm(0)
hp.add_run().add_picture(str(CROPPED_LOGO), width=Mm(57))
add_rule(hp, size='14', space='6')

# Recipient and contact: fixed three-column grid with a true 12 mm gutter.
info = doc.add_table(rows=1, cols=3)
set_table_widths(info, [74, 12, 74])
remove_table_borders(info)
for cell in info.rows[0].cells:
    set_cell_margins(cell)
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.TOP
left, spacer, right = info.rows[0].cells

p = left.paragraphs[0]
set_paragraph(p, after=4, line=1.0)
add_text(p, 'BESTELLER / BAUHERRSCHAFT', size=7.4, bold=True, color=BORDEAUX)
for text in ('[Firma / Name]', '[Kontaktperson]', '[Strasse / Nr.]', '[PLZ Ort]'):
    p = left.add_paragraph()
    set_paragraph(p, after=1.5, line=1.0)
    style_placeholder(add_text(p, text))

p = right.paragraphs[0]
set_paragraph(p, after=4, line=1.0)
add_text(p, 'IHRE ANSPRECHPERSON', size=7.4, bold=True, color=BORDEAUX)
for text, size, bold, color in (
    ('Andreas Gasser', 10.5, True, ANTHRACITE),
    ('Geschäftsführer', 9.2, False, STEEL),
    ('077 268 25 29', 9.2, False, ANTHRACITE),
    ('info@gassergmbh.ch', 9.2, False, ANTHRACITE),
    ('www.gassergmbh.ch', 9.2, False, ANTHRACITE),
):
    p = right.add_paragraph()
    set_paragraph(p, after=1.2, line=1.0)
    add_text(p, text, size=size, bold=bold, color=color)

# Date — right aligned, visually closing the address field.
p = doc.add_paragraph()
set_paragraph(p, before=18, after=18, alignment=WD_ALIGN_PARAGRAPH.RIGHT)
add_text(p, 'Windisch, 20. August 2026', size=10.2, color=STEEL)

# Document heading
p = doc.add_paragraph()
set_paragraph(p, after=2, keep_next=True)
add_text(p, 'Abmahnung', size=17, bold=True, color=BORDEAUX)
p = doc.add_paragraph()
set_paragraph(p, after=13, keep_next=True)
add_text(p, 'gemäss Art. 365/369 OR bzw. Norm SIA 118:2013 Art. 25 usw.', size=9.4, color=STEEL)

# Project information with fixed alignment.
facts = doc.add_table(rows=2, cols=2)
set_table_widths(facts, [36, 124])
remove_table_borders(facts)
for row in facts.rows:
    for cell in row.cells:
        set_cell_margins(cell)
        cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.TOP
for idx, (label, value) in enumerate((
    ('Projekt:', '410.3170.10 – Mönchaltorf – Gemeindehaus mit Wohnbau'),
    ('Werkvertrag vom:', '[Datum Werkvertrag]'),
)):
    p = facts.cell(idx, 0).paragraphs[0]
    set_paragraph(p, after=2, line=1.05)
    add_text(p, label, size=10.3, bold=True)
    p = facts.cell(idx, 1).paragraphs[0]
    set_paragraph(p, after=2, line=1.05)
    run = add_text(p, value, size=10.3)
    if value.startswith('['):
        style_placeholder(run)

# Main letter text
p = doc.add_paragraph()
set_paragraph(p, before=14, after=11)
add_text(p, 'Sehr geehrte Damen und Herren')

p = doc.add_paragraph()
set_paragraph(p, after=11, line=1.18)
add_text(p, 'Wir mahnen in Erfüllung unserer vertraglichen und gesetzlichen Sorgfaltspflichten die nachfolgend angeführten Verhältnisse ab und lehnen die Haftung für alle Mängel und anderen nachteiligen Folgen, die sich aus der Nichtbeachtung dieser Abmahnung einstellen, vollumfänglich ab.')

p = doc.add_paragraph()
set_paragraph(p, after=3, keep_next=True)
add_text(p, 'Abgemahnte Verhältnisse:', bold=True)
p = doc.add_paragraph()
set_paragraph(p, after=11)
style_placeholder(add_text(p, '[Abgemahnte Verhältnisse präzise beschreiben]'))

p = doc.add_paragraph()
set_paragraph(p, after=3, keep_next=True)
add_text(p, 'Sachverhalt bereits mündlich mitgeteilt am ')
style_placeholder(add_text(p, '[Datum]'))
p = doc.add_paragraph()
set_paragraph(p, after=11)
add_text(p, 'an ')
style_placeholder(add_text(p, '[Name / Funktion]'))

p = doc.add_paragraph()
set_paragraph(p, after=14)
style_placeholder(add_text(p, '[Ort / Datum]'))

p = doc.add_paragraph()
set_paragraph(p, after=34)
add_text(p, 'Freundliche Grüsse')
p = doc.add_paragraph()
set_paragraph(p, after=1)
add_text(p, 'Andreas Gasser', bold=True)
p = doc.add_paragraph()
set_paragraph(p)
add_text(p, 'Geschäftsführer', size=9.4, color=STEEL)

# Footer: fixed widths and generous separation between company, contact and page count.
footer = sec.footer
fp = footer.paragraphs[0]
set_paragraph(fp, after=5, line=1.0)
add_rule(fp, size='10', space='5')
ft = footer.add_table(rows=1, cols=3, width=Mm(160))
set_table_widths(ft, [58, 72, 30])
remove_table_borders(ft)
for cell in ft.rows[0].cells:
    set_cell_margins(cell)
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.TOP

p = ft.cell(0, 0).paragraphs[0]
set_paragraph(p, after=1, line=1.0)
add_text(p, 'Gasser Bauunternehmen GmbH', size=7.7, bold=True, color=BORDEAUX)
p = ft.cell(0, 0).add_paragraph(); set_paragraph(p, line=1.0)
add_text(p, 'Grünrain 3 · 5210 Windisch', size=7.7, color=STEEL)

p = ft.cell(0, 1).paragraphs[0]
set_paragraph(p, after=1, line=1.0)
add_text(p, '077 268 25 29 · info@gassergmbh.ch', size=7.7, color=STEEL)
p = ft.cell(0, 1).add_paragraph(); set_paragraph(p, line=1.0)
add_text(p, 'www.gassergmbh.ch', size=7.7, color=STEEL)

p = ft.cell(0, 2).paragraphs[0]
set_paragraph(p, line=1.0, alignment=WD_ALIGN_PARAGRAPH.RIGHT)
add_text(p, 'Seite ', size=7.7, color=STEEL)
add_field(p, 'PAGE', '1')
add_text(p, ' / ', size=7.7, color=STEEL)
add_field(p, 'NUMPAGES', '1')

props = doc.core_properties
props.title = 'Abmahnung an Besteller'
props.subject = 'Optimierte CI-Vorlage der Gasser Bauunternehmen GmbH'
props.author = 'Gasser Bauunternehmen GmbH'
props.keywords = 'Abmahnung, Besteller, SIA 118, Gasser Bauunternehmen GmbH'

# Swiss German proofing and automatic field update.
for container in [doc] + list(doc.sections):
    pass
for paragraph in list(doc.paragraphs) + list(header.paragraphs) + list(footer.paragraphs):
    for run in paragraph.runs:
        rPr = run._r.get_or_add_rPr()
        lang = rPr.find(qn('w:lang'))
        if lang is None:
            lang = OxmlElement('w:lang')
            rPr.append(lang)
        lang.set(qn('w:val'), 'de-CH')
settings = doc.settings._element
update = settings.find(qn('w:updateFields'))
if update is None:
    update = OxmlElement('w:updateFields')
    settings.append(update)
update.set(qn('w:val'), 'true')

doc.save(str(OUT))
print(OUT)
