from __future__ import annotations

import copy
import zipfile
from pathlib import Path
from xml.etree import ElementTree as ET


INPUT_DOCX = Path("belum_fix_source.docx")
OUTPUT_DOCX = Path("Laporan_ConfiVoice_Bab4_Bab5_Sesuai_Project.docx")
XML_PATH = "word/document.xml"

W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
NS = {"w": W_NS}
ET.register_namespace("w", W_NS)


def qn(tag: str) -> str:
    prefix, local = tag.split(":", 1)
    if prefix != "w":
        raise ValueError(tag)
    return f"{{{W_NS}}}{local}"


def paragraph_text(paragraph: ET.Element) -> str:
    return "".join(t.text or "" for t in paragraph.findall(".//w:t", NS)).strip()


def append_run(paragraph: ET.Element, text: str, bold: bool = False) -> None:
    run = ET.SubElement(paragraph, qn("w:r"))
    if bold:
        rpr = ET.SubElement(run, qn("w:rPr"))
        ET.SubElement(rpr, qn("w:b"))
    t = ET.SubElement(run, qn("w:t"))
    t.set("{http://www.w3.org/XML/1998/namespace}space", "preserve")
    t.text = text


def paragraph(text: str = "", bold: bool = False, center: bool = False) -> ET.Element:
    p = ET.Element(qn("w:p"))
    ppr = ET.SubElement(p, qn("w:pPr"))
    spacing = ET.SubElement(ppr, qn("w:spacing"))
    spacing.set(qn("w:after"), "120")
    if center:
        jc = ET.SubElement(ppr, qn("w:jc"))
        jc.set(qn("w:val"), "center")
    append_run(p, text, bold=bold)
    return p


def heading(text: str) -> ET.Element:
    return paragraph(text, bold=True, center=True)


def subheading(text: str) -> ET.Element:
    return paragraph(text, bold=True)


def table(rows: list[list[str]]) -> ET.Element:
    tbl = ET.Element(qn("w:tbl"))
    tbl_pr = ET.SubElement(tbl, qn("w:tblPr"))
    tbl_w = ET.SubElement(tbl_pr, qn("w:tblW"))
    tbl_w.set(qn("w:w"), "9360")
    tbl_w.set(qn("w:type"), "dxa")
    borders = ET.SubElement(tbl_pr, qn("w:tblBorders"))
    for side in ["top", "left", "bottom", "right", "insideH", "insideV"]:
        border = ET.SubElement(borders, qn(f"w:{side}"))
        border.set(qn("w:val"), "single")
        border.set(qn("w:sz"), "4")
        border.set(qn("w:space"), "0")
        border.set(qn("w:color"), "000000")

    col_count = max((len(row) for row in rows), default=1)
    col_width = str(max(1100, 9360 // col_count))
    grid = ET.SubElement(tbl, qn("w:tblGrid"))
    for _ in range(col_count):
        col = ET.SubElement(grid, qn("w:gridCol"))
        col.set(qn("w:w"), col_width)

    for row_index, row in enumerate(rows):
        tr = ET.SubElement(tbl, qn("w:tr"))
        for value in row:
            tc = ET.SubElement(tr, qn("w:tc"))
            tc_pr = ET.SubElement(tc, qn("w:tcPr"))
            tc_w = ET.SubElement(tc_pr, qn("w:tcW"))
            tc_w.set(qn("w:w"), col_width)
            tc_w.set(qn("w:type"), "dxa")
            tc.append(paragraph(value, bold=row_index == 0))
    return tbl


def copy_lampiran_drawing(body: ET.Element, label: str) -> ET.Element | None:
    children = list(body)
    lampiran_index = 0
    for index, child in enumerate(children):
        if child.tag == qn("w:p") and paragraph_text(child).upper() == "LAMPIRAN":
            lampiran_index = index
            break

    for index, child in enumerate(children[lampiran_index:], start=lampiran_index):
        if child.tag == qn("w:p") and paragraph_text(child).lower() == label.lower():
            for next_child in children[index + 1 : index + 5]:
                if next_child.find(".//w:drawing", NS) is not None:
                    return copy.deepcopy(next_child)
    return None


def build_bab45(use_case_drawing: ET.Element | None, flowchart_drawing: ET.Element | None) -> list[ET.Element]:
    metrics = [
        ["Keterangan", "Hasil"],
        ["Total data valid", "48 audio"],
        ["Jumlah data PD", "24 audio"],
        ["Jumlah data TPD", "24 audio"],
        ["Jumlah fitur per audio", "78 fitur"],
        ["Algoritma", "Support Vector Machine kernel RBF"],
        ["Best parameter", "C=1, gamma=scale, kernel=rbf"],
        ["Best CV f1_macro", "0,7873"],
        ["Accuracy", "0,7917 atau 79,17%"],
        ["Precision Macro", "0,7937 atau 79,37%"],
        ["Recall Macro", "0,7917 atau 79,17%"],
        ["F1 Macro", "0,7913 atau 79,13%"],
    ]
    confusion = [
        ["Kelas Aktual", "Diprediksi PD", "Diprediksi TPD", "Total"],
        ["PD", "20", "4", "24"],
        ["TPD", "6", "18", "24"],
    ]
    erd = [
        ["Entitas", "Atribut Utama", "Keterangan"],
        [
            "users",
            "id, full_name, username, password_hash, created_at",
            "Menyimpan data akun pengguna/admin yang digunakan untuk login aplikasi mobile dan admin web.",
        ],
        [
            "prediction_results",
            "id, user_id, student_name, student_gender, predicted_label, confidence, probability_pd, probability_tpd, audio_duration, volume_score, intonation_score, pause_score, created_at",
            "Menyimpan hasil analisis suara siswa, nilai probabilitas, indikator suara, dan waktu penyimpanan data.",
        ],
    ]
    blackbox = [
        ["Fitur", "Skenario Pengujian", "Hasil Yang Diharapkan", "Status"],
        ["Registrasi", "Pengguna mengisi nama lengkap, username, dan password", "Akun berhasil dibuat", "Berhasil"],
        ["Login", "Pengguna memasukkan username dan password yang benar", "Sistem masuk ke halaman utama", "Berhasil"],
        ["Rekam Audio", "Pengguna menekan tombol rekam lalu berhenti rekam", "Audio WAV tersimpan dan preview gelombang tampil", "Berhasil"],
        ["Pilih Audio", "Pengguna memilih file audio dari perangkat", "Audio dipilih dan siap diprediksi", "Berhasil"],
        ["Prediksi", "Pengguna mengirim audio valid ke backend", "Sistem menampilkan hasil PD/TPD, confidence, dan probabilitas", "Berhasil"],
        ["Simpan Hasil", "Pengguna menekan Simpan Hasil setelah prediksi valid", "Hasil analisis masuk ke database", "Berhasil"],
        ["Lihat Analisis", "Pengguna membuka halaman riwayat", "Data hasil analisis tampil per siswa", "Berhasil"],
        ["Admin Web", "Admin membuka dashboard", "Admin dapat melihat, menambah, mengubah, dan menghapus data", "Berhasil"],
    ]

    items: list[ET.Element] = [
        heading("BAB 4. HASIL DAN PEMBAHASAN"),
        subheading("4.1 Analisis Kebutuhan"),
        paragraph(
            "Tahap analisis kebutuhan dilakukan untuk mengetahui kebutuhan sistem yang akan dibangun pada penelitian "
            "klasifikasi tingkat percaya diri berdasarkan analisis suara. Sistem yang dikembangkan adalah ConfiVoice, "
            "yaitu aplikasi berbasis mobile yang terhubung dengan backend machine learning untuk mengolah rekaman suara "
            "siswa. Hasil keluaran sistem berupa klasifikasi Percaya Diri (PD) atau Tidak Percaya Diri (TPD), nilai "
            "confidence, probabilitas, dan indikator suara."
        ),
        subheading("4.1.1 Analisis Kebutuhan Pengguna"),
        paragraph(
            "Pengguna utama pada aplikasi mobile adalah guru atau pengguna yang melakukan proses analisis suara siswa. "
            "Pengguna dapat melakukan registrasi, login, memasukkan nama siswa, memilih jenis kelamin, merekam atau "
            "memilih audio, menjalankan prediksi, menyimpan hasil analisis, dan melihat riwayat analisis. Selain itu, "
            "admin dapat mengakses halaman web untuk melihat dashboard, menambah data manual, mengubah data, menghapus "
            "data, serta mengelola user."
        ),
        subheading("4.1.2 Analisis Kebutuhan Data Audio"),
        paragraph(
            "Data yang digunakan pada penelitian ini berupa rekaman suara siswa dalam format WAV. Dataset disimpan pada "
            "folder ml/data dan terdiri dari dua label, yaitu PD untuk Percaya Diri dan TPD untuk Tidak Percaya Diri. "
            "Jumlah data yang digunakan adalah 48 audio, terdiri dari 24 audio PD dan 24 audio TPD. Penamaan file "
            "menggunakan penanda _pd dan _tpd agar label dapat dibaca otomatis oleh sistem."
        ),
        subheading("4.1.3 Analisis Data dan Pembentukan Model"),
        paragraph(
            "Data audio yang telah dikumpulkan diproses melalui beberapa tahap, yaitu pengecekan kualitas audio, "
            "preprocessing, ekstraksi fitur, training model, dan evaluasi model. Model yang digunakan adalah Support "
            "Vector Machine (SVM) dengan pipeline StandardScaler dan SVC. StandardScaler digunakan untuk menormalkan "
            "fitur, sedangkan SVM digunakan untuk melakukan klasifikasi dua kelas."
        ),
        subheading("4.1.4 Representasi Data"),
        paragraph(
            "Setiap file audio direpresentasikan menjadi vektor fitur numerik. Fitur yang digunakan meliputi MFCC, "
            "delta MFCC, RMS energy, zero crossing rate, spectral centroid, spectral bandwidth, spectral rolloff, pitch, "
            "durasi suara aktif, silence ratio, jumlah jeda, rata-rata durasi jeda, dan speech activity ratio. Total "
            "fitur yang dihasilkan dari setiap audio adalah 78 fitur."
        ),
        subheading("4.1.5 Pembagian Data"),
        paragraph(
            "Pembagian data dilakukan dengan pendekatan cross-validation menggunakan Stratified K-Fold. Pendekatan ini "
            "dipilih karena jumlah dataset masih terbatas, sehingga evaluasi model dapat dilakukan secara lebih seimbang "
            "terhadap kelas PD dan TPD. Data tetap dijaga agar distribusi label pada setiap fold tidak terlalu timpang."
        ),
        subheading("4.1.6 Hasil Perhitungan Model SVM"),
        paragraph(
            "Perhitungan model dimulai dari fitur audio yang telah diekstraksi, kemudian fitur dinormalisasi menggunakan "
            "rumus z = (x - mean) / standard deviation. Setelah itu, SVM menghitung pemisahan kelas menggunakan kernel RBF "
            "dengan bentuk K(xi, xj) = exp(-gamma ||xi - xj||^2). Sistem tidak menggunakan entropy dan information gain "
            "karena metode yang digunakan bukan Decision Tree, melainkan Support Vector Machine."
        ),
        paragraph(
            "Pada hasil prediksi, sistem menampilkan probability_pd dan probability_tpd. Kelas dengan probabilitas lebih "
            "besar akan dipilih sebagai hasil klasifikasi. Sebagai contoh, jika probability_pd sebesar 0,78 dan "
            "probability_tpd sebesar 0,22, maka hasil klasifikasi adalah Percaya Diri dengan confidence sebesar 78%."
        ),
        subheading("4.1.7 Evaluasi Model"),
        paragraph("Hasil evaluasi model berdasarkan training dan cross-validation dapat dilihat pada tabel berikut."),
        table(metrics),
        paragraph(
            "Berdasarkan confusion matrix, dari 24 data PD terdapat 20 data yang berhasil diklasifikasikan benar dan 4 "
            "data salah klasifikasi. Dari 24 data TPD terdapat 18 data yang berhasil diklasifikasikan benar dan 6 data "
            "salah klasifikasi."
        ),
        table(confusion),
        subheading("4.1.8 Integrasi Model"),
        paragraph(
            "Model terbaik hasil training disimpan dalam file ml/models/svm_voice_confidence_model.joblib. Model tersebut "
            "diintegrasikan dengan backend FastAPI melalui endpoint /predict. Ketika aplikasi mobile mengirimkan file "
            "audio, backend melakukan validasi format, konversi audio, ekstraksi fitur, prediksi menggunakan model SVM, "
            "lalu mengembalikan hasil prediksi ke aplikasi mobile."
        ),
        subheading("4.2 Perancangan Sistem"),
        paragraph(
            "Perancangan sistem dibuat untuk menjelaskan alur kerja ConfiVoice dari sisi pengguna, backend, database, dan "
            "admin web. Sistem terdiri dari aplikasi mobile Flutter, backend API FastAPI, model machine learning SVM, "
            "database MySQL/MariaDB, serta admin web untuk pengelolaan hasil analisis."
        ),
        subheading("4.2.1 Wireframe"),
        paragraph(
            "Wireframe aplikasi ConfiVoice dibuat sebagai rancangan awal tampilan dan alur penggunaan sistem sebelum "
            "aplikasi diimplementasikan. Wireframe menggambarkan halaman login, registrasi, halaman utama analisis, input "
            "nama siswa, pilihan jenis kelamin, fitur rekam atau pilih audio, tombol Prediksi, kartu hasil klasifikasi, "
            "tombol Simpan Hasil, dan halaman Lihat Analisis."
        ),
        subheading("4.2.2 Use Case Diagram"),
        paragraph(
            "Use case diagram digunakan untuk menggambarkan hubungan antara aktor dengan fitur yang terdapat pada sistem. "
            "Aktor pada sistem ini terdiri dari pengguna mobile dan admin. Pengguna mobile dapat registrasi, login, mengisi "
            "data siswa, merekam atau memilih audio, melakukan prediksi, menyimpan hasil, dan melihat riwayat analisis. "
            "Admin dapat login, melihat dashboard, menambah data manual, mengedit data, menghapus data, dan mengelola user."
        ),
    ]
    if use_case_drawing is not None:
        items.append(use_case_drawing)
    items.extend(
        [
            paragraph("Gambar 4.1 Use Case Diagram Sistem ConfiVoice", center=True),
            subheading("4.2.3 Flowchart Mobile"),
            paragraph(
                "Flowchart mobile menjelaskan alur penggunaan aplikasi dari awal sampai hasil analisis disimpan. Alur "
                "dimulai dari pengguna membuka aplikasi, melakukan login atau registrasi, mengisi nama siswa dan jenis "
                "kelamin, memilih atau merekam audio, menekan tombol Prediksi, kemudian sistem mengirim audio ke backend. "
                "Setelah backend mengembalikan hasil PD atau TPD, pengguna dapat menyimpan hasil analisis ke database."
            ),
        ]
    )
    if flowchart_drawing is not None:
        items.append(flowchart_drawing)
    items.extend(
        [
            paragraph("Gambar 4.2 Flowchart Mobile ConfiVoice", center=True),
            subheading("4.2.4 Entity Relationship Diagram (ERD)"),
            paragraph(
                "ERD digunakan untuk menggambarkan struktur data pada sistem ConfiVoice. Database utama yang digunakan "
                "adalah MySQL/MariaDB dengan nama database confivoice. Tabel utama yang digunakan adalah users dan "
                "prediction_results. Relasi antara kedua tabel ditunjukkan melalui user_id pada tabel prediction_results "
                "yang mengarah ke data pengguna."
            ),
            table(erd),
            subheading("4.2.5 Data Flow Diagram (DFD)"),
            paragraph(
                "Data Flow Diagram menggambarkan perpindahan data pada sistem. Pengguna mengirimkan data akun, data siswa, "
                "dan audio melalui aplikasi mobile. Backend menerima audio melalui endpoint /predict, kemudian memproses "
                "audio menggunakan model SVM. Hasil prediksi dikirim kembali ke aplikasi mobile dan dapat disimpan melalui "
                "endpoint /predictions. Data yang tersimpan dapat diakses kembali melalui halaman Lihat Analisis dan "
                "dashboard admin web."
            ),
            subheading("4.3 Implementasi"),
            paragraph(
                "Implementasi sistem dilakukan berdasarkan hasil perancangan. Bagian mobile dibuat menggunakan Flutter, "
                "backend dibuat menggunakan FastAPI, model klasifikasi dibuat menggunakan Python dan scikit-learn, sedangkan "
                "penyimpanan data menggunakan MySQL/MariaDB."
            ),
            subheading("4.3.1 Implementasi Aplikasi Mobile"),
            paragraph(
                "Aplikasi mobile memiliki fitur login, registrasi, input nama siswa, pemilihan jenis kelamin, rekam audio, "
                "pilih audio, prediksi suara, simpan hasil, dan lihat analisis. Struktur kode mobile dipisahkan ke beberapa "
                "bagian, seperti auth, home, prediction, saved analyses, dan model data agar pengelolaan kode lebih rapi."
            ),
            subheading("4.3.2 Implementasi Backend API"),
            paragraph(
                "Backend API dibuat menggunakan FastAPI. Endpoint yang digunakan antara lain /register untuk pendaftaran, "
                "/login untuk autentikasi, /predict untuk prediksi audio, /predictions untuk menyimpan dan mengambil hasil, "
                "/save sebagai endpoint alternatif penyimpanan, dan /model untuk melihat informasi model."
            ),
            subheading("4.3.3 Implementasi Database"),
            paragraph(
                "Database yang digunakan adalah MySQL/MariaDB dengan tabel users dan prediction_results. Tabel users "
                "menyimpan data akun, sedangkan prediction_results menyimpan hasil analisis suara siswa, termasuk nama "
                "siswa, jenis kelamin, label prediksi, confidence, probability_pd, probability_tpd, indikator suara, dan "
                "tanggal penyimpanan."
            ),
            subheading("4.3.4 Implementasi Admin Web"),
            paragraph(
                "Admin web dibuat menggunakan FastAPI dengan tampilan HTML. Halaman admin menyediakan fitur login, register "
                "admin, dashboard hasil prediksi, filter data, tambah data manual, edit data, hapus data, detail hasil siswa, "
                "dan pengelolaan user. Admin web memudahkan pengelola melihat hasil analisis tanpa membuka database secara langsung."
            ),
            subheading("4.4 Pengujian Sistem"),
            paragraph(
                "Pengujian sistem dilakukan untuk memastikan setiap fitur berjalan sesuai kebutuhan. Pengujian yang dilakukan "
                "meliputi pengujian black box, pengujian model klasifikasi, dan pengujian penerimaan pengguna."
            ),
            subheading("4.4.1 Pengujian Black Box"),
            paragraph("Hasil pengujian black box pada fitur utama sistem dapat dilihat pada tabel berikut."),
            table(blackbox),
            subheading("4.4.2 Pengujian Model Klasifikasi"),
            paragraph(
                "Pengujian model klasifikasi dilakukan untuk mengetahui kemampuan SVM dalam membedakan suara PD dan TPD. "
                "Berdasarkan hasil evaluasi, model memperoleh accuracy sebesar 79,17% dan F1 macro sebesar 79,13%. Nilai "
                "tersebut menunjukkan bahwa model sudah mampu mengenali pola suara dengan cukup baik, namun masih dapat "
                "ditingkatkan dengan menambah jumlah dan variasi dataset."
            ),
            subheading("4.4.3 Pengujian User Acceptance Test (UAT)"),
            paragraph(
                "Pengujian UAT dilakukan untuk mengetahui apakah aplikasi mudah digunakan dan sudah sesuai kebutuhan pengguna. "
                "Aspek yang dinilai meliputi kemudahan login, kemudahan merekam atau memilih audio, kejelasan hasil prediksi, "
                "kemudahan menyimpan hasil, kemudahan melihat riwayat analisis, dan kemudahan admin dalam melihat data."
            ),
            subheading("4.5 Pemeliharaan Sistem"),
            paragraph(
                "Pemeliharaan sistem dilakukan dengan memperbaiki kesalahan yang ditemukan selama penggunaan, menjaga endpoint "
                "API agar tetap sesuai, melakukan backup database, menambah dataset audio apabila diperlukan, dan melakukan "
                "training ulang model jika terdapat penambahan data. Pemeliharaan juga dilakukan pada tampilan aplikasi agar "
                "tetap mudah digunakan oleh pengguna."
            ),
            heading("BAB 5. KESIMPULAN DAN SARAN"),
            subheading("5.1 Kesimpulan"),
            paragraph(
                "Berdasarkan hasil perancangan, implementasi, dan pengujian, sistem ConfiVoice berhasil dibangun sebagai aplikasi "
                "klasifikasi tingkat percaya diri berdasarkan analisis suara. Sistem ini menggunakan aplikasi mobile Flutter, "
                "backend FastAPI, model machine learning SVM, database MySQL/MariaDB, dan admin web."
            ),
            paragraph(
                "Model SVM yang digunakan mampu mengklasifikasikan suara ke dalam dua kelas, yaitu Percaya Diri (PD) dan Tidak "
                "Percaya Diri (TPD). Berdasarkan hasil pengujian pada dataset yang digunakan, model memperoleh accuracy sebesar "
                "79,17% dan F1 macro sebesar 79,13%."
            ),
            paragraph(
                "Aplikasi mobile dapat digunakan untuk registrasi, login, memasukkan data siswa, merekam atau memilih audio, "
                "melakukan prediksi, menyimpan hasil, dan melihat riwayat analisis. Admin web dapat digunakan untuk memantau "
                "dan mengelola data hasil analisis yang tersimpan di database."
            ),
            subheading("5.2 Saran"),
            paragraph(
                "Saran untuk pengembangan berikutnya adalah menambah jumlah dataset audio dari lebih banyak siswa agar model "
                "dapat mengenali variasi suara yang lebih luas. Dataset juga sebaiknya direkam dengan kondisi yang lebih "
                "beragam agar model menjadi lebih stabil saat digunakan pada lingkungan nyata."
            ),
            paragraph(
                "Penelitian selanjutnya dapat membandingkan metode SVM dengan algoritma lain seperti Random Forest, K-Nearest "
                "Neighbor, atau metode deep learning. Selain itu, aplikasi dapat dikembangkan dengan fitur laporan hasil analisis "
                "dalam bentuk PDF, grafik perkembangan siswa, dan pengaturan admin yang lebih lengkap."
            ),
            paragraph(""),
        ]
    )
    return items


def patch(xml_bytes: bytes) -> bytes:
    root = ET.fromstring(xml_bytes)
    body = root.find(".//w:body", NS)
    if body is None:
        raise RuntimeError("Body dokumen tidak ditemukan.")

    use_case_drawing = copy_lampiran_drawing(body, "Use Case")
    flowchart_drawing = copy_lampiran_drawing(body, "Flowchart")

    children = list(body)
    start = None
    end = None
    for i, child in enumerate(children):
        if child.tag == qn("w:p") and paragraph_text(child).replace(" ", "").startswith("BAB4."):
            start = i
        if child.tag == qn("w:p") and paragraph_text(child) == "DAFTAR PUSTAKA":
            end = i
            break
    if start is None or end is None or end <= start:
        raise RuntimeError("Rentang BAB 4 sampai DAFTAR PUSTAKA tidak ditemukan.")

    for child in children[start:end]:
        body.remove(child)

    new_items = build_bab45(use_case_drawing, flowchart_drawing)
    insert_at = list(body).index(children[end])
    for offset, item in enumerate(new_items):
        body.insert(insert_at + offset, item)

    return ET.tostring(root, encoding="utf-8", xml_declaration=True)


def main() -> None:
    OUTPUT_DOCX.unlink(missing_ok=True)
    with zipfile.ZipFile(INPUT_DOCX, "r") as zin, zipfile.ZipFile(OUTPUT_DOCX, "w", zipfile.ZIP_DEFLATED) as zout:
        for info in zin.infolist():
            data = zin.read(info.filename)
            if info.filename == XML_PATH:
                data = patch(data)
            zout.writestr(info, data)


if __name__ == "__main__":
    main()
