from docx import Document
from docx.shared import Inches, 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
from PIL import Image, ImageDraw, ImageFont
from pathlib import Path
from datetime import datetime
import textwrap, math

OUT_DIR = Path(r'C:\xampp\htdocs\audit\dokumentasi')
DOCX_PATH = OUT_DIR / 'Dokumentasi_Sistem_Dashboard_Audit_MAINPP.docx'
ERD_PATH = OUT_DIR / 'erd_sistem_audit.png'
FLOW_PATH = OUT_DIR / 'flow_sistem_audit.png'
RESET_FLOW_PATH = OUT_DIR / 'flow_lupa_kata_laluan.png'
BLUE = RGBColor(15, 76, 129); GREEN = RGBColor(15, 118, 110); DARK = RGBColor(15, 23, 42); MUTED = RGBColor(100,116,139)
try:
    FONT_BOLD = ImageFont.truetype('arialbd.ttf', 22); FONT = ImageFont.truetype('arial.ttf', 18); FONT_SMALL = ImageFont.truetype('arial.ttf', 15); FONT_TINY = ImageFont.truetype('arial.ttf', 13)
except Exception:
    FONT_BOLD = FONT = FONT_SMALL = FONT_TINY = ImageFont.load_default()

def wrap_text(t,w=28): return textwrap.wrap(str(t), width=w) or ['']
def rr(draw, box, r, fill, outline='#BFD7EA', width=2): draw.rounded_rectangle(box, radius=r, fill=fill, outline=outline, width=width)
def arrow(draw, a, b, color='#64748B'):
    draw.line((a,b), fill=color, width=3); x1,y1=a; x2,y2=b; ang=math.atan2(y2-y1,x2-x1); s=12
    draw.polygon([(x2,y2),(x2-s*math.cos(ang-.45),y2-s*math.sin(ang-.45)),(x2-s*math.cos(ang+.45),y2-s*math.sin(ang+.45))], fill=color)
def draw_box(draw,x,y,w,h,title,lines,fill='#FFFFFF'):
    rr(draw,(x,y,x+w,y+h),18,fill); draw.text((x+16,y+14),title,fill='#0F4C81',font=FONT_BOLD); yy=y+48
    for line in lines:
        for part in wrap_text(line, 32): draw.text((x+16,yy),part,fill='#263244',font=FONT_TINY); yy+=18

def create_erd():
    img=Image.new('RGB',(1800,1240),'#F4F7FB'); d=ImageDraw.Draw(img)
    d.text((55,35),'ERD Sistem Dashboard Audit MAINPP',fill='#0F172A',font=FONT_BOLD); d.text((55,66),'Hubungan utama jadual database audit_system',fill='#64748B',font=FONT_SMALL)
    boxes=[('users',70,125,330,230,['id PK','nama, ic, emel','role, jabatan','approval_status','account_status']),('audit_forms',575,120,340,230,['id PK','perkara','tarikh_mula, tarikh_akhir','created_by FK users.id']),('audit_form_assignments',1080,120,385,230,['id PK','audit_form_id FK','assigned_unit','audit_approval_status']),('audit_feedbacks',575,455,430,270,['id PK','audit_form_id FK','submitted_by FK users.id','submitted_unit','maklum_balas','catatan_audit']),('audit_form_attachments',1100,455,400,210,['id PK','audit_form_id FK','attachment_path','attachment_name']),('audit_feedback_attachments',1100,740,430,210,['id PK','audit_feedback_id FK','attachment_path','attachment_name']),('notifications',70,455,360,230,['id PK','user_id FK users.id','type, title, message','link, is_read']),('department_access_permissions',70,775,405,245,['jabatan + module_key UNIQUE','can_create/read/update/delete','can_rollback','updated_by']),('user_access_permissions',560,810,380,220,['user_id FK users.id','module_key','CRUD + rollback','override capaian bahagian']),('password_reset_codes',1030,1005,410,175,['user_id FK users.id','emel, ic','code_hash, expires_at','used_at']),('user_transaction_logs',70,1065,380,150,['user_id, nama, jabatan, role','operasi, created_at']),('access_permission_rollbacks',560,1065,390,150,['jabatan, snapshot','created_by, restored_by'])]
    for title,x,y,w,h,lines in boxes: draw_box(d,x,y,w,h,title,lines,fill='#EEF5FF' if title in ['users','audit_forms','audit_feedbacks'] else '#FFFFFF')
    for a,b in [((400,240),(575,235)),((915,235),(1080,235)),((745,350),(745,455)),((915,555),(1100,555)),((1005,620),(1100,830)),((400,565),(575,585)),((250,355),(250,455)),((430,890),(560,900))]: arrow(d,a,b)
    d.text((70,1190),'Nota: Capaian pengguna individu boleh override capaian bahagian untuk modul/action tertentu.',fill='#475569',font=FONT_SMALL)
    img.save(ERD_PATH)

def create_flow():
    img=Image.new('RGB',(1600,1200),'#F4F7FB'); d=ImageDraw.Draw(img)
    d.text((55,35),'Flow Chart Operasi Audit',fill='#0F172A',font=FONT_BOLD); d.text((55,66),'Aliran utama daripada login hingga audit selesai',fill='#64748B',font=FONT_SMALL)
    steps=[(120,130,360,120,'1. Login',['Emel dan kata laluan disahkan.']),(620,130,390,120,'2. Semak Role & Capaian',['Role, capaian bahagian dan capaian pengguna.']),(1120,130,360,120,'3. Dashboard',['Statistik, carta, notifikasi dan tugasan.']),(120,370,420,140,'4. Daftar Maklumat Audit',['Perkara audit, tempoh, lampiran dan tindakan bahagian.']),(620,370,390,140,'5. Notifikasi Tugasan',['Bahagian menerima notifikasi audit baru.']),(1120,370,360,140,'6. Maklum Balas',['Bahagian menghantar ringkasan, maklum balas dan lampiran.']),(120,650,420,140,'7. Catatan Audit',['Admin Audit memberi catatan kepada bahagian.']),(620,650,390,140,'8. Kelulusan Audit',['Perlu Kemaskini atau Selesai Audit.']),(1120,650,360,140,'9. Isu Selesai',['Semua bahagian selesai audit.']),(620,920,390,130,'10. Log & Rekod',['Aktiviti direkod sebagai transaksi log.'])]
    for x,y,w,h,t,l in steps: draw_box(d,x,y,w,h,t,l)
    for a,b in [((480,190),(620,190)),((1010,190),(1120,190)),((1300,250),(330,370)),((540,440),(620,440)),((1010,440),(1120,440)),((1300,510),(330,650)),((540,720),(620,720)),((1010,720),(1120,720)),((800,790),(800,920))]: arrow(d,a,b)
    img.save(FLOW_PATH)

def create_reset_flow():
    img=Image.new('RGB',(1500,760),'#F4F7FB'); d=ImageDraw.Draw(img)
    d.text((55,35),'Flow Chart Lupa Kata Laluan',fill='#0F172A',font=FONT_BOLD); d.text((55,66),'Reset kata laluan menggunakan emel, IC dan kod verifikasi',fill='#64748B',font=FONT_SMALL)
    steps=[(75,150,300,120,'1. Emel & IC',['Pengguna masukkan emel dan nombor kad pengenalan.']),(460,150,300,120,'2. Semak Database',['Sistem semak padanan dan status akaun.']),(845,150,300,120,'3. Hantar Kod',['Kod verifikasi dihantar melalui SMTP.']),(460,430,300,120,'4. Verifikasi Kod',['Pengguna memasukkan kod diterima.']),(845,430,300,120,'5. Password Baru',['Jika kod sah, password baru disimpan.']),(1230,430,220,120,'6. Login',['Login semula dengan password baru.'])]
    for x,y,w,h,t,l in steps: draw_box(d,x,y,w,h,t,l)
    for a,b in [((375,210),(460,210)),((760,210),(845,210)),((995,270),(610,430)),((760,490),(845,490)),((1145,490),(1230,490))]: arrow(d,a,b)
    img.save(RESET_FLOW_PATH)
create_erd(); create_flow(); create_reset_flow()

def shade_cell(cell, fill):
    tcPr=cell._tc.get_or_add_tcPr(); shd=OxmlElement('w:shd'); shd.set(qn('w:fill'), fill); tcPr.append(shd)
def cell_text(cell,text,bold=False,color=None):
    cell.text=''; p=cell.paragraphs[0]; r=p.add_run(str(text)); r.bold=bold; r.font.name='Arial'; r.font.size=Pt(10)
    if color: r.font.color.rgb=color
    cell.vertical_alignment=WD_CELL_VERTICAL_ALIGNMENT.CENTER
def style_table(tbl):
    tbl.alignment=WD_TABLE_ALIGNMENT.LEFT; tbl.style='Table Grid'
    for i,row in enumerate(tbl.rows):
        for cell in row.cells:
            if i==0: shade_cell(cell,'F8FBFF')
            for p in cell.paragraphs:
                for r in p.runs:
                    r.font.name='Arial'; r.font.size=Pt(10)
                    if i==0: r.bold=True; r.font.color.rgb=BLUE
def heading(doc,text,level=1):
    p=doc.add_heading(text,level); 
    for r in p.runs: r.font.name='Arial'; r.font.color.rgb=BLUE if level<=2 else DARK
    return p
def bullet(doc,text): doc.add_paragraph(text, style='List Bullet')

doc=Document(); sec=doc.sections[0]; sec.top_margin=Inches(.75); sec.bottom_margin=Inches(.75); sec.left_margin=Inches(.8); sec.right_margin=Inches(.8)
for s in ['Normal','Title','Subtitle','Heading 1','Heading 2','Heading 3']:
    doc.styles[s].font.name='Arial'
doc.styles['Normal'].font.size=Pt(10.5); doc.styles['Title'].font.size=Pt(22); doc.styles['Heading 1'].font.size=Pt(16); doc.styles['Heading 2'].font.size=Pt(13)
h=sec.header.paragraphs[0]; h.text='Dokumentasi Sistem Dashboard Audit MAINPP'; h.alignment=WD_ALIGN_PARAGRAPH.RIGHT
for r in h.runs: r.font.name='Arial'; r.font.size=Pt(8); r.font.color.rgb=MUTED
f=sec.footer.paragraphs[0]; f.text='Dashboard Audit MAINPP | Dokumentasi Sistem'; f.alignment=WD_ALIGN_PARAGRAPH.CENTER
for r in f.runs: r.font.name='Arial'; r.font.size=Pt(8); r.font.color.rgb=MUTED
p=doc.add_paragraph(); p.alignment=WD_ALIGN_PARAGRAPH.CENTER; r=p.add_run('DOKUMENTASI SISTEM'); r.bold=True; r.font.size=Pt(14); r.font.color.rgb=GREEN
p=doc.add_paragraph(style='Title'); p.alignment=WD_ALIGN_PARAGRAPH.CENTER; r=p.add_run('Dashboard Audit MAINPP'); r.font.color.rgb=BLUE
p=doc.add_paragraph(style='Subtitle'); p.alignment=WD_ALIGN_PARAGRAPH.CENTER; r=p.add_run('Dokumentasi fungsi sistem, struktur database, ERD dan flow chart'); r.font.color.rgb=MUTED
meta=doc.add_table(rows=5,cols=2); rows=[('Nama Sistem','Dashboard Audit MAINPP'),('Organisasi','Majlis Agama Islam Negeri Pulau Pinang'),('Platform','PHP, MySQL, XAMPP, HTML/CSS/JavaScript'),('Database','audit_system'),('Tarikh Dokumentasi',datetime.now().strftime('%d/%m/%Y'))]
for i,(k,v) in enumerate(rows): cell_text(meta.cell(i,0),k,True,BLUE); cell_text(meta.cell(i,1),v); shade_cell(meta.cell(i,0),'F8FBFF')
style_table(meta); doc.add_paragraph('Nota Keselamatan: Akaun lalai pembangunan perlu ditukar kata laluan sebelum sistem digunakan secara rasmi.').runs[0].font.bold=True; doc.add_page_break()
heading(doc,'1. Pengenalan Sistem'); doc.add_paragraph('Dashboard Audit MAINPP ialah sistem dalaman untuk mengurus pendaftaran maklumat audit, tugasan kepada bahagian/unit, maklum balas audit, catatan audit, kelulusan status audit, notifikasi, capaian pengguna dan transaksi log pengguna.')
for x in ['Memusatkan rekod audit dan tugasan mengikut bahagian/unit.','Membolehkan bahagian terlibat menghantar maklum balas dan lampiran.','Membolehkan Admin Audit memberi catatan audit dan menilai status Selesai Audit.','Menyediakan kawalan capaian berdasarkan bahagian dan pengguna individu.','Merekod aktiviti utama pengguna untuk tujuan jejak audit.']: bullet(doc,x)
heading(doc,'2. Peranan Pengguna'); tbl=doc.add_table(rows=1,cols=3); [cell_text(tbl.cell(0,i),h,True,BLUE) for i,h in enumerate(['Role','Skop Utama','Keterangan'])]
for row in [('Pentadbir Sistem','Kawalan sistem penuh','Mengurus capaian bahagian, capaian pengguna, pengguna, status akaun dan konfigurasi akses.'),('Admin Audit','Pengurusan audit','Mendaftarkan audit, menyemak maklum balas, memberi catatan audit dan mengemaskini kelulusan audit.'),('Pengguna Biasa','Maklum balas bahagian','Melihat audit yang ditugaskan kepada bahagian dan menghantar/kemaskini maklum balas dalam tempoh audit.')]:
    cells=tbl.add_row().cells; [cell_text(cells[i],v,i==0) for i,v in enumerate(row)]
style_table(tbl)
heading(doc,'3. Modul Sistem'); mods=[('Dashboard','Statistik audit, isu selesai/belum selesai, carta dan tugasan.'),('Daftar Maklumat Audit','Daftar/kemaskini perkara audit, tempoh, lampiran dan tindakan bahagian.'),('Senarai Maklumat Audit','Senarai audit dengan filter dan tindakan edit/view/delete.'),('Senarai Maklum Balas Audit','Senarai maklum balas, lampiran dan tarikh kemaskini.'),('Pengurusan Pendaftaran','Kelulusan atau penolakan akaun pengguna baharu.'),('Senarai Pengguna','Paparan pengguna, role, bahagian dan status akaun.'),('Capaian Pengguna/Bahagian','Tetapan CRUD dan rollback berdasarkan bahagian serta pengguna individu.'),('Transaksi Log Pengguna','Rekod aktiviti pengguna.'),('Notifikasi','Makluman tugasan audit dan catatan audit.'),('Profile Pengguna','Kemaskini profil dan kata laluan.'),('Lupa Kata Laluan','Reset password menggunakan emel, IC dan kod verifikasi SMTP.')]
tbl=doc.add_table(rows=1,cols=2); [cell_text(tbl.cell(0,i),h,True,BLUE) for i,h in enumerate(['Modul','Fungsi'])]
for row in mods: cells=tbl.add_row().cells; cell_text(cells[0],row[0],True); cell_text(cells[1],row[1])
style_table(tbl); doc.add_page_break()
heading(doc,'4. Flow Chart Sistem'); doc.add_paragraph('Rajah berikut menunjukkan aliran utama operasi audit daripada login hingga rekod audit selesai dan direkodkan.'); doc.add_picture(str(FLOW_PATH),width=Inches(7.0)); doc.paragraphs[-1].alignment=WD_ALIGN_PARAGRAPH.CENTER
heading(doc,'5. Flow Chart Lupa Kata Laluan'); doc.add_paragraph('Flow ini digunakan apabila pengguna terlupa kata laluan dan perlu mengesahkan emel serta nombor kad pengenalan.'); doc.add_picture(str(RESET_FLOW_PATH),width=Inches(7.0)); doc.paragraphs[-1].alignment=WD_ALIGN_PARAGRAPH.CENTER; doc.add_page_break()
heading(doc,'6. Entity Relationship Diagram (ERD)'); doc.add_paragraph('ERD berikut memaparkan hubungan jadual utama dalam database audit_system.'); doc.add_picture(str(ERD_PATH),width=Inches(7.0)); doc.paragraphs[-1].alignment=WD_ALIGN_PARAGRAPH.CENTER
heading(doc,'7. Struktur Database'); db=[('users','Pengguna, role, bahagian, status akaun dan password hash.'),('audit_forms','Rekod utama maklumat audit.'),('audit_form_assignments','Bahagian/unit ditugaskan dan status audit setiap bahagian.'),('audit_form_attachments','Lampiran maklumat audit.'),('audit_feedbacks','Maklum balas, catatan audit dan metadata pengguna.'),('audit_feedback_attachments','Lampiran tambahan maklum balas.'),('notifications','Notifikasi sistem kepada pengguna.'),('department_access_permissions','Capaian modul/action berdasarkan bahagian.'),('user_access_permissions','Override capaian bagi pengguna individu.'),('department_access_permission_rollbacks','Snapshot capaian untuk rollback.'),('password_reset_codes','Kod reset kata laluan dan tempoh sah.'),('user_transaction_logs','Jejak transaksi pengguna.')]
tbl=doc.add_table(rows=1,cols=2); [cell_text(tbl.cell(0,i),h,True,BLUE) for i,h in enumerate(['Jadual','Tujuan'])]
for row in db: cells=tbl.add_row().cells; cell_text(cells[0],row[0],True); cell_text(cells[1],row[1])
style_table(tbl)
heading(doc,'8. Kawalan Capaian'); doc.add_paragraph('Kawalan capaian menggunakan gabungan role, capaian bahagian dan capaian pengguna individu. Jika tetapan pengguna individu wujud, ia akan override capaian bahagian untuk modul/action tersebut.')
for x in ['Pentadbir Sistem mempunyai capaian penuh.','Admin Audit dan Pengguna Biasa tertakluk kepada permission modul.','Action yang digunakan ialah Create, Read, Update, Delete dan Rollback.','Modul Daftar Pengguna menggunakan Create dan Read; Pengurusan Pendaftaran menggunakan Read dan Update.']: bullet(doc,x)
heading(doc,'9. Peraturan Perniagaan Utama')
for x in ['Maklum balas belum boleh diisi sebelum tarikh mula audit.','Maklum balas tidak boleh diisi atau dikemaskini selepas tarikh akhir audit tamat.','Kelulusan audit setiap bahagian boleh ditetapkan kepada Perlu Kemaskini atau Selesai Audit.','Isu selesai dikira apabila status bahagian ialah Selesai Audit.','Admin Audit tidak dibenarkan mengubah role Pentadbir Sistem.','Notifikasi dihantar apabila audit ditugaskan dan apabila catatan audit diberikan.']: bullet(doc,x)
heading(doc,'10. Keselamatan dan Sesi'); tbl=doc.add_table(rows=1,cols=2); [cell_text(tbl.cell(0,i),h,True,BLUE) for i,h in enumerate(['Komponen','Keterangan'])]
for row in [('Password Hash','Password disimpan menggunakan hash PHP.'),('SMTP Reset Password','Kod verifikasi dihantar melalui email.'),('Session Timeout','Amaran sesi tamat dipaparkan dan pengguna boleh sambung sesi.'),('Account Status','Akaun pending/rejected/inactive tidak boleh login.'),('Transaction Log','Aktiviti penting direkod untuk audit trail.')]: cells=tbl.add_row().cells; cell_text(cells[0],row[0],True); cell_text(cells[1],row[1])
style_table(tbl)
heading(doc,'11. Akaun Lalai Pembangunan'); tbl=doc.add_table(rows=1,cols=3); [cell_text(tbl.cell(0,i),h,True,BLUE) for i,h in enumerate(['Role','Emel','Nota'])]
for row in [('Admin Audit','admin@mainpp.gov.my','Tukar kata laluan sebelum production.'),('Pentadbir Sistem','superadmin@mainpp.gov.my','Tukar kata laluan sebelum production.')]: cells=tbl.add_row().cells; [cell_text(cells[i],v,i==0) for i,v in enumerate(row)]
style_table(tbl)
heading(doc,'12. Cadangan Penambahbaikan')
for x in ['Aktifkan HTTPS apabila sistem ditempatkan pada server rasmi.','Hadkan cubaan login dan reset password.','Tambah backup berkala database audit_system dan folder uploads.','Sediakan SOP pentadbiran capaian pengguna dan semakan permission berkala.']: bullet(doc,x)
doc.save(DOCX_PATH); print(DOCX_PATH)
